效果预览

在本站PC端右键即可体验

方法

  • [blogroot]/themes/butterfly/layout/includes文件夹下新建rightmenu.pug文件,添加如下内容
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
.js-pjax
#rightMenu
.rightMenu-group.rightMenu-small
.rightMenu-item#menu-backward
i.fas.fa-arrow-left
.rightMenu-item#menu-forward
i.fas.fa-arrow-right
.rightMenu-item#menu-refresh
i.fas.fa-arrow-rotate-right
.rightMenu-item#menu-top
i.fas.fa-arrow-up
.rightMenu-group.rightMenuPost
if theme.readmode
.rightMenu-item#menu-reading
i.fas.fa-book
span= '沉浸阅读'
.rightMenu-item#menu-postlink
i.fas.fa-external-link
span= '分享本文'
.rightMenu-group.rightMenuPlugin
.rightMenu-item#menu-copytext
i.fas.fa-copy
span= '复制内容'
.rightMenu-item#menu-pastetext
i.fas.fa-clipboard
span= '粘贴内容'
.rightMenu-item#menu-searchBaidu
i.fas.fa-magnifying-glass
span= '百度搜索'
.rightMenu-item#menu-newwindow
i.far.fa-window-restore
span= '新标签页中打开'
.rightMenu-item#menu-copylink
i.fas.fa-link
span= '复制链接地址'
.rightMenu-item#menu-copylinkimg
i.fas.fa-link
span= '复制图片地址'
.rightMenu-item#menu-newwindowimg
i.fas.fa-window-restore
span= '新标签页中打开'
.rightMenu-group.rightMenuOther
if theme.darkmode.enable
.rightMenu-item#menu-darkmode
i.fas.fa-circle-half-stroke
span= '显示模式'
if theme.translate.enable
a.rightMenu-item#menu-translate
i.fas.fa-earth-asia
span= '繁简转换'
.rightMenu-item#menu-asidehide
i.fas.fa-arrows-left-right-to-line
span= '边栏控制'
#rightMenu-mask

  • [blogroot]/themes/butterfly/layout/includes/layout.pug的末尾部分新增include ./rightmenu.pug

更改前

1
2
include ./rightside.pug
include ./additional-js.pug

更改后

1
2
3
include ./rightside.pug
include ./rightmenu.pug
include ./additional-js.pug
  • [blogroot]/themes/butterfly/source/css/_layout文件夹下新建文件rightmenu.styl,添加如下内容
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
#rightMenu
position fixed
display none
padding 4px
border-radius 8px
z-index 91
font-size 14px
user-select none
transition border .3s, box-shadow .3s

&:hover
box-shadow 0 0 0 1px rgba(100,180,255,.25)

.rightMenu-group
padding 6px

&:not(:last-child)
margin-bottom 2px

&.rightMenu-small
display flex
align-items center
gap .5rem

&:not(.rightMenu-small)
.rightMenu-item
padding 0 6px
height 40px

&:not(:last-child)
margin-bottom 4px

*
height 40px
line-height 40px

.rightMenu-item
display flex
align-items center
border-radius 6px
cursor pointer
transition background-color .25s, color .25s

i
display inline-block
width 30px
height 30px
line-height 30px
text-align center
padding 0 5px

#rightMenu .icat-refresh,
#rightMenu .icat-changing-over,
#rightMenu .icat-simple-complex
font-weight 900


/* =======================
浅色模式
======================= */
html[data-theme="light"]
#rightMenu
background #ffffff
border 1px solid rgba(0,0,0,.08)
box-shadow 0 2px 6px rgba(0,0,0,.08),
0 8px 24px rgba(0,0,0,.06)

#rightMenu .rightMenu-item
color rgba(0,0,0,.85)

&:hover
background rgba(0,0,0,.05)
color rgba(0,0,0,.95)

#rightMenu .rightMenu-group:not(:last-child)
border-bottom 1px solid rgba(0,0,0,.06)


/* =======================
深色模式
======================= */
html[data-theme="dark"]
#rightMenu
background rgba(32,32,36,.75)
backdrop-filter blur(20px) saturate(1.4)
border 1px solid rgba(255,255,255,.18)
box-shadow 0 8px 28px rgba(0,0,0,.35)

