更新记录

2023/01/30

修改博文排版

2022/08/09

修复在不支持clipboard的浏览器中复制内容时会弹出软键盘的问题

2022/08/06

手动编写复制文本的代码,减少代码体积

2022/02/14

发布初版

注意

  该博文是基于Butterfly主题修改的,不跟我一个主题的不要照搬我添加元素的方法。

  本人没有学过前端知识,所以有一些名词可能会用错或者有这个名词但是我不知道,还请各位谅解。

缘由

  想要做这个魔改的原因一方面是QQ的分享太过反人类,另一方面是我发现有些浏览器(比如我正在用的)在使用PWA浏览网页时打不开地址栏,这时候想要复制链接简直是难之又难。所以一直想要加一个复制链接的按钮,但是又不会加。正巧,前段时间看到了群友写的一篇教程,我就在他的基础上修改了一些内容,就有了这篇博文。

魔改步骤

添加按钮

  打开themes/[butterfly]/layout/includes/rightside.pug,修改如下:

1
2
3
4
5
6
7
8
9
10
11
  when 'chat'
if chat_btn
button#chat_btn(type="button" title=_p("rightside.chat_btn"))
i.fas.fa-sms
when 'comment'
if commentsJsLoad
a#to_comment(href="#post-comment" title=_p("rightside.scroll_to_comment"))
i.fas.fa-comments
+ when 'share'
+ button#share-link(type="button" title='分享链接')
+ i.fa.fa-share-alt
1
2
3
4
5
  #rightside
- const { enable, hide, show } = theme.rightside_item_order
- const hideArray = enable ? hide && hide.split(',') : ['readmode','translate','darkmode','hideAside']
- - const showArray = enable ? show && show.split(',') : ['toc','chat','comment']
+ - const showArray = enable ? show && show.split(',') : ['toc','chat','comment', 'share']

添加JS

  新建:[butterfly]\source\js\custom\share.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
document.addEventListener('DOMContentLoaded', () => {
/**
* 复制指定文本
* @param text {string} 要复制的文本
*/

const copyText = text => {
// `success`和`noSupport`分别是复制成功和失败时的操作
// 如果你没有启用bf的弹窗,请自行修改代码

// noinspection JSUnresolvedVariable
const success = () => btf.snackbarShow(GLOBAL_CONFIG.copy.success)
// noinspection JSUnresolvedVariable
const noSupport = () => btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport)

const commandCopy = () => {
// noinspection JSDeprecatedSymbols
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
const input = document.createElement('p')
if (navigator.userAgent.includes('Firefox')) input.textContent = text
else input.innerText = text
document.body.appendChild(input)
const selection = getSelection()
selection.removeAllRanges()
const range = document.createRange()
range.selectNodeContents(input)
selection.addRange(range)
// noinspection JSDeprecatedSymbols
document.execCommand('copy')
document.body.removeChild(input)
selection.removeAllRanges()
success()
} else noSupport()
}

const clipboard = navigator.clipboard
if (clipboard) clipboard.writeText(text).then(success).catch(commandCopy)
else commandCopy()
}
/** 获取要复制的文本,想要修改复制文本的格式的话在这里修改 */
const getText = () => {
// 获取当前页面的URL(不包含参数和hash)
const url = location.protocol + '//' + location.host + location.pathname
return `${document.title}:\r\n${url}`
}
document.getElementById('rightside').addEventListener('click', event => {
const element = event.target.id ? event.target : event.target.parentNode
if (element?.id === 'share-link')
copyText(getText())
})
})

  同时我们可以发现 BF 其实也内置了一个复制功能,不过其被隐藏在了main.js的内部,外部无法访问,读者可以把我写的copyText提取到外部,然后将main.js中的代码改为调用copyText,然后将main.js中包含的复制函数删掉,可以节省一点空间。

  然后在配置文件中引入 JS:

1
2
3
  inject:
bottom:
+ - <script defer src="/js/custom/share.js"></script>

原理介绍

  向剪切板添加数据常见的方案有两种,一种是使用浏览器clipboard接口,另一种是使用copy命令。两种方案各有优劣,前者是异步接口,后者是同步接口。通常建议优先使用前者(后者已经被标记为废弃但是能用),但是由于部分浏览器不兼容,所以也要写上后者以防不备之需。

  clipboard接口的用法很简单,看一段示例程序:

1
2
3
navigator.clipboard.writeText('测试数据')
.then(() => console.log('复制成功'))
.catch(() => console.error('复制失败'))

  clipboard中包含以下几个函数:

  • write - 写入任意数据到剪切板,返回一个Promise
  • writeText - 写入一个字符串到剪切板,返回一个Promise
  • read - 从剪切板读取任意数据,返回一个Promise
  • readText - 从剪切板读取一个字符串,返回一个Promise

  更多内容见:MDN 文档

  copy命令的用法同样很简单,大体思路就是通过代码模拟用户选取文本,然后执行复制命令即可。

  不过需要注意的是,要复制的内容一定保存在已经存在于DOM中且display不为none的元素中,不然是无法选中的。同样,个人建议使用p标签存储文本,因为使用document.createTextNode的话是无法把换行符正确的添加到剪切板中的,使用input标签会导致移动端弹出输入法(虽然只是闪一下,但是很烦人)

  大体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (document.queryCommandSupported && document.queryCommandSupported('copy')) {
const text = '测试数据'
// 构建p标签
const input = document.createElement('p')
// 火狐浏览器兼容
if (navigator.userAgent.includes('Firefox')) input.textContent = text
else input.innerText = text
document.body.appendChild(input)

// 复制
const selection = getSelection()
selection.removeAllRanges() // 清除已选文本
const range = document.createRange()
range.selectNodeContents(input) // 选中创建的标签
selection.addRange(range)
// noinspection JSDeprecatedSymbols
document.execCommand('copy') //复制

// 还原
document.body.removeChild(input)
selection.removeAllRanges()
console.log('复制成功')
} else console.error('复制失败')

  然后把代码结合起来就好了。