前言

Typora是个很好用的本地Markdown编辑器,可即使如此也避免不了插入图片这个难题。拿我举例,每次要把图片上传到我的图床,获取到链接后再回到Typora中插入,非常的麻烦,有没有什么办法提高效率呢?有。

准备工作

网上冲浪的时候发现了这款插件typora-plugins-win-img

typora-plugins-win-img 插件在编辑时,跟之前没有任何差别。不论是直接粘贴QQ、微信等工具的截图,还是通过“编辑->图片工具->插入本地图片”,都会自动帮你将图片上传到网络服务器,并替换文件中的图片地址为网络图片地址。

注意的小细节:

  1. 如图片原本就是网络图片地址,插件将保持原链接不处理(正则匹配:/^(https?:)?\/\//i);
  2. 如发现图片链接还是本地文件地址,没有被正常上传,可以点击下对应的图片将再次触发上传操作;
  3. 不论图片上传成功或者失败,编辑器顶部都会有提醒;

插件给出的方案是将图片自动上传到阿里云OSS、腾讯云COS或者FTP空间里等,那么修改一下自然可以让他上传到我们的图床里。

我的图床用的是Chevereto,官方给出了API接口提供上传服务https://chevereto.com/docs/api-v1,API Key可以去后台-仪表盘-设置-API查看:

API Key

安装插件

typora-plugins-win-img下载下来,解压里面的/plugins/upload.phpwindow.html到Typora的安装目录下的resources/app/下。

然后打开刚刚解压好的/plugins/image/upload.js,作者在插件中内置了七牛云腾讯云cos阿里云oss又拍云github等的上传功能,按需修改即可。

修改代码

编辑安装目录/resources/app/plugins/upload.js

替换成以下代码:

(function($){
    // 配置信息
    var setting = {
        target:'self',        
        //target=self 时涉及的配置参数
        self: {
            url: 'http://your_website/api/1/upload/',   //这里填你图床的api地址
            key: 'YOUR API KEY',    //这里填你的APIKEY
            action: 'upload',
            //自定义请求头,做校验,防止其他人随意调接口
        },

        //==============回调函数==============
        // 上传成功
        onSuccess: function(url){
            //替换图片位置
            setting.element.removeAttr(locked).attr('src', url);
            setting.element.
                parent('span[md-inline="image"]').
                data('src', url).
                find('.md-image-src-span').
                html(url);
            //提醒
            var text = '图片上传成功:'+ url;
            $('#'+noticeEle).
            css({
                'background':'rgba(0,166,90,0.7)',
            }).
            html(text).
            show().
            delay(5000).
            fadeOut();
        },
        // 上传失败
        onFailure: function(text){
            setting.element.removeAttr(locked);
            $('#'+noticeEle).
            css({
                'background':'rgba(255,0,0,0.7)'
            }).
            html(text).
            show().
            delay(10000).
            fadeOut();
        }
    };

    var helper = {
        // 将base64转文件流
        base64ToBlob: function(base64) {
            var arr = base64.split(',');
            var mime = arr[0].match(/:(.*?);/)[1] || 'image/png';
            // 去掉url的头,并转化为byte
            var bytes = window.atob(arr[1]);
            // 处理异常,将ascii码小于0的转换为大于0
            var ab = new ArrayBuffer(bytes.length);
            // 生成视图(直接针对内存):8位无符号整数,长度1个字节
            var ia = new Uint8Array(ab);

            for (var i = 0; i < bytes.length; i++) {
                ia[i] = bytes.charCodeAt(i);
            }

            return new Blob([ab], {
                type: mime
            });
        },
        // 根据base64获取文件扩展名
        extension: function(base64){
            var ext = base64.split(',')[0].match(/data:image\/(.*?);base64/)[1] || 'png';
            console.log("the file ext is: "+ext);
            return ext;
        },
        // 根据base64获取图片内容
        content: function(base64){
            var content = base64.split(',')[1];
            return content;
        },
        mine: function(base64){
            var arr  = base64.split(',');
            var mime = arr[0].match(/:(.*?);/)[1] || 'image/png';
            console.log("the file mime is: "+mime);
            return mime;
        },
        // 时间格式化函数
        dateFormat: function (date, fmt) {
            var o = {
                "M+": date.getMonth() + 1, //月份
                "d+": date.getDate(), //日
                "H+": date.getHours(), //小时
                "m+": date.getMinutes(), //分
                "s+": date.getSeconds(), //秒
                "q+": Math.floor((date.getMonth() + 3) / 3), //季度
                "S": date.getMilliseconds() //毫秒
            };
            if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
            for (var k in o)
            if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            return fmt;
        }
    };

    // 上传文件的方法
    var upload = {
        // 自建服务器存储时,适用的上传方法
        self : function(fileData, successCall, failureCall){

            var filename = helper.dateFormat((new Date()),'yyyyMMddHHmmss-')+Math.floor(Math.random() * Math.floor(999999))+'.'+helper.extension(fileData);
            var fileData = helper.base64ToBlob(fileData);
            var formData = new FormData();
            formData.append('source', fileData);
            formData.append('filename', filename);
            $.ajax({
                type: "POST",
                url: setting.self.url+'?key='+setting.self.key+'&upload='+setting.self.action,
                processData:false,
                data:formData,
                contentType: false,
                success: function(result) {
                    //奇葩的阿里云,响应内容为空
                    console.log(result);
                    successCall(result.image.url);
                },
                error:function(result){
                    console.log(result);
                    failureCall('服务响应解析失败,请稍后再试');
                }
            });
        },

    };

    //读取文件为base64,再回调上传函数将文件发到服务器
    var loadImgAndSend = function(url){
        var xhr = new XMLHttpRequest();
        xhr.onload = function() {
            var reader = new FileReader();
            reader.onloadend = function() {
                switch (setting.target) {
                    case 'self':
                        upload.self(reader.result, setting.onSuccess, setting.onFailure);
                        break;
                    default:
                        setting.onFailure('配置错误,不支持的图片上传方式,可选方式:self/tencent/aliyun/qiniu/github');
                } 
            }
            reader.readAsDataURL(xhr.response);
        };
        xhr.open('GET', url);
        xhr.responseType = 'blob';
        xhr.send();
    }

    // 核心方法
    var locked = 'doing';
    var noticeEle = 'image-result-notice';    
    $.image = {};
    $.image.init = function(options){
        options = options||{};
        setting.target = options.target||setting.target;
        setting.self = options.self||setting.self;

        // 监听鼠标事件
        $('#write').on('mouseleave click', 'img', function(e){
            try{
                var src = e.target.src;
                if( /^(https?:)?\/\//i.test(src) ){
                    console.log('The image already upload to server, url:' + src);
                    return false;
                }
                setting.element = element = $(e.target);
                var doing = element.attr(locked)=='1';
                if( doing ){
                    console.log('uploading...');
                    return false;
                }else{
                    element.attr(locked, '1');
                }
                $('content').prepend('<div id="'+noticeEle+'" style="position:fixed;height:40px;line-height:40px;padding:0 15px;overflow-y:auto;overflow-x:hidden;z-index:10;color:#fff;width:100%;display:none;"></div>');
                //转换成普通的图片地址
                //src = src.substring(8, src.indexOf('?last'));
                loadImgAndSend(src);
            }catch(e){console.log(e);};
        });
    };
})(jQuery);

$.image.init();

然后将自己的APIKEY及图床地址填入代码开头对应位置即可,由于本文用不到其余图床的代码,所以这里删掉了。

然后保存退出,重新打开typora,粘贴图片进去就可以自动上传了。

typora不支持直接粘贴gif图片,所以插入动图需要ctrl+shift+i,然后手动选择动图添加,之后也会自动上传替换。

自定义chevereto上传用户和上传相册(可选)

cheveretoapi默认会新建一个相册,但是会有bug,就是在相册菜单下找不到这个相册,只能从图片菜单下去打开,所以可以按照下面的方法修改api上传的默认相册和用户。

打开cheveretoweb目录,将默认的app/routes/route.api.php的文件复制到

app/routes/overrides/route.api.php文件夹,

把这段代码:

CHV\Image::uploadToWebsite($source);

改成这个:(将user更换成目标用户名或用户id)

// 这里的user是要传的用户,'album_id'=>4是对应的相册id,按需修改
CHV\Image::uploadToWebsite($source, 'user', array('album_id'=>4));

然后保存即可。

也可以在api中指定用户和相册,只需要在uploadToWebsite()方法前增加几个request字段就行了,不知道官方为啥不提供,可能是出于安全性考虑的吧。

最后修改:2020 年 07 月 02 日 09 : 48 PM
如果觉得我的文章对你有用,请随意赞赏