#rightMenu .rightMenu-item
color rgba(255,255,255,.85)

&:hover
background rgba(100,180,255,.85)
color #fff

#rightMenu .rightMenu-group:not(:last-child)
border-bottom 1px dashed rgba(255,255,255,.25)


/* 遮罩 */
#rightMenu-mask
position fixed
inset 0
background transparent
display none
z-index 90

  • [blogroot]/source/js文件夹下新建文件rightmenu.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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
class RightMenu {
constructor() {
this.selectTextNow = ""
this.domhref = ""
this.domImgSrc = ""
this.globalEvent = null
this.rmWidth = 0
this.rmHeight = 0

this.initialize()
}

/* ========== 初始化 ========== */

initialize() {
window.addEventListener("load", () => {
this.addRightMenuClickEvent()
document.onmouseup = document.ondbclick = this.selectText.bind(this)

btf.addGlobalFn(
"pjaxComplete",
() => this.addRightMenuClickEvent(),
"addRightMenuClickEvent"
)
})

window.oncontextmenu = (e) => this.onContextMenu(e)
}

/* ========== 菜单点击事件 ========== */

addRightMenuClickEvent() {
const menuItems = {
"rightMenu-mask": [this.hideRightMenu.bind(this)],

// 浏览器
"menu-backward": [() => { history.back(); this.hideRightMenu() }],
"menu-forward": [() => { history.forward(); this.hideRightMenu() }],
"menu-refresh": [() => { location.reload() }],
"menu-top": [() => { btf.scrollToDest(0, 500); this.hideRightMenu() }],

// 文章
"menu-reading": [() => {
const isReadMode = document.body.classList.contains("read-mode")

if (!isReadMode) {
// 进入阅读模式
document.querySelector("#readmode")?.click()
} else {
// 退出阅读模式(点击主题自带的退出按钮)
document.querySelector(".exit-readmode")?.click()
}

this.hideRightMenu()
}],
"menu-postlink": [this.copyPostUrl.bind(this)],

// 文本
"menu-copytext": [() => {
this.rightmenuCopyText(this.selectTextNow)
GLOBAL_CONFIG.Snackbar && btf.snackbarShow("已复制选中文本")
}],
"menu-pastetext": [this.pasteText.bind(this)],
"menu-searchBaidu": [this.searchBaidu.bind(this)],

// 链接
"menu-newwindow": [() => {
this.domhref && window.open(this.domhref)
this.hideRightMenu()
}],
"menu-copylink": [() => {
this.rightmenuCopyText(this.domhref)
GLOBAL_CONFIG.Snackbar && btf.snackbarShow("已复制链接地址")
}],

// 图片
"menu-copylinkimg": [() => {
this.rightmenuCopyText(this.domImgSrc)
GLOBAL_CONFIG.Snackbar && btf.snackbarShow("已复制图片链接")
}],
"menu-newwindowimg": [() => {
this.domImgSrc && window.open(this.domImgSrc)
this.hideRightMenu()
}],

// 主题 / 页面
"menu-darkmode": [() => {
document.querySelector("#darkmode")?.click()
this.hideRightMenu()
}],
"menu-translate": [() => {
document.querySelector("#translateLink")?.click()
this.hideRightMenu()
}],
"menu-asidehide": [() => {
document.querySelector("#hide-aside-btn")?.click()
this.hideRightMenu()
}]
}

for (const id in menuItems) {
const el = document.getElementById(id)
if (el) menuItems[id].forEach(fn => el.addEventListener("click", fn))
}

document.getElementById("rightMenu-mask")
?.addEventListener("contextmenu", () => {
this.hideRightMenu()
return false
})
}

/* ========== 菜单显示控制 ========== */

reloadrmSize() {
const menu = document.getElementById("rightMenu")
this.rmWidth = menu.offsetWidth
this.rmHeight = menu.offsetHeight
}

showRightMenu(show, mouseY = 0, mouseX = 0) {
const menu = document.getElementById("rightMenu")
if (!show) {
menu.style.display = "none"
return
}

menu.style.display = "block"
this.reloadrmSize()

if (mouseX + this.rmWidth + 20 > window.innerWidth) {
mouseX -= this.rmWidth + 20
}
if (mouseY + this.rmHeight > window.innerHeight) {
mouseY -= mouseY + this.rmHeight - window.innerHeight + 20
}

menu.style.left = mouseX + "px"
menu.style.top = mouseY + "px"
document.getElementById("rightMenu-mask").style.display = "flex"
}

hideRightMenu() {
document.getElementById("rightMenu").style.display = "none"
document.getElementById("rightMenu-mask").style.display = "none"
}

/* ========== 文本处理 ========== */

selectText() {
this.selectTextNow = window.getSelection
? window.getSelection().toString()
: ""
}

rightmenuCopyText(text) {
if (!text) return
navigator.clipboard?.writeText(text)
this.hideRightMenu()
}

copyPostUrl() {
this.rightmenuCopyText(location.href)
GLOBAL_CONFIG.Snackbar && btf.snackbarShow("已复制本文链接")
}

searchBaidu() {
if (!this.selectTextNow) return
window.open("https://www.baidu.com/s?wd=" + this.selectTextNow)
this.hideRightMenu()
}

pasteText() {
navigator.clipboard?.readText().then(text => {
this.insertAtCaret(this.globalEvent.target, text)
GLOBAL_CONFIG.Snackbar && btf.snackbarShow("已粘贴剪贴板内容")
})
this.hideRightMenu()
}

insertAtCaret(input, text) {
if (!input || !("selectionStart" in input)) return
const start = input.selectionStart
const end = input.selectionEnd
input.value =
input.value.slice(0, start) +
text +
input.value.slice(end)
input.selectionStart = input.selectionEnd = start + text.length
}

/* ========== 右键核心逻辑 ========== */

onContextMenu(e) {
// 页面宽度小于768px, 显示浏览器默认右键菜单
if (document.body.clientWidth <= 768) return true

const target = e.target
const mouseX = e.clientX + 12
const mouseY = e.clientY

this.globalEvent = e

const hasText = !!this.selectTextNow
const hasLink = !!target.href
const hasImage = !!target.currentSrc
const isInput = ["input", "textarea"].includes(target.tagName.toLowerCase())

this.domhref = target.href || ""
this.domImgSrc = target.currentSrc || ""

const toggle = (id, show) => {
const el = document.getElementById(id)
if (el) el.style.display = show ? "block" : "none"
}

/* item 级 */
toggle("menu-copytext", hasText)
toggle("menu-searchBaidu", hasText)

toggle("menu-newwindow", hasLink)
toggle("menu-copylink", hasLink)

toggle("menu-copylinkimg", hasImage)
toggle("menu-newwindowimg", hasImage)

toggle("menu-pastetext", isInput)

/* group 级 */
const postGroup = document.querySelector(".rightMenuPost")
const pluginGroup = document.querySelector(".rightMenuPlugin")
const otherGroup = document.querySelector(".rightMenuOther")

const hasSpecialTarget = hasText || hasLink || hasImage
const isPostPage = !!document.querySelector("#body-wrap.post")

postGroup && (postGroup.style.display = !isPostPage || hasSpecialTarget ? "none" : "block")
pluginGroup && (pluginGroup.style.display = hasSpecialTarget ? "block" : "none")
otherGroup && (otherGroup.style.display = hasSpecialTarget ? "none" : "block")

/* 阅读模式文案 */
const readingItem = document.getElementById("menu-reading")
if (readingItem) {
const span = readingItem.querySelector("span")
if (span) {
span.textContent = document.body.classList.contains("read-mode")
? "退出沉浸"
: "沉浸阅读"
}
}

this.showRightMenu(true, mouseY, mouseX)
// 阻止浏览器默认右键菜单
e.preventDefault()
return false
}
}

const rightMenu = new RightMenu()

  • 引入rightMenu.js,在主题的配置文件_config.butterfly.yml的 inject 中添加相应的内容
1
2
3
4
5
6
7
8
# Inject
# 插入代码到 head(在 '</head>' 标签之前)和底部(在 '</body>' 标签之前)
inject:
head:
# - <link rel="stylesheet" href="/xxx.css">
bottom:
# - <script src="xxxx"></script>
- <script src="/js/rightmenu.js"></script>