leanote 折腾笔记

写博客、做笔记,使用markdown 是挺爽的,然而图片插入却不是很方便。目前发现能使用ctrl+v 完成截图插入的笔记软件有为知笔记、leanote,很可惜,这两个软件都开始收费了,leanote更是霸道,不续费,以前的笔记都没法查看。好在leanote是开源的,改改代码,使leanote生成的私有图片协议链接,变成本地链接;去掉vip续费提醒也不失为一款出色的markdown编辑器。

默认图片是保存到本地的,导出markdown文件,将会上传图片到七牛云,替换markdown中的图片链接,这样就方便发布到博客、分享给别人看了。使用效果如下:

修改过程记录一下,给用的上的人参考。

1.修改图片保存目录

public/js/app/service.js
public/js/app/service_login.js

var basePath = app.getPath('appData') + '/leanote';

改为

const path = require('path');
var basePath = path.dirname(path.dirname(app.getAppPath())) + '/data';
basePath = basePath.replace(/\\/g,'/');

修改后,笔记数据就默认保存到leanote软件目录下的data目录了。

2. 修改md 插入图片路径

2.1 截图粘贴图片

node_modules/file.js

pasteImage2 方法

me._addImage(Common.objectId(), filePath, function(newImg) {
                callback && callback(Evt.getImageLocalUrl(newImg.FileId));
            });

改为

me._addImage(Common.objectId(), filePath, function(newImg) {
                callback && callback(Evt.getImageLocalUrl(newImg.Path));
            });

node_modules/evt.js

    getImageLocalUrl: function(fileId) {
        return 'leanote://file/getImage?fileId=' + fileId;
    }

改为

    getImageLocalUrl: function(fileId) {
        return fileId;
    }

2.2 插入图片

public/js/common.js

insertLocalImage 方法

var url = EvtService.getImageLocalUrl(newImage.FileId);

改为

var url = EvtService.getImageLocalUrl(newImage.Path);

3.去掉过期需要升级vip

public/js/app/page.js

