最近朋友推荐了一个用markdown写博客的神器,typora,用了一下,真的是好用到哭,之前是使用的markText,其实也挺好用的,这两个都是开源的,相对来说,Marktext做的更加美观点,Marktext唯一的缺点是,我在使用的时候,粘贴图片时上传是有bug的。因此,决定用下typora。切换到typora之后,发现快捷建基本上和Marktext是一样的,真的是无痛切入,然后剩下的就是配置一下如何上传图片了。
配置图片地方可以通过快捷键cmd+,
呼出。上传服务有多张方式,我选择Custom Command,执行的命名是PicGo.
首先我们来探讨一下,为何使用PicGo。其实,typora是有非常多种选择的:
对比了一下iPic
和uPic
,他俩都是收费的,因此在有免费的情况下优先不考虑,然后picGo是有GUI
版本和命令行(CLI)版本的,这里我之所以考虑CLI版本的,是因为,实际上,上传图片只有在我写markdown的时候需要,因此,装一个GUI版本的显得有些重,在者,安装包也有点大,Mac空间还是有限的,本着能省就省的原则,果断选择安装CLI版本。
1、如果说找不到node,那么你需要认为指定node路径。因为通过源码可以发现,picgo默认的node路径是:#!/usr/bin/env node
<img src="https://raw.githubusercontent.com/bravekingzhang/pic_go/master/2020/0E505D78-BE29-41A6-8459-A5A2D99C0DAD.png" alt="0E505D78-BE29-41A6-8459-A5A2D99C0DAD" style="zoom: 50%;" />
2、如果你公司的网络有代理,那么请使用 --proxy|-p 添加代理,比如: -p http://127.0.0.1:12379
如图所示,复制一张图片,在命令行下执行上述命令,图片就上传了,我这里配置的是github作为图床。用了之后,真心感慨,PicGo是在是好用,对于我现在写markdown的需求来说,当真是无可挑剔了。所以,想着,是否应该研究下他的原理呢?
通过这个流程图,我们可以发现,picgo V2版本确实比较清晰,让我想起了移动端那个图片加载神器Glide,可以提供各种各样的Transformer,来做图片处理,比如圆角,加边框,缩放,加水印等等。尽然没想到,一个小小的PicGo居然也做得这么人性化。
首先,我们知道,PicGo上传有两种方式:
1、直接上传剪切板复制的图片。
2、上传指定路径下的图片。
其实,两种方式都是一样的,通过源码可以看到,通过剪切板方式传图片,实际上也是最终转化为图片路径的方式。
所以,我们的主流程还是没变,那就是 upload imagePath
.其实就是上面那个 lifecycle.start(input)
了,所以,跟踪一下:
async start (input: any[]): Promise<PicGo> {
try {
// images input
if (!Array.isArray(input)) {
throw new Error('Input must be an array.')
}
this.ctx.input = input
this.ctx.output = []
// lifecycle main
await this.beforeTransform()
await this.doTransform()
await this.beforeUpload()
await this.doUpload()
await this.afterUpload()
return this.ctx
} catch (e) {
this.ctx.log.warn('failed')
this.ctx.emit('uploadProgress', -1)
this.ctx.emit('failed', e)
this.ctx.log.error(e)
if (this.ctx.getConfig<Undefinable<string>>('debug')) {
throw e
}
return this.ctx
}
}
可以看到,这里就是我们原理环节提到的那个流程了,doTransform->doUpload等环节了。我们先看看Transform做了些什么?
private async doTransform (): Promise<PicGo> {
this.ctx.emit('uploadProgress', 30)
this.ctx.log.info('Transforming...')
const type = this.ctx.getConfig<Undefinable<string>>('picBed.transformer') || 'path'
let transformer = this.ctx.helper.transformer.get(type)
if (!transformer) {
transformer = this.ctx.helper.transformer.get('path')
this.ctx.log.warn(`Can't find transformer - ${type}, switch to default transformer - path`)
}
await transformer?.handle(this.ctx)
return this.ctx
}
默认就是找到path这个来处理,而这个path,我们看看,他在plugins/transformer/包下,看下实际上做了啥?
const handle = async (ctx: PicGo): Promise<PicGo> => {
const results: IImgInfo[] = ctx.output
await Promise.all(ctx.input.map(async (item: string, index: number) => {
let info: IPathTransformedImgInfo
if (isUrl(item)) {
info = await getURLFile(item)
} else {
info = await getFSFile(item)
}
if (info.success && info.buffer) {
try {
const imgSize = getImgSize(ctx, info.buffer, item)
results[index] = {
buffer: info.buffer,
fileName: info.fileName,
width: imgSize.width,
height: imgSize.height,
extname: info.extname
}
} catch (e) {
ctx.log.error(e)
}
} else {
ctx.log.error(info.reason)
}
}))
// remove empty item
ctx.output = results.filter(item => item)
return ctx
}
其实就是处理下拿到 IPathTransformedImgInfo
这个信息,这个信息其实是有冗余的,因为PicGo
实现的是多图床上传,因此,不同的图片所需的参数必然不同,需要一个可以包容较多场景的描述信息,而这个就是IPathTransformedImgInfo
。
export interface IImgInfo {
buffer?: Buffer
base64Image?: string
fileName?: string
width?: number
height?: number
extname?: string
[propName: string]: any
}
export interface IPathTransformedImgInfo extends IImgInfo {
success: boolean
}
接下来,我们直接进入upload环节,其代码放在/plugin/upload中,这里面,作者已经预制了很多个图床上传插件了,因为我使用的github图床,因此就看了下github:
const handle = async (ctx: PicGo): Promise<PicGo> => {
const githubOptions = ctx.getConfig<IGithubConfig>('picBed.github')
if (!githubOptions) {
throw new Error('Can\'t find github config')
}
try {
const imgList = ctx.output
for (const img of imgList) {
if (img.fileName && img.buffer) {
const base64Image = img.base64Image || Buffer.from(img.buffer).toString('base64')
const data = {
message: 'Upload by PicGo',
branch: githubOptions.branch,
content: base64Image,
path: githubOptions.path + encodeURI(img.fileName)
}
const postConfig = postOptions(img.fileName, githubOptions, data)
const body = await ctx.Request.request(postConfig)
if (body) {
delete img.base64Image
delete img.buffer
if (githubOptions.customUrl) {
img.imgUrl = `${githubOptions.customUrl}/${githubOptions.path}${img.fileName}`
} else {
img.imgUrl = body.content.download_url
}
} else {
throw new Error('Server error, please try again')
}
}
}
return ctx
} catch (err) {
ctx.emit('notification', {
title: '上传失败',
body: '服务端出错,请重试'
})
throw err
}
}
通过这个重配置文件中读取我们对于github图床的配置,比如分支,token等信息,对,就是这行代码。
const githubOptions = ctx.getConfig<IGithubConfig>('picBed.github')
其他部分就是很简单了,对接github的文件上传api,我们关心拿到结果之后,拿到结果之后,如果用户配置了customUrl,那么这里就可以把图片链接包装为CDN链接,因为考虑到有些地方访问github图片还是挺慢的,这个是在是太人性化了。
单纯的实现文件复制到剪切板,然后调用github的接口上传到github上,这个过程其实不难,关键是如何做的这么通用,足见作者是花了心思的。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。