Hexo - 开发文章搬运功能

我们不生产文章,我们只是文章的搬运工。

最近发现一个蛮有意思的现象:在掘金刷到了一篇写挺好的文章,看完顺手三连。然后过了几天,就能在各种渠道看到这篇文章,公众号、技术博客、朋友圈……最后发现,掘金那篇也不是原创,而是搬运来的。且原文明明标注了“BY-NC-SA”版权协议,大家转载的时候都不注明。

甲:我们不生产文章,我们只是文章的搬运工。
乙:这篇文章写的很不错嘛,下一秒就是我的了。
丙:读书人的事,怎么能叫偷。
……

有意思,那我就在主题写个小功能吧,帮助大家一键搬运。(没有人会搬运我的水文)

效果是酱紫滴:

版权声明

(正常开启版权声明)

放弃抵抗

弹窗提示

(放弃抵抗,大家随意)

点击【一键打包带走】后,将全文(MarkDown、HTML、文本)复制到剪切板,并且弹窗提示。

内容获取

这部分挺难整的,主要是获取 MarkDown 比较难, hexo 渲染生成 html 之后是不会在生成文件中保留 md 源文件的。所以得想办法在 hexo 渲染的时候把 md 内容抓出来,翻了半天 hexo 官方文档,也没什么完美的思路。(吐槽一下,文档是真的简略。)

最后在生命周期中找到这个函数:hexo.extend.processor.register(rule, function(file){ var data = file.readSync(); });

主要是在渲染前,载入 md 文件的时候做一个监听,获取原始内容(也就是 MarkDown)。

这里就有一个问题了,我们需要判断 Front-Matter 中的 carrier 字段是否为 true 来决定是否开启功能。但是使用 processor 劫持原始数据的时候,内容还未被渲染,所以拿不到该字段。另外就是拿到原始数据后,如何进行持久化保存?给出如下方案:

  1. 不管三七二十一,劫持所有文章,全部保存为文件。后续通过运行时的生命周期获取 carrier 字段并判断是否启用。 (×)
  2. 在原始文件中通过正则表达式手动解析 carrier 字段。选择性的保存文件,甚至直接插入 DOM 节点。 (√)

最后选择了方案二,正则就比较头疼了……(作者正则水平不太好)

需要解析的原文片段如下,需要解析出 carrier 字段。

1
2
3
4
5
6
7
8
9
10
11
12
---
title: hexo - 开发文章搬运功能
date: 2020-07-25 17:14:33
tags: [Hexo, 主题]
keywords: hexo-theme-zhaoo, zhaoo, hexo, 主题, 文章搬运, 一键复制
categories:
- 项目
image: https://pic.izhaoo.com/20200718151502.jpg
carrier: true
---

文本内容……

网上搜到的正则是这样的,还需要做个改动:^(---(?:\r?\n(?!--|\s*$).*)*)\s*((?:\r?\n(?!---).*)*\r?\n---)$

(不会正则就很难受了,书到用时方恨少~)

复制逻辑

前面在 hexo 生命周期中(carrier())获取到了文章内容,下面要完成用户点击链接后,复制到剪切板的交互逻辑。但是 help 函数只能在模板引擎渲染的时候使用,无法同 js 文件进行同步。(类别于 php函数 与 js)

用个 hack 方法:在模板中创建一个隐藏的 input ,模板渲染的时候调用 carrier() 函数将文章内容加载到 input 中,然后在 js 中通过与 input 进行交互间接获取了内容。

1
2
3
4
<% if (theme.carrier.enable && page.carrier) { %>
<li><strong>版权声明:</strong>本文作者放弃了版权,大家随意搬运,特此奉上搬运链接:<a href="javascript:;" class="j-carrier-btn">一键打包带走</a></li>
<input type="hidden" value="<%= carrier(); %>" class="j-carrier-data carrier-data">
<% } %>

接下来完成复制到剪切板功能。很简单,通过 select 选择 input 框,再通过 document.execCommand("Copy") 方法拷贝到剪切板。有个小问题,隐藏的 input 无法被 select,我们就让它变成小透明,假装隐藏了。比较常用的就是 opacity: 0; 设置透明,但是仍会占据文档流,顺便给个 left: -100px 拖出去。

