有觉性 以安住且中立的心 照见身心的实相
MediaWiki:QuoteCopy.js
来自Dhamma.cn
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/* =========================================================
Dhamma.cn
QuoteCopy.js
功能:
选中文字
↓
松开鼠标
↓
出现引用工具
↓
复制原文 / 复制出处 / 复制学术脚注
MediaWiki 1.43
使用现有 UrlShortener
========================================================= */
(function () {
'use strict';
/* =====================================================
基本设置
===================================================== */
var MIN_LENGTH = 8;
var MAX_LENGTH = 8000;
var popup = null;
var selectedText = '';
/*
* 当前页面短网址。
*
* 同一个页面只请求一次。
*/
var pageShortUrl = null;
var shortUrlPromise = null;
/* =====================================================
1. 判断是否启用
===================================================== */
function shouldEnable() {
/*
* 只在 Main namespace
*/
if (
mw.config.get('wgNamespaceNumber') !== 0
) {
return false;
}
/*
* 只在普通阅读页面
*/
if (
mw.config.get('wgAction') !== 'view'
) {
return false;
}
/*
* 如果明确存在引用元数据,
* 启用。
*/
if (
document.getElementById(
'dhamma-quote-metadata'
)
) {
return true;
}
/*
* 如果属于:
*
* 分类:隆波帕默尊者
*
* 自动启用。
*/
var categories =
mw.config.get('wgCategories') || [];
return categories.indexOf(
'隆波帕默尊者'
) !== -1;
}
/* =====================================================
2. 获取引用信息
===================================================== */
function getMetadata() {
var metadata =
document.getElementById(
'dhamma-quote-metadata'
);
/*
* 默认设置
*/
var result = {
author: '',
source: 'Dhamma.cn',
title: '',
date: ''
};
/*
* 如果页面有 metadata
*/
if (metadata) {
result.author =
metadata.getAttribute(
'data-author'
) || '';
result.source =
metadata.getAttribute(
'data-source'
) || 'Dhamma.cn';
result.title =
metadata.getAttribute(
'data-title'
) || '';
result.date =
metadata.getAttribute(
'data-date'
) || '';
return result;
}
/*
* 没有 metadata 时,
* 如果是隆波分类,
* 自动判断作者。
*/
var categories =
mw.config.get('wgCategories') || [];
if (
categories.indexOf(
'隆波帕默尊者'
) !== -1
) {
result.author =
'隆波帕默尊者';
}
return result;
}
/* =====================================================
3. 获取页面标题
===================================================== */
function getPageTitle() {
var metadata =
getMetadata();
/*
* metadata 中指定标题
* 优先使用。
*/
if (metadata.title) {
return metadata.title;
}
/*
* MediaWiki 页面标题
*/
var title =
mw.config.get('wgTitle');
if (title) {
return title;
}
/*
* 后备方案
*/
var heading =
document.querySelector(
'#firstHeading, .mw-page-title-main'
);
if (heading) {
return heading.textContent.trim();
}
/*
* 最终后备
*/
return document.title
.replace(/\s*-\s*.*$/, '')
.trim();
}
/* =====================================================
4. 标题格式处理
===================================================== */
function formatTitle(title) {
if (!title) {
return '';
}
/*
* 如果本身已经有《》
* 不重复添加。
*/
if (
title.charAt(0) === '《' &&
title.charAt(
title.length - 1
) === '》'
) {
return title;
}
return '《' + title + '》';
}
/* =====================================================
5. 创建引用 Popup
===================================================== */
function createPopup() {
if (popup) {
return popup;
}
popup =
document.createElement('div');
popup.id =
'dhamma-quote-popup';
popup.setAttribute(
'role',
'dialog'
);
popup.setAttribute(
'aria-label',
'Dhamma.cn 引用工具'
);
popup.innerHTML =
'<div class="dhamma-quote-popup-inner">' +
'<div class="dhamma-quote-popup-label">' +
'引用此段开示' +
'</div>' +
'<div class="dhamma-quote-popup-buttons">' +
'<button type="button" ' +
'class="dhamma-quote-button" ' +
'data-action="copy">' +
'复制原文' +
'</button>' +
'<button type="button" ' +
'class="dhamma-quote-button dhamma-quote-primary" ' +
'data-action="source">' +
'复制并附出处' +
'</button>' +
'<button type="button" ' +
'class="dhamma-quote-button" ' +
'data-action="footnote">' +
'复制学术脚注' +
'</button>' +
'</div>' +
'</div>';
document.body.appendChild(
popup
);
/*
* 不让点击 Popup 时
* 改变当前选区。
*/
popup.addEventListener(
'mousedown',
function (event) {
event.preventDefault();
}
);
/*
* 按钮事件
*/
popup.addEventListener(
'click',
function (event) {
var button =
event.target.closest(
'.dhamma-quote-button'
);
if (!button) {
return;
}
var action =
button.getAttribute(
'data-action'
);
if (
action === 'copy'
) {
copyPlainText();
}
if (
action === 'source'
) {
copyWithSource();
}
if (
action === 'footnote'
) {
copyFootnote();
}
}
);
return popup;
}
/* =====================================================
6. 获取选中文字
===================================================== */
function getSelectionData() {
var selection =
window.getSelection();
if (
!selection ||
selection.rangeCount === 0
) {
return null;
}
var text =
selection.toString().trim();
if (!text) {
return null;
}
if (
text.length < MIN_LENGTH
) {
return null;
}
if (
text.length > MAX_LENGTH
) {
return null;
}
var range =
selection.getRangeAt(0);
var container =
range.commonAncestorContainer;
if (
container.nodeType ===
Node.TEXT_NODE
) {
container =
container.parentElement;
}
if (!container) {
return null;
}
/*
* 必须来自正文。
*/
var content =
container.closest(
'#mw-content-text .mw-parser-output'
);
if (!content) {
return null;
}
return {
text: normalizeText(text),
range: range.cloneRange()
};
}
/* =====================================================
7. 清理选中文字
===================================================== */
function normalizeText(text) {
return text
.replace(
/\u00a0/g,
' '
)
.replace(
/\r\n/g,
'\n'
)
.replace(
/[ \t]+\n/g,
'\n'
)
.replace(
/\n[ \t]+/g,
'\n'
)
.replace(
/\n{3,}/g,
'\n\n'
)
.trim();
}
/* =====================================================
8. 获取 Canonical URL
===================================================== */
function getCanonicalUrl() {
var url =
mw.config.get(
'wgCanonicalUrl'
);
if (url) {
return url;
}
/*
* 后备
*/
var pageName =
mw.config.get(
'wgPageName'
);
if (
pageName &&
mw.util &&
mw.util.getUrl
) {
return mw.util.getUrl(
pageName
);
}
return window.location.href;
}
/* =====================================================
9. 获取短网址
===================================================== */
function getShortUrl() {
/*
* 当前页面已经取得。
*/
if (pageShortUrl) {
return Promise.resolve(
pageShortUrl
);
}
/*
* 已经正在请求。
*/
if (shortUrlPromise) {
return shortUrlPromise;
}
/*
* 调用 MediaWiki UrlShortener
*/
shortUrlPromise =
new mw.Api().post({
action: 'shortenurl',
url:
getCanonicalUrl()
}).then(
function (data) {
if (
data &&
data.shortenurl &&
data.shortenurl.shorturl
) {
pageShortUrl =
data.shortenurl.shorturl;
return pageShortUrl;
}
throw new Error(
'UrlShortener 未返回短网址'
);
}
).catch(
function (error) {
shortUrlPromise =
null;
throw error;
}
);
return shortUrlPromise;
}
/* =====================================================
10. 获取日期
===================================================== */
function getAccessDate() {
var now =
new Date();
var year =
now.getFullYear();
var month =
String(
now.getMonth() + 1
).padStart(
2,
'0'
);
var day =
String(
now.getDate()
).padStart(
2,
'0'
);
return (
year +
'-' +
month +
'-' +
day
);
}
/* =====================================================
11. Clipboard
===================================================== */
function copyText(
text,
successMessage
) {
/*
* 现代浏览器
*/
if (
navigator.clipboard &&
window.isSecureContext
) {
navigator.clipboard
.writeText(text)
.then(
function () {
showMessage(
successMessage
);
}
)
.catch(
function () {
fallbackCopy(
text,
successMessage
);
}
);
return;
}
/*
* 后备
*/
fallbackCopy(
text,
successMessage
);
}
/* =====================================================
12. 备用复制
===================================================== */
function fallbackCopy(
text,
successMessage
) {
var textarea =
document.createElement(
'textarea'
);
textarea.value =
text;
textarea.setAttribute(
'readonly',
''
);
textarea.style.position =
'fixed';
textarea.style.left =
'-9999px';
textarea.style.top =
'0';
document.body.appendChild(
textarea
);
textarea.focus();
textarea.select();
var success = false;
try {
success =
document.execCommand(
'copy'
);
} catch (error) {
success = false;
}
document.body.removeChild(
textarea
);
if (success) {
showMessage(
successMessage
);
} else {
showMessage(
'复制失败,请使用 Ctrl+C。'
);
}
}
/* =====================================================
13. 复制原文
===================================================== */
function copyPlainText() {
if (!selectedText) {
return;
}
copyText(
selectedText,
'已复制选中的法谈内容'
);
hidePopup();
}
/* =====================================================
14. 复制并附出处
===================================================== */
function copyWithSource() {
if (!selectedText) {
return;
}
showMessage(
'正在生成 Dhamma.cn 短网址……'
);
getShortUrl()
.then(
function (shortUrl) {
var metadata =
getMetadata();
var title =
formatTitle(
getPageTitle()
);
var output =
selectedText +
'\n\n' +
'----------------------------------------' +
'\n' +
'法谈摘自:' +
metadata.source +
' ' +
title +
'\n' +
'该篇网址:' +
shortUrl +
'\n' +
'转载请保持原文,随喜分享,Sadhu!';
copyText(
output,
'已复制法谈内容及出处'
);
hidePopup();
}
)
.catch(
function (error) {
console.error(
'Dhamma.cn UrlShortener:',
error
);
/*
* 短网址失败时,
* 不复制长 URL。
*/
var metadata =
getMetadata();
var title =
formatTitle(
getPageTitle()
);
var output =
selectedText +
'\n\n' +
'----------------------------------------' +
'\n' +
'法谈摘自:' +
metadata.source +
' ' +
title +
'\n' +
'转载请保持原文,随喜分享,Sadhu!';
copyText(
output,
'短网址暂时无法生成,已复制出处'
);
hidePopup();
}
);
}
/* =====================================================
15. 学术脚注
===================================================== */
function copyFootnote() {
if (!selectedText) {
return;
}
showMessage(
'正在生成 Dhamma.cn 短网址……'
);
getShortUrl()
.then(
function (shortUrl) {
var metadata =
getMetadata();
var author =
metadata.author ||
'隆波帕默尊者';
var source =
metadata.source ||
'Dhamma.cn';
var title =
getPageTitle();
var output =
author +
':《' +
title +
'》,' +
source +
',' +
shortUrl +
'(访问日期:' +
getAccessDate() +
')。';
copyText(
output,
'已复制学术脚注'
);
hidePopup();
}
)
.catch(
function (error) {
console.error(
'Dhamma.cn UrlShortener:',
error
);
var metadata =
getMetadata();
var author =
metadata.author ||
'隆波帕默尊者';
var source =
metadata.source ||
'Dhamma.cn';
var title =
getPageTitle();
var output =
author +
':《' +
title +
'》,' +
source +
'(访问日期:' +
getAccessDate() +
')。';
copyText(
output,
'短网址生成失败,已复制脚注'
);
hidePopup();
}
);
}
/* =====================================================
16. Popup 定位
===================================================== */
function positionPopup(range) {
if (
!popup ||
!range
) {
return;
}
var rect =
range.getBoundingClientRect();
if (
rect.width === 0 &&
rect.height === 0
) {
return;
}
popup.style.display =
'block';
var popupRect =
popup.getBoundingClientRect();
var left =
rect.left +
rect.width / 2 -
popupRect.width / 2;
var top =
rect.bottom +
10;
var margin = 10;
/*
* 左边界
*/
if (
left < margin
) {
left = margin;
}
/*
* 右边界
*/
if (
left +
popupRect.width >
window.innerWidth -
margin
) {
left =
window.innerWidth -
popupRect.width -
margin;
}
/*
* 底部空间不足
*/
if (
top +
popupRect.height >
window.innerHeight -
margin
) {
top =
rect.top -
popupRect.height -
10;
}
/*
* 顶部边界
*/
if (
top < margin
) {
top = margin;
}
popup.style.left =
Math.round(
left +
window.scrollX
) + 'px';
popup.style.top =
Math.round(
top +
window.scrollY
) + 'px';
}
/* =====================================================
17. 显示 Popup
===================================================== */
function showPopup(range) {
createPopup();
popup.style.display =
'block';
positionPopup(range);
}
/* =====================================================
18. 隐藏 Popup
===================================================== */
function hidePopup() {
if (!popup) {
return;
}
popup.style.display =
'none';
}
/* =====================================================
19. MediaWiki 提示
===================================================== */
function showMessage(message) {
if (
typeof mw.notify ===
'function'
) {
mw.notify(
message,
{
type: 'info',
autoHide: true
}
);
} else {
console.log(
message
);
}
}
/* =====================================================
20. 鼠标松开
===================================================== */
function handleMouseUp() {
setTimeout(
function () {
var data =
getSelectionData();
if (!data) {
return;
}
selectedText =
data.text;
showPopup(
data.range
);
},
20
);
}
/* =====================================================
21. 点击页面其他位置
===================================================== */
function handleDocumentMouseDown(
event
) {
if (!popup) {
return;
}
if (
popup.contains(
event.target
)
) {
return;
}
hidePopup();
}
/* =====================================================
22. ESC
===================================================== */
function handleKeyDown(event) {
if (
event.key ===
'Escape'
) {
hidePopup();
}
}
/* =====================================================
23. 页面滚动
===================================================== */
function handleScroll() {
hidePopup();
}
/* =====================================================
24. 初始化
===================================================== */
function init() {
if (!shouldEnable()) {
return;
}
createPopup();
document.addEventListener(
'mouseup',
handleMouseUp
);
document.addEventListener(
'mousedown',
handleDocumentMouseDown
);
document.addEventListener(
'keydown',
handleKeyDown
);
window.addEventListener(
'scroll',
handleScroll,
{ passive: true }
);
window.addEventListener(
'resize',
handleScroll
);
}
/* =====================================================
25. 启动
===================================================== */
if (
document.readyState ===
'loading'
) {
document.addEventListener(
'DOMContentLoaded',
init
);
} else {
init();
}
})();
