0%

typora + chevereto之博客图片终极解决方法

前言

生命不息,折腾不止。话说博客没写几篇,相关的一些工具倒是折腾了一堆233333

对于用markdown来写博客的用户来说,图片的引用问题是个众所周知的难题。其实也可以直接放在本地,但是不管是内容分享和管理都不够友好。最主要的,很多人喜欢资源文件和文章本身分开(比如我),这样看起来比较整洁,也比较方便。

所以一般hexo用户会用图床来存放图片,然后文章中直接引用远程链接即可。对于读者来说,反正读博客是在web端,那么在哪找图片是一个无感知的问题。

目前的图床应用很多,作为一个不愿意去到处注册id,同时VPS比较多的人来说,我毫不犹豫的选择了自建图床。毕竟还是自己的用起来自由。

图床应用我选的chevereto,个人用户来说免费版就够了,还可以当做个人相册,安装教程略,网上一大把。

关于如何将之前的本地图片迁移到chevereto可以看我之前的这篇文章:批量将hexo文章中的本地图片转到chevereto图床

准备工作

将图片都迁移到了图床之后,写博客的时候需要打开图床的上传界面,然后将图片上传,再点开详情,再复制图片链接,再在文章中添加图片引用……

想想就麻烦,所以就联想到chevereto是支持API的,上文也使用过,所以就想着能不能自己写一个上传的插件,正好这时候typorabug了,就去搜索好用的markdown编辑器,想给它换掉。

结果找到了作者Thobian的这个项目typora-plugins-win-img,于是发现连上传插件都不用写了,有现成的了。

说到这里,typorabug我也能忍了233333。



下面是教程:

加载typora自定义插件

首先你要有一台运行chevereto的服务器

然后去typora-plugins-win-img这里下载需要的文件(也可以直接git clone

下载好后长这样:

image-20200226225336653

其实只需要plugins文件夹就行了。

接下来找到你typora的安装目录,例如我的安装目录在/usr/share/typora/

然后将plugins文件夹复制到安装目录/resources/app/下,复制完后长这样:

image-20200226230123987

然后编辑window.html文件:

找到<script src="./app/window/frame.js" defer="defer"></script>

在下面添加:

window.html
1
<script src="./plugins/image/upload.js" defer="defer"></script>

修改完后重启typora就可以加载我们自定义的插件了。

作者在插件中内置了七牛云腾讯云cos阿里云oss又拍云github等的上传功能,按需修改即可。

修改chevereto上传代码

没有chevereto,无妨,我们添加一个就是了:

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

替换成以下代码:

upload.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
(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,粘贴图片进去就可以自动上传了。

效果图:

Peek-2020-02-26-23-36.gif

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

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

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

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

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

把这段代码:

rout.api.php
1
CHV\Image::uploadToWebsite($source);

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

app/routes/overrides/route.api.php
1
2
// 这里的juanito是要传的用户,'album_id'=>4是对应的相册id,按需修改
CHV\Image::uploadToWebsite($source, 'juanito', array('album_id'=>4));

然后保存即可。

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