1
2
3
4
.carrier-data
opacity 0
position fixed
left -100px
1
2
3
4
5
6
7
carrier: function () {
$(".j-carrier-btn").on("click", function () {
$(".j-carrier-data").select();
document.execCommand("Copy");
alert('已经复制到剪切板');
});
}

消息弹窗

文章复制到剪切板后,需要弹出消息弹窗,提示用户。

主题开发之初我就给自己规定,不到万不得已,绝不用第三方库。(jQuery实在是没办法了)所以我们就自己封装一个消息弹窗组件。

第一版代码:

逻辑很清晰。触发弹窗后,先构造 DOM 并插入到 body (根),添加 in 样式(渐入动画)。若干秒后移除 in 样式(渐出动画),并删除 DOM

1
2
3
4
5
6
7
8
9
10
Message: function ({ text, type, timer }) {
var message = '<div class="zui-message ' + (type || "info") + '"><p>' + text + '</p></div>';
$("body").append(message);
var e = $(".zui-message");
e.addClass("in");
setTimeout(function () {
e.removeClass("in");
$(this).remove();
}, timer || 3000);
}
1
Message({ text: '已复制到剪切板', type: 'success' });
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
$color-info = #909399
$color-success = #67c23a
$color-danger = #f56c6c
$color-warning = #e6a23c
$color-info-bgc = #edf2fc
$color-success-bgc = #f0f9eb
$color-danger-bgc = #fef0f0
$color-warning-bgc = #fdf6ec
.zui-message
position fixed
margin 0
padding 10px 20px
top -50px
left 50%
min-width 250px
overflow hidden
z-index 2020
display flex
justify-content center
align-items center
transform translateX(-50%)
transition top 0.4s
background-color $color-info-bgc
p
margin 0
color $color-info
for $type in info success danger warning //遍历四种类型
&.{$type}
background-color convert('$color-' + $type + '-bgc') //拼接变量名,引入颜色
p
color convert('$color-' + $type)
&.in
top 50px //渐入动画

跑一便,似乎不太对,点击后弹窗直接显示,三秒后弹窗直接消失,没有出现动画效果。

分析一下原因:由于动态插入 DOM 后直接添加了样式(绘制未完成),此时浏览器还未计算出 CSS 属性就直接给绑定了 transition,导致直接渲染了最终效果,给个异步延迟可以解决。关闭很好理解,没等渐出效果生效就直接删除 DOM 了。

第二版代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Message: function ({ text, type, timer }) {
var message = '<div class="zui-message ' + (type || "info") + '"><p>' + text + '</p></div>';
$("body").append(message);
var e = $(".zui-message");
setTimeout(function () {
e.addClass("in");
}, 0);
setTimeout(function () {
e.removeClass("in");
setTimeout(function () {
$(this).remove();
}, 0);
}, timer || 3000);
}

一堆 setTimeout(fn, 0) 太丑了,绑定事件代替之。

第三版代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Message: function ({ text, type, timer }) {
var message = '<div class="zui-message ' + (type || "info") + '"><p>' + text + '</p></div>';
$("body").append(message);
var e = $(".zui-message");
e.ready(function () {
e.addClass("in");
setTimeout(function () {
e.removeClass("in");
e.on("transitionend webkitTransitionEnd", function () {
$(this).remove();
});
}, timer || 3000);
});
}

动画效果是出来了,但是感觉好卡,一帧一帧的。

犯了个低级错误:用定位来做动画,浏览器主线程会不停地回流,改变元素位置,然后再计算下一个渲染位置。优化一下,使用 transform 代替,浏览器只会计算动画初始位置和结束位置,不会频繁触发回流

1
2
3
4
5
6
7
8
9
.zui-message
top 0px
left 50%
opacity 0 //顺便加个淡入
transform translate(-50%, -50px)
transition opacity 0.3s, transform 0.4s, top 0.4s
&.in
transform translate(-50%, 50px)
opacity 1

最后套个节流函数:

1
2
3
4
5
6
7
carrier: function () {
$(".j-carrier-btn").on("click", utils.throttle(function () { //节流
$(".j-carrier-data").select();
document.execCommand("Copy");
zui.Message({ text: '已复制到剪切板', type: 'success' });
}, 3000));
}
查看评论