我们不生产文章,我们只是文章的搬运工。
最近发现一个蛮有意思的现象:在掘金刷到了一篇写挺好的文章,看完顺手三连。然后过了几天,就能在各种渠道看到这篇文章,公众号、技术博客、朋友圈……最后发现,掘金那篇也不是原创,而是搬运来的。且原文明明标注了“BY-NC-SA”版权协议,大家转载的时候都不注明。
甲:我们不生产文章,我们只是文章的搬运工。
乙:这篇文章写的很不错嘛,下一秒就是我的了。
丙:读书人的事,怎么能叫偷。
……
有意思,那我就在主题写个小功能吧,帮助大家一键搬运。(没有人会搬运我的水文)
效果是酱紫滴:
版权声明
(正常开启版权声明)
放弃抵抗
弹窗提示
(放弃抵抗,大家随意)
点击【一键打包带走】后,将全文(MarkDown、HTML、文本)复制到剪切板,并且弹窗提示。
内容获取
2020.09.06
今日去 hexo
文档转了一圈,发现居然更新了(昨天更新的),新增了一些实用 API
,相见恨晚啊!其中就有 page.raw
用来获取文章原始内容,也就是 MarkDown
,这样就不需要之前这么折腾了,so easy~
直接贴代码(这部分是辅助函数 Helper
):
'use strict';
module.exports.carrier = function (hexo) { hexo.extend.helper.register('carrier', function () { var config = hexo.theme.config; if (!config.carrier || !config.carrier.enable || !config.carrier.type) { return; } var data; switch (config.carrier.type) { case 'html': data = this.page.content; break; case 'markdown': data = this.page.raw; break; case 'text': data = this.strip_html(this.page.content); break; default: data = '没有抓到内容哦~'; } return data; }); }
|
2020.07.22
已废弃……
这部分挺难整的,主要是获取 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
劫持原始数据的时候,内容还未被渲染,所以拿不到该字段。另外就是拿到原始数据后,如何进行持久化保存?给出如下方案:
- 不管三七二十一,劫持所有文章,全部保存为文件。后续通过运行时的生命周期获取
carrier
字段并判断是否启用。 (×)
- 在原始文件中通过正则表达式手动解析
carrier
字段。选择性的保存文件,甚至直接插入 DOM
节点。 (√)
最后选择了方案二,正则就比较头疼了……(作者正则水平不太好)
需要解析的原文片段如下,需要解析出 carrier
字段。
--- 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
进行交互间接获取了内容。
<% 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
拖出去。
.carrier-data opacity 0 position fixed left -100px
|
carrier: function () { $(".j-carrier-btn").on("click", function () { $(".j-carrier-data").select(); document.execCommand("Copy"); alert('已经复制到剪切板'); }); }
|
消息弹窗
文章复制到剪切板后,需要弹出消息弹窗,提示用户。
主题开发之初我就给自己规定,不到万不得已,绝不用第三方库。(jQuery实在是没办法了)所以我们就自己封装一个消息弹窗组件。
第一版代码:
逻辑很清晰。触发弹窗后,先构造 DOM
并插入到 body
(根),添加 in
样式(渐入动画)。若干秒后移除 in
样式(渐出动画),并删除 DOM
。
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); }
|
Message({ text: '已复制到剪切板', type: 'success' });
|
$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
了。
第二版代码:
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)
太丑了,绑定事件代替之。
第三版代码:
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
代替,浏览器只会计算动画初始位置和结束位置,不会频繁触发回流。
.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
|
最后套个节流函数:
carrier: function () { $(".j-carrier-btn").on("click", utils.throttle(function () { $(".j-carrier-data").select(); document.execCommand("Copy"); zui.Message({ text: '已复制到剪切板', type: 'success' }); }, 3000)); }
|