UserService.init 方法

 fullSync(function(err, info) {
                    err = false; //加上这行
                    if (err) {

4.导出md, 图片上传到七牛云

4.1 修改配置文件

public/config.js

var Config = {
    "plugins": [
        "theme",
        "import_leanote",
        "import_evernote",
        "import_html",
        "export_md", //新增
        "export_pdf",
        "export_html",
        "export_leanote",
        "export_evernote",
        "langs",
        "accounts"
    ],
    "langs": [
        {
            "filename": "en-us",
            "name": "English"
        },
        {
            "filename": "de-de",
            "name": "Deutsch"
        },
        {
            "filename": "zh-cn",
            "name": "简体中文"
        },
        {
            "filename": "zh-hk",
            "name": "繁体中文"
        },
        {
            "filename": "ja-jp",
            "name": "日本語"
        }
    ],
    "lang": "",
    "theme": "",
    "view": "snippet",
    //新增七牛配置
    "qiniu": {
        "ak": "xx",
        "sk": "xxx",
        "bucket": "mynote",
        "domain": "http://note.img.xuphp.com", //图片域名
        "uploadUrl": "http://up-z2.qiniu.com" //上传域名
    }
};

4.2 添加第三方七牛云sdk

npm install qn

4.3 新增导出markdown插件

public/plugins/export_md/plugin.js

/**
 * 导出markdown插件
 * 上传图片到七牛云
 *
 */
define(function () {
    var async; //  = require('async');
    var resanitize; // = require('resanitize');
    //===========
    // start

    var exportmarkdown = {
        langs: {
            'en-us': {
                'export': 'Export markdown',
                'Exporting': 'Exporting',
                'Exporting: ': 'Exporting: ',
                'exportSuccess': 'markdown saved successful!',
                'exportFailure': 'markdown saved failure!',
                'notExists': 'Please sync your note to ther server firslty.'
            },
            'de-de': {
                'export': 'Als markdown exportieren',
                'Exporting': 'Exportiere',
                'Exporting: ': 'Exportiere: ',
                'exportSuccess': 'markdown erfolgreich gespeichert!',
                'exportFailure': 'markdown speichern fehlgeschlagen!',
                'notExists': 'Bitte Notizen zuerst mit dem Server synchronisieren.'
            },
            'zh-cn': {
                'export': '导出markdown',
                'Exporting': '正在导出',
                'Exporting: ': '正在导出: ',
                'exportSuccess': 'markdown导出成功!',
                'exportFailure': 'markdown导出失败!'
            },
            'zh-hk': {
                'export': '導出markdown',
                'Exporting': '正在導出',
                'Exporting: ': '正在導出: ',
                'exportSuccess': 'markdown導出成功!',
                'exportFailure': 'markdown導出失敗!'
            }
        },

        _inited: false,
        init: function () {
            var me = this;
            if (me._inited) {
                return;
            }

            async = require('async');
            resanitize = require('resanitize');

            me._inited = true;
        },

        replaceAll: function (src, pattern, to) {
            if (!src) {
                return src;
            }
            while (true) {
                var oldSrc = src;
                src = src.replace(pattern, to);
                if (oldSrc === src) {
                    return src;
                }
            }
        },

        fixFilename: function (filename) {
            var reg = new RegExp("/|#|\\$|!|\\^|\\*|'| |\"|%|&|\\(|\\)|\\+|\\,|/|:|;|<|>|=|\\?|@|\\||\\\\", 'g');
            filename = filename.replace(reg, "-");
            // 防止出现两个连续的-
            while (filename.indexOf('--') != -1) {
                filename = this.replaceAll(filename, '--', '-');
            }
            if (filename.length > 1) {
                // 最后一个-
                filename = filename.replace(/\-$/, '');
            }
            return filename;
        },


        render: function (note, callback) {
            var me = this;
            me.fixFiles(note, function (content, files) {
                // 非markdown才需要这样, 补全html标签
                if (!note.IsMarkdown) {
                    content = $('<div>' + content + '</div>').html();
                }

                callback(content);
            });
        },

        findAllImages: function (note) {
            var content = note.Content;
            var allMatchs = [];

            // markdown下
            // [](http://localhost://fileId=32);
            if (note.IsMarkdown) {
                var reg = new RegExp('!\\[([^\\]]*?)\\]\\((.+)\\)', 'g');
                var matches = reg.exec(content);
                while (matches) {
                    var all = matches[0];
                    var title = matches[1]; // img与src之间
                    var fileId = matches[2];
                    allMatchs.push({
                        fileId: fileId,
                        title: title,
                        all: all
                    });
                    // 下一个
                    matches = reg.exec(content);
                }
            }
            return allMatchs;
        },
        /**
         * 七牛云
         * @param file 本地路径
         * @param fileName
         */
        upload: function (file,fileName) {
            var path = require("path");

            var qn = require('qn');
            var client = qn.create({
                accessKey: Config.qiniu.ak,
                secretKey: Config.qiniu.sk,
                bucket: Config.qiniu.bucket,
                //origin: 'http://{bucket}.u.qiniudn.com',
                // timeout: 3600000, // default rpc timeout: one hour, optional
                uploadURL: Config.qiniu.uploadUrl
            });

            // upload a file with custom key
            client.uploadFile(file, {key: fileName}, function (err, result) {
                console.log(result);
                Notify.show({title: 'Info', body: getMsg(result.url+"上传成功!")});
                // {
                //   hash: 'FhGbwBlFASLrZp2d16Am2bP5A9Ut',
                //   key: 'qn/lib/client.js',
                //   url: 'http://qtestbucket.qiniudn.com/qn/lib/client.js'
                //   "x:ctime": "1378150371",
                //   "x:filename": "client.js",
                //   "x:mtime": "1378150359",
                //   "x:size": "21944",
                // }
            });
        },

        fixFiles: function (note, callback) {
            var me = this;

            var content = note.Content;

            var allImages = me.findAllImages(note) || [];

            var allMatchs = allImages;

            if (allMatchs.length == 0) {
                callback(content, []);
                return;
            }


            var files = {}; // fileId => {}

            function replaceContent() {

                for (var i = 0; i < allMatchs.length; ++i) {
                    var eachMatch = allMatchs[i];
                    var link;
                    var href;
                    var fileName = eachMatch.fileId.split("/").slice(-3).join("/");

                    href = Config.qiniu.domain + '/' + fileName;
                    me.upload(eachMatch.fileId,fileName);
                    //上传图片到七牛云
                    link = '![' + eachMatch.title + '](' + href + ')';
                    content = content.replace(eachMatch.all, link);
                }
            }

            // 得到图片资源
            var fileIdFixed = {};
            async.eachSeries(allImages, function (eachMatch, cb) {
                var fileId = eachMatch.fileId;
                if (fileIdFixed[fileId]) {
                    cb();
                    return;
                }

                Api.fileService.getImageInfo(fileId, function (err, doc) {
                    fileIdFixed[fileId] = true;
                    if (doc) {
                        var base64AndMd5 = Api.fileService.getFileBase64AndMd5(doc.Path);
                        if (base64AndMd5) {
                            files[doc.FileId] = {
                                base64: base64AndMd5.base64,
                                md5: base64AndMd5.md5,
                                type: doc.Type,
                                title: doc.Title
                            }
                        }
                        cb();
                    }
                    else {
                        cb();
                    }
                });

            }, function () {
                replaceContent();
                callback(content, files);
            });
        },

        //--------------

        // 得到可用的文件名, 避免冲突
        getExportedFilePath: function (pathInfo, n, cb) {
            var me = this;
            if (n > 1) {
                pathInfo.nameNotExt = pathInfo.nameNotExtRaw + '-' + n;
            }
            var absPath = pathInfo.getFullPath();

            // Api.nodeFs.existsSync(absPath) 总是返回false, 不知道什么原因
            // 在控制台上是可以的
            Api.nodeFs.exists(absPath, function (exists) {
                if (!exists) {
                    cb(absPath);
                }
                else {
                    me.getExportedFilePath(pathInfo, n + 1, cb);
                }
            });
        },

        getTargetPath: function (callback) {
            // showSaveDialog 不支持property选择文件夹
            Api.gui.dialog.showOpenDialog(Api.gui.getCurrentWindow(),
                {
                    defaultPath: Api.gui.app.getPath('userDesktop') + '/',
                    properties: ['openDirectory']
                },
                function (targetPath) {
                    callback(targetPath);
                }
            );
        },

        loadingIsClosed: false,

        exportmarkdownForNotebook: function (notebookId) {
            var me = this;
            if (!notebookId) {
                return;
            }
            me.getTargetPath(function (targetPath) {
                if (!targetPath) {
                    return;
                }

                me.loadingIsClosed = false;
                Api.loading.show(Api.getMsg('导出markdown'),
                    {
                        hasProgress: true,
                        isLarge: true,
                        onClose: function () {
                            me.loadingIsClosed = true;
                            setTimeout(function () {
                                me.hideLoading();
                            });
                        }
                    });
                Api.loading.setProgress(1);

                Api.noteService.getNotes(notebookId, function (notes) {
                    if (!notes) {
                        me.hideLoading();
                        return;
                    }

                    var total = notes.length;
                    var i = 0;
                    async.eachSeries(notes, function (note, cb) {
                        if (me.loadingIsClosed) {
                            cb();
                            me.hideLoading();
                            return;
                        }
                        i++;
                        Api.loading.setProgress(100 * i / total);
                        me._exportmarkdown(note, targetPath, function () {
                            cb();
                        }, i, total);
                    }, function () {
                        me.hideLoading();
                        Notify.show({title: 'Info', body: getMsg('导出 markdown成功')});
                    });
                });
            });
        },

        hideLoading: function () {
            setTimeout(function () {
                Api.loading.hide();
            }, 1000);
        },

        exportmarkdown: function (noteIds) {
            var me = this;
            if (!noteIds || noteIds.length == 0) {
                return;
            }
            me.getTargetPath(function (targetPath) {
                if (!targetPath) {
                    return;
                }

                me.loadingIsClosed = false;
                Api.loading.show(Api.getMsg('导出markdown'),
                    {
                        hasProgress: true,
                        isLarge: true,
                        onClose: function () {
                            me.loadingIsClosed = true;
                            setTimeout(function () {
                                me.hideLoading();
                            });
                        }
                    });
                Api.loading.setProgress(1);

                var i = 0;
                var total = noteIds.length;

                async.eachSeries(noteIds, function (noteId, cb) {
                    if (me.loadingIsClosed) {
                        cb();
                        return;
                    }

                    i++;
                    Api.loading.setProgress(100 * i / total);
                    Api.noteService.getNote(noteId, function (note) {
                        me._exportmarkdown(note, targetPath, function () {
                            cb();
                        }, i, total);
                    });

                }, function () {
                    me.hideLoading();
                    Notify.show({title: 'Info', body: getMsg('导出 markdownSuccess')});
                });
            });
        },

        _exportmarkdown: function (note, path, callback, i, total) {
            var me = this;
            if (!note) {
                return;
            }

            if (me.loadingIsClosed) {
                callback();
                return;
            }

            setTimeout(function () {
                Api.loading.setMsg(Api.getMsg('导出markdown: ') + (note.Title || getMsg('Untitled')));
                Api.loading.setProgressRate(i + '/' + total);
            }, 100);

            var name = note.Title ? note.Title + '.md' : getMsg('Untitled') + '.md';
            name = me.fixFilename(name);

            var targetPath = path + Api.commonService.getPathSep() + name;

            // 将路径和名字区分开
            var pathInfo = Api.commonService.splitFile(targetPath);
            pathInfo.nameNotExt = me.fixFilename(pathInfo.nameNotExt); // 重新修正一次
            var nameNotExt = pathInfo.nameNotExt;
            pathInfo.nameNotExtRaw = pathInfo.nameNotExt;

            // 得到可用文件的绝对路径
            me.getExportedFilePath(pathInfo, 1, function (absmarkdownFilePath) {
                me.render(note, function (content) {
                    Api.commonService.writeFile(absmarkdownFilePath, content);
                    callback();
                });
            });
        },

        // 打开前要执行的
        onOpen: function () {
            var me = this;
            var gui = Api.gui;

            var menu = {
                label: Api.getMsg('导出 markdown'),
                enabled: function (noteIds) {
                    return true;
                },
                click: (function () {
                    return function (noteIds) {
                        me.init();
                        me.exportmarkdown(noteIds);
                    }
                })()
            };
            Api.addExportMenu(menu);

            Api.addExportMenuForNotebook({
                label: Api.getMsg('导出 markdown'),
                enabled: function (notebookId) {
                    return true;
                },
                click: (function () {
                    return function (notebookId) {
                        me.init();
                        me.exportmarkdownForNotebook(notebookId);
                    }
                })()
            });
        },
        // 打开后
        onOpenAfter: function () {
        },
        // 关闭时需要运行的
        onClose: function () {
        }
    };

    return exportmarkdown;
});

大功告成,以后有空再增加一键发布到wordpress的功能。