hexo-theme-volantis
Version:
Elegant and powerful theme for Hexo.
623 lines (578 loc) • 22.3 kB
JavaScript
const RightMenus = {
defaultEvent: ['copyText', 'copyLink', 'copyPaste', 'copyAll', 'copyCut', 'copyImg', 'printMode', 'readMode'],
defaultGroup: ['navigation', 'inputBox', 'seletctText', 'elementCheck', 'elementImage', 'articlePage'],
messageRightMenu: volantis.GLOBAL_CONFIG.plugins.message.enable && volantis.GLOBAL_CONFIG.plugins.message.rightmenu.enable,
corsAnywhere: volantis.GLOBAL_CONFIG.plugins.rightmenus.options.corsAnywhere,
urlRegx: /^((https|http)?:\/\/)+[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"\"])*$/,
imgRegx: /\.(jpe?g|png|webp|svg|gif|jifi)(-|_|!|\?|\/)?.*$/,
/**
* 加载右键菜单
*/
initialMenu: () => {
RightMenus.fun.init();
volantis.pjax.send(() => {
RightMenus.fun.hideMenu();
if (volantis.isReadModel) RightMenus.fun.readMode();
})
},
/**
* 读取剪切板
* @returns text
*/
readClipboard: async () => {
let clipboardText;
const result = await navigator.permissions.query({ name: 'clipboard-read' });
switch (result.state) {
case 'granted':
case 'prompt':
clipboardText = await navigator.clipboard.readText()
break;
default:
window.clipboardRead = false;
break;
}
return clipboardText;
},
/**
* 写入文本到剪切板
* @param {String} text
*/
writeClipText: text => {
return navigator.clipboard
.writeText(text)
.then(() => {
return Promise.resolve()
})
.catch(err => {
return Promise.reject(err)
})
},
/**
* 写入图片到剪切板
* @param {*} link
* @param {*} success
* @param {*} error
*/
writeClipImg: async (link, success, error) => {
const image = new Image;
image.crossOrigin = "Anonymous";
image.addEventListener('load', () => {
let canvas = document.createElement("canvas");
let context = canvas.getContext("2d");
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
canvas.toBlob(blob => {
navigator.clipboard.write([
new ClipboardItem({ 'image/png': blob })
]).then(e => {
success(e)
}).catch(e => {
error(e)
})
}, 'image/png')
}, false)
image.src = `${link}?(lll¬ω¬)`;
},
/**
* 粘贴文本到剪切板
* @param {*} elemt
* @param {*} value
*/
insertAtCaret: (elemt, value) => {
const startPos = elemt.selectionStart,
endPos = elemt.selectionEnd;
if (document.selection) {
elemt.focus();
var sel = document.selection.createRange();
sel.text = value;
elemt.focus();
} else {
if (startPos || startPos == '0') {
var scrollTop = elemt.scrollTop;
elemt.value = elemt.value.substring(0, startPos) + value + elemt.value.substring(endPos, elemt.value.length);
elemt.focus();
elemt.selectionStart = startPos + value.length;
elemt.selectionEnd = startPos + value.length;
elemt.scrollTop = scrollTop;
} else {
elemt.value += value;
elemt.focus();
}
}
}
}
/**
* 事件处理区域
*/
RightMenus.fun = (() => {
const rightMenuConfig = volantis.GLOBAL_CONFIG.plugins.rightmenus;
const
fn = {},
_rightMenuWrapper = document.getElementById('rightmenu-wrapper'),
_rightMenuContent = document.getElementById('rightmenu-content'),
_rightMenuList = document.querySelectorAll('#rightmenu-content li.menuLoad-Content'),
_rightMenuListWithHr = document.querySelectorAll('#rightmenu-content li, #rightmenu-content hr, #menuMusic'),
_readBkg = document.getElementById('read_bkg'),
_menuMusic = document.getElementById('menuMusic'),
_backward = document.querySelector('#menuMusic .backward'),
_toggle = document.querySelector('#menuMusic .toggle'),
_forward = document.querySelector('#menuMusic .forward');
// 公共数据
let globalData = {
mouseEvent: null,
isInputBox: false,
selectText: '',
inputValue: '',
isLink: false,
linkUrl: '',
isMediaLink: false,
mediaLinkUrl: '',
isImage: false,
isArticle: false,
pathName: '',
isReadClipboard: true,
isShowMusic: false,
statusCheck: false
}
const globalDataBackup = Object.assign({}, globalData);
/**
* 初始化监听事件处理
*/
fn.initEvent = () => {
fn.elementAppend();
fn.contextmenu();
fn.menuEvent();
}
/**
* 预置元素设定
*/
fn.elementAppend = () => {
// 阅读模式
if (_readBkg) _readBkg.parentNode.removeChild(_readBkg);
const readBkg = document.createElement("div");
readBkg.className = "common_read_bkg common_read_hide";
readBkg.id = "read_bkg";
window.document.body.appendChild(readBkg);
}
/**
* 右键菜单位置设定
* @param {*} event
*/
fn.menuPosition = (event) => {
try {
let mouseClientX = event.clientX;
let mouseClientY = event.clientY;
let screenWidth = document.documentElement.clientWidth || document.body.clientWidth;
let screenHeight = document.documentElement.clientHeight || document.body.clientHeight;
_rightMenuWrapper.style.display = 'block';
fn.menuControl(event);
let menuWidth = _rightMenuContent.offsetWidth;
let menuHeight = _rightMenuContent.offsetHeight;
let showLeft = mouseClientX + menuWidth > screenWidth ? mouseClientX - menuWidth + 10 : mouseClientX;
let showTop = mouseClientY + menuHeight > screenHeight ? mouseClientY - menuHeight + 10 : mouseClientY;
showTop = mouseClientY + menuHeight > screenHeight && showTop < menuHeight && mouseClientY < menuHeight ?
showTop + (screenHeight - menuHeight - showTop - 10) : showTop;
_rightMenuWrapper.style.left = `${showLeft}px`;
_rightMenuWrapper.style.top = `${showTop}px`;
if (volantis.GLOBAL_CONFIG.plugins.message.rightmenu.notice) fn.menuNotic();
} catch (error) {
console.error(error);
fn.hideMenu();
return true;
}
return false;
}
/**
* 菜单项控制
* @param {*} event
*/
fn.menuControl = (event) => {
fn.globalDataSet(event);
if (!!_menuMusic) _menuMusic.style.display = globalData.isShowMusic ? 'block' : 'none';
_rightMenuList.forEach(item => {
item.style.display = 'none';
const nodeName = item.firstElementChild.nodeName;
const groupName = item.firstElementChild.getAttribute('data-group');
const itemEvent = item.firstElementChild.getAttribute('data-event');
if (globalData.statusCheck || globalData.isArticle) {
switch (groupName) {
case 'inputBox':
if (globalData.isInputBox) {
item.style.display = 'block';
if (itemEvent === 'copyCut' && !globalData.selectText) item.style.display = 'none';
if (itemEvent === 'copyAll' && !globalData.inputValue) item.style.display = 'none';
if (itemEvent === 'copyPaste' && !globalData.isReadClipboard) item.style.display = 'none';
}
break;
case 'seletctText':
if (!!globalData.selectText) item.style.display = 'block';
break;
case 'elementCheck':
if (globalData.isLink || globalData.isMediaLink) item.style.display = 'block';
break;
case 'elementImage':
if (globalData.isImage) item.style.display = 'block';
break;
case 'articlePage':
if (globalData.isArticle) item.style.display = 'block';
break;
default:
item.style.display = nodeName === 'A'
? globalData.isArticle && !globalData.statusCheck && rightMenuConfig.options.articleShowLink
? 'block'
: 'none'
: 'block';
break;
}
} else if (nodeName === 'A' || RightMenus.defaultGroup.every(item => { return groupName !== item })) {
item.style.display = 'block';
}
})
// 执行外部事件
volantis.mouseEvent = event;
volantis.rightmenu.method.handle.start()
// 过滤 HR 元素
let elementHrItem = { item: null, hide: true };
_rightMenuListWithHr.forEach((item) => {
if (item.nodeName === "HR") {
item.style.display = 'block';
if (!elementHrItem.item) {
elementHrItem.item = item;
return;
}
if (elementHrItem.hide || elementHrItem.item.nextElementSibling.nodeName === "hr") {
elementHrItem.item.style.display = 'none';
}
elementHrItem.item = item;
elementHrItem.hide = true;
} else {
if (item.style.display === 'block' && elementHrItem.hide) {
elementHrItem.hide = false;
}
}
})
if (!!elementHrItem.item && elementHrItem.hide) elementHrItem.item.style.display = 'none';
}
/**
* 元素状态判断/全局数据设置
* @param {*} event
*/
fn.globalDataSet = (event) => {
globalData = Object.assign({}, globalDataBackup);
globalData.mouseEvent = event;
globalData.selectText = window.getSelection().toString();
// 判断是否为输入框
if (event.target.tagName.toLowerCase() === 'input' || event.target.tagName.toLowerCase() === 'textarea') {
globalData.isInputBox = true;
globalData.inputValue = event.target.value;
}
// 判断是否允许读取剪切板
if (globalData.isInputBox && window.clipboardRead === false) {
globalData.isReadClipboard = false;
}
// 判断是否包含链接
if (!!event.target.href && RightMenus.urlRegx.test(event.target.href)) {
globalData.isLink = true;
globalData.linkUrl = event.target.href;
}
// 判断是否包含媒体链接
if (!!event.target.currentSrc && RightMenus.urlRegx.test(event.target.currentSrc)) {
globalData.isMediaLink = true;
globalData.mediaLinkUrl = event.target.currentSrc;
}
// 判断是否为图片地址
if (globalData.isMediaLink && RightMenus.imgRegx.test(globalData.mediaLinkUrl)) {
globalData.isImage = true;
}
// 判断是否为文章页面
if (!!(document.querySelector('#post.article') || null)) {
globalData.isArticle = true;
globalData.pathName = window.location.pathname;
}
// 判断是否显示音乐控制器
if (volantis.GLOBAL_CONFIG.plugins.aplayer?.enable
&& typeof RightMenuAplayer !== 'undefined'
&& RightMenuAplayer.APlayer.player !== undefined) {
if (rightMenuConfig.options.musicAlwaysShow
|| RightMenuAplayer.APlayer.status === 'play'
|| RightMenuAplayer.APlayer.status === 'undefined') {
globalData.isShowMusic = true;
}
}
// 设定校验状态
if (!!globalData.selectText || globalData.isInputBox || globalData.isLink || globalData.isMediaLink) {
globalData.statusCheck = true;
}
}
/**
* 全局右键监听函数
*/
fn.contextmenu = () => {
window.document.oncontextmenu = (event) => {
if (event.ctrlKey || document.body.offsetWidth <= 500) {
fn.hideMenu();
return true;
}
return fn.menuPosition(event);
}
_rightMenuWrapper.oncontextmenu = (event) => {
event.stopPropagation();
event.preventDefault();
return false;
}
window.removeEventListener('blur', fn.hideMenu);
window.addEventListener('blur', fn.hideMenu);
document.body.removeEventListener('click', fn.hideMenu);
document.body.addEventListener('click', fn.hideMenu);
}
/**
* 菜单项事件处理函数
*/
fn.menuEvent = () => {
_rightMenuList.forEach(item => {
let eventName = item.firstElementChild.getAttribute('data-event');
const id = item.firstElementChild.getAttribute('id');
const groupName = item.firstElementChild.getAttribute('data-group');
if (item.firstElementChild.nodeName === "A") return;
item.addEventListener('click', () => {
try {
if (RightMenus.defaultEvent.every(item => { return eventName !== item })) {
if (groupName === 'seletctText') {
RightMenusFunction[id](globalData.selectText)
} else if (groupName === 'elementCheck') {
RightMenusFunction[id](globalData.isLink ? globalData.linkUrl : globalData.mediaLinkUrl)
} else if (groupName === 'elementImage') {
RightMenusFunction[id](globalData.mediaLinkUrl)
} else {
RightMenusFunction[id]()
}
} else {
fn[eventName]()
}
} catch (error) {
if (volantis.GLOBAL_CONFIG.debug === "rightMenus") {
console.error({
id: id,
error: error,
globalData: globalData,
groupName: groupName,
eventName: eventName
});
}
if (RightMenus.messageRightMenu) {
VolantisApp.message('错误提示', error, {
icon: rightMenuConfig.options.iconPrefix + ' fa-exclamation-square red',
time: '15000'
});
}
}
})
})
if (_forward && _toggle && _forward) {
_backward.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
RightMenuAplayer.aplayerBackward();
}
_toggle.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
RightMenuAplayer.aplayerToggle();
}
_forward.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
RightMenuAplayer.aplayerForward();
}
}
}
/**
* 隐藏菜单显示
*/
fn.hideMenu = () => {
_rightMenuWrapper.style.display = null;
_rightMenuWrapper.style.left = null;
_rightMenuWrapper.style.top = null;
}
/**
* 右键菜单覆盖提示
*/
fn.menuNotic = () => {
const NoticeRightMenu = localStorage.getItem('NoticeRightMenu') === 'true';
if (RightMenus.messageRightMenu && !NoticeRightMenu)
VolantisApp.message('右键菜单', '唤醒原系统菜单请使用:<kbd>Ctrl</kbd> + <kbd>右键</kbd>', {
icon: rightMenuConfig.options.iconPrefix + ' fa-exclamation-square red',
displayMode: 1,
time: 9000
}, () => {
localStorage.setItem('NoticeRightMenu', 'true')
});
}
fn.copyText = () => {
VolantisApp.utilWriteClipText(globalData.selectText)
.then(() => {
if (RightMenus.messageRightMenu) {
VolantisApp.messageCopyright();
}
}).catch(e => {
if (RightMenus.messageRightMenu) {
VolantisApp.message('系统提示', e, {
icon: rightMenuConfig.options.iconPrefix + ' fa-exclamation-square red',
displayMode: 1,
time: 9000
});
}
})
}
fn.copyLink = () => {
VolantisApp.utilWriteClipText(globalData.linkUrl || globalData.mediaLinkUrl)
.then(() => {
if (RightMenus.messageRightMenu) {
VolantisApp.messageCopyright();
}
}).catch(e => {
if (RightMenus.messageRightMenu) {
VolantisApp.message('系统提示', e, {
icon: rightMenuConfig.options.iconPrefix + ' fa-exclamation-square red',
displayMode: 1,
time: 9000
});
}
})
}
fn.copyAll = () => {
globalData.mouseEvent.target.select();
}
fn.copyPaste = async () => {
const result = await RightMenus.readClipboard() || '';
if (RightMenus.messageRightMenu && window.clipboardRead === false) {
VolantisApp.message('系统提示', '未授予剪切板读取权限!');
} else if (RightMenus.messageRightMenu && result === '') {
VolantisApp.message('系统提示', '仅支持复制文本内容!');
} else {
RightMenus.insertAtCaret(globalData.mouseEvent.target, result);
}
}
fn.copyCut = () => {
const statrPos = globalData.mouseEvent.target.selectionStart;
const endPos = globalData.mouseEvent.target.selectionEnd;
const inputStr = globalData.inputValue;
fn.copyText(globalData.selectText);
globalData.mouseEvent.target.value = inputStr.substring(0, statrPos) + inputStr.substring(endPos, inputStr.length);
globalData.mouseEvent.target.selectionStart = statrPos;
globalData.mouseEvent.target.selectionEnd = statrPos;
globalData.mouseEvent.target.focus();
}
fn.copyImg = () => {
if (volantis.GLOBAL_CONFIG.plugins.message.rightmenu.notice) {
VolantisApp.message('系统提示', '复制中,请等待。', {
icon: rightMenuConfig.options.iconPrefix + ' fa-images'
})
}
RightMenus.writeClipImg(globalData.mediaLinkUrl, e => {
if (RightMenus.messageRightMenu) {
VolantisApp.hideMessage();
VolantisApp.message('系统提示', '图片复制成功!', {
icon: rightMenuConfig.options.iconPrefix + ' fa-images'
});
}
}, (e) => {
console.error(e);
if (RightMenus.messageRightMenu) {
VolantisApp.hideMessage();
VolantisApp.message('系统提示', '复制失败:' + e, {
icon: rightMenuConfig.options.iconPrefix + ' fa-exclamation-square red',
time: 9000
});
}
})
}
fn.printMode = () => {
if (window.location.pathname === globalData.pathName) {
if (RightMenus.messageRightMenu) {
const message = '是否打印当前页面?<br><em style="font-size: 80%">建议打印时勾选背景图形</em><br>'
VolantisApp.question('', message, { time: 9000 }, () => { fn.printHtml() })
} else {
fn.printHtml()
}
}
}
fn.printHtml = () => {
if (volantis.isReadModel) fn.readMode();
DOMController.setAttribute('details', 'open', 'true');
DOMController.removeList([
'.cus-article-bkg', '.iziToast-overlay', '.iziToast-wrapper', '.prev-next',
'footer', '#l_header', '#l_cover', '#l_side', '#comments', '#s-top', '#BKG',
'#rightmenu-wrapper', '.nav-tabs', '.parallax-mirror', '.new-meta-item.share',
'.new-meta-box', 'button.btn-copy', 'iframe'
]);
DOMController.setStyleList([
['body', 'backgroundColor', 'unset'], ['#l_main, .copyright.license', 'width', '100%'],
['#post', 'boxShadow', 'none'], ['#post', 'background', 'none'], ['#post', 'padding', '0'],
['h1', 'textAlign', 'center'], ['h1', 'fontWeight', '600'], ['h1', 'fontSize', '2rem'], ['h1', 'marginBottom', '20px'],
['.tab-pane', 'display', 'block'], ['.tab-content', 'borderTop', 'none'], ['.highlight>table pre', 'whiteSpace', 'pre-wrap'],
['.highlight>table pre', 'wordBreak', 'break-all'], ['.fancybox img', 'height', 'auto'], ['.fancybox img', 'weight', 'auto'],
['.copyright.license', 'margin', '0'], ['.copyright.license', 'padding', '1.25em 20px'],
['figure.highlight, .copyright.license', 'display', 'inline-block'],
]);
setTimeout(() => {
window.print();
document.body.innerHTML = '';
window.location.reload();
}, 50);
}
fn.readMode = () => {
if (typeof ScrollReveal === 'function') ScrollReveal().clean('#comments');
DOMController.setStyle('#l_header', 'opacity', 0);
DOMController.fadeToggleList([
document.querySelector('#l_cover'), document.querySelector('footer'),
document.querySelector('#s-top'), document.querySelector('.article-meta#bottom'),
document.querySelector('.prev-next'), document.querySelector('#l_side'),
document.querySelector('#comments'),
]);
DOMController.toggleClassList([
[document.querySelector('#l_main'), 'common_read'], [document.querySelector('#l_main'), 'common_read_main'],
[document.querySelector('#l_body'), 'common_read'], [document.querySelector('#safearea'), 'common_read'],
[document.querySelector('#pjax-container'), 'common_read'], [document.querySelector('#read_bkg'), 'common_read_hide'],
[document.querySelector('h1'), 'common_read_h1'], [document.querySelector('#post'), 'post_read'],
[document.querySelector('#l_cover'), 'read_cover'], [document.querySelector('.widget.toc-wrapper'), 'post_read']
]);
DOMController.setStyle('.copyright.license', 'margin', '15px 0');
volantis.isReadModel = volantis.isReadModel === undefined ? true : !volantis.isReadModel;
if (volantis.isReadModel) {
if (RightMenus.messageRightMenu) VolantisApp.message('系统提示', '阅读模式已开启,您可以点击屏幕空白处退出。', {
backgroundColor: 'var(--color-read-post)',
icon: rightMenuConfig.options.iconPrefix + ' fa-book-reader',
displayMode: 1,
time: 5000
});
document.querySelector('#l_body').removeEventListener('click', fn.readMode);
document.querySelector('#l_body').addEventListener('click', (event) => {
if (DOMController.hasClass(event.target, 'common_read')) {
fn.readMode();
}
});
} else {
document.querySelector('#l_body').removeEventListener('click', fn.readMode);
document.querySelector('#post').removeEventListener('click', fn.readMode);
DOMController.setStyle('.prev-next', 'display', 'flex');
DOMController.setStyle('.copyright.license', 'margin', '15px -40px');
}
}
return {
init: fn.initEvent,
hideMenu: fn.hideMenu,
readMode: fn.readMode
}
})()
Object.freeze(RightMenus);
volantis.requestAnimationFrame(() => {
if (document.readyState !== 'loading') {
RightMenus.initialMenu();
} else {
document.addEventListener("DOMContentLoaded", function () {
RightMenus.initialMenu();
})
}
});