有觉性 以安住且中立的心 照见身心的实相
MediaWiki:QuoteCopy.js:修订间差异
来自Dhamma.cn
创建页面,内容为“→========================================================= Dhamma.cn QuoteCopy.js 功能: 选中文字 ↓ 松开鼠标 ↓ 出现引用工具 ↓ 复制原文 / 复制出处 / 复制学术脚注 MediaWiki 1.43 使用现有 UrlShortener =========================================================: (function () { 'use strict'; /* ===================================================== 基本…” |
小无编辑摘要 |
||
| 第1行: | 第1行: | ||
/* ========================================================= | /* ========================================================= | ||
Dhamma.cn | Dhamma.cn | ||
法谈划词引用 | |||
MediaWiki 1.43 | |||
功能: | 功能: | ||
1. 在隆波帕默尊者法谈正文中选择文字 | |||
2. 松开鼠标左键 | |||
3. 弹出唯一按钮: | |||
“复制该段开示及出处” | |||
4. 调用 UrlShortener | |||
5. 自动生成短网址 | |||
6. 复制完整引用 | |||
示例: | |||
觉知不是控制心,而是如实地知道身心…… | |||
---------------------------------------- | |||
法谈摘自:Dhamma.cn 《隆波帕默尊者开示:觉知的要领》 | |||
该篇网址:https://dhamma.cn/s/x9K2 | |||
转载请保持原文,随喜分享,Sadhu! | |||
========================================================= */ | ========================================================= */ | ||
(function () { | (function () { | ||
'use strict'; | 'use strict'; | ||
| 第24行: | 第36行: | ||
===================================================== */ | ===================================================== */ | ||
var MIN_LENGTH = | var MIN_LENGTH = 2; | ||
var MAX_LENGTH = 8000; | var MAX_LENGTH = 8000; | ||
| 第32行: | 第45行: | ||
/* | /* | ||
* | * 当前页面短网址缓存 | ||
* | * | ||
* | * 同一页面只调用一次 UrlShortener。 | ||
*/ | */ | ||
var pageShortUrl = null; | var pageShortUrl = null; | ||
| 第51行: | 第64行: | ||
*/ | */ | ||
if ( | if ( | ||
mw.config.get('wgNamespaceNumber') !== 0 | mw.config.get( | ||
'wgNamespaceNumber' | |||
) !== 0 | |||
) { | ) { | ||
return false; | return false; | ||
| 第58行: | 第73行: | ||
/* | /* | ||
* | * 只在正常阅读页面 | ||
*/ | */ | ||
if ( | if ( | ||
mw.config.get('wgAction') !== 'view' | mw.config.get( | ||
'wgAction' | |||
) !== 'view' | |||
) { | ) { | ||
return false; | return false; | ||
| 第68行: | 第85行: | ||
/* | /* | ||
* | * 如果页面属于: | ||
* | * | ||
* 分类:隆波帕默尊者 | * 分类:隆波帕默尊者 | ||
| 第88行: | 第92行: | ||
*/ | */ | ||
var categories = | var categories = | ||
mw.config.get | mw.config.get( | ||
'wgCategories' | |||
) || []; | |||
' | |||
| 第162行: | 第102行: | ||
) !== -1 | ) !== -1 | ||
) { | ) { | ||
return true; | |||
} | } | ||
/* | /* | ||
* | * 允许将来通过隐藏元数据 | ||
* | * 手动启用。 | ||
*/ | */ | ||
if ( | if ( | ||
document.getElementById( | |||
'dhamma-quote-metadata' | |||
) | |||
) | |||
) { | ) { | ||
return | return true; | ||
} | } | ||
return | return false; | ||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
2. 创建 Popup | |||
===================================================== */ | ===================================================== */ | ||
| 第259行: | 第130行: | ||
if (popup) { | if (popup) { | ||
return | return; | ||
} | } | ||
popup = | popup = | ||
document.createElement('div'); | document.createElement( | ||
'div' | |||
); | |||
popup.id = | popup.id = | ||
| 第278行: | 第152行: | ||
popup.setAttribute( | popup.setAttribute( | ||
'aria-label', | 'aria-label', | ||
' | '复制该段开示及出处' | ||
); | ); | ||
/* | |||
* 只有一个按钮。 | |||
*/ | |||
popup.innerHTML = | popup.innerHTML = | ||
'< | '<button ' + | ||
' | 'type="button" ' + | ||
' | 'class="dhamma-quote-button" ' + | ||
'data-action="source">' + | |||
'复制该段开示及出处' + | |||
'</button>'; | |||
| 第321行: | 第180行: | ||
/* | /* | ||
* | * 点击按钮时, | ||
* | * 不让 Selection 因失焦消失。 | ||
*/ | */ | ||
popup.addEventListener( | popup.addEventListener( | ||
| 第335行: | 第194行: | ||
/* | /* | ||
* | * 点击按钮 | ||
*/ | */ | ||
popup.addEventListener( | popup.addEventListener( | ||
| 第352行: | 第211行: | ||
var | copyWithSource(); | ||
} | |||
); | |||
} | |||
/* ===================================================== | |||
3. 获取当前页面标题 | |||
===================================================== */ | |||
function getRawPageTitle() { | |||
/* | |||
* MediaWiki 当前页面标题 | |||
*/ | |||
var title = | |||
mw.config.get( | |||
'wgTitle' | |||
); | |||
if (title) { | |||
return title.trim(); | |||
} | |||
/* | |||
* 后备方案 | |||
*/ | |||
var heading = | |||
document.querySelector( | |||
'#firstHeading, .mw-page-title-main' | |||
); | |||
if (heading) { | |||
return heading | |||
.textContent | |||
.trim(); | |||
} | |||
return | return document.title | ||
.replace( | |||
/\s*-\s*.*$/, | |||
'' | |||
) | |||
.trim(); | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
4. 获取引用标题 | |||
===================================================== */ | ===================================================== */ | ||
function | function getCitationTitle() { | ||
/* | |||
* 如果页面存在明确的引用 metadata, | |||
* 优先使用。 | |||
*/ | |||
var metadata = | |||
document.getElementById( | |||
'dhamma-quote-metadata' | |||
); | |||
if (metadata) { | |||
var customTitle = | |||
metadata.getAttribute( | |||
'data-title' | |||
); | |||
if ( | |||
customTitle && | |||
customTitle.trim() | |||
) { | |||
return normalizeCitationTitle( | |||
customTitle | |||
); | |||
} | |||
} | } | ||
var | /* | ||
* 自动从页面标题判断。 | |||
*/ | |||
var title = | |||
getRawPageTitle(); | |||
return normalizeCitationTitle( | |||
title | |||
); | |||
} | |||
/* ===================================================== | |||
5. 标题清理 | |||
===================================================== */ | |||
function normalizeCitationTitle( | |||
title | |||
) { | |||
if (! | if (!title) { | ||
return | return '隆波帕默尊者开示'; | ||
} | } | ||
title = | |||
title.trim(); | |||
/* | |||
* MediaWiki 页面标题中的下划线 | |||
*/ | |||
title = | |||
title.replace( | |||
/_/g, | |||
' ' | |||
); | |||
/* | |||
* ------------------------------------------------- | |||
* 情况一: | |||
* | |||
* 《觉知的要领》-隆波帕默尊者-2026年... | |||
* | |||
* 提取第一个《...》 | |||
* ------------------------------------------------- | |||
*/ | |||
var bookMatch = | |||
title.match( | |||
/《([^》]+)》/ | |||
); | |||
if (bookMatch) { | |||
title = | |||
bookMatch[1].trim(); | |||
} | |||
/* | |||
* ------------------------------------------------- | |||
* 情况二: | |||
* | |||
* 隆波帕默尊者开示:觉知的要领 | |||
* ------------------------------------------------- | |||
*/ | |||
if ( | if ( | ||
title.indexOf( | |||
'隆波帕默尊者开示:' | |||
) === 0 | |||
) { | ) { | ||
return title; | |||
return | |||
} | } | ||
/* | /* | ||
* | * ------------------------------------------------- | ||
* 去掉常见前缀 | |||
* | |||
* D24.91 | |||
* D24.91- | |||
* D24.91: | |||
* ------------------------------------------------- | |||
*/ | */ | ||
title = | |||
title.replace( | |||
/^D\d+(?:\.\d+)?[\s::._-]*/, | |||
'' | |||
); | ); | ||
/* | |||
* 去除“隆波帕默尊者”后缀 | |||
*/ | |||
title = | |||
title.replace( | |||
/[-–—_\s]*隆波帕默尊者.*$/u, | |||
'' | |||
); | |||
/* | |||
* 去除常见日期后缀 | |||
* | |||
* 例如: | |||
* -2024年10月20日 | |||
* 2024年10月20日 | |||
*/ | |||
title = | |||
title.replace( | |||
/[-–—_\s]*\d{4}年\d{1,2}月\d{1,2}日.*$/u, | |||
'' | |||
); | |||
/* | |||
* 去除多余的连接符 | |||
*/ | |||
title = | |||
title.replace( | |||
/^[-–—_\s]+|[-–—_\s]+$/g, | |||
'' | |||
); | |||
. | title = | ||
title.trim(); | |||
/* | |||
* 如果清理后为空, | |||
* 使用原页面标题。 | |||
*/ | |||
if (!title) { | |||
title = | |||
getRawPageTitle(); | |||
} | |||
/* | |||
* 最终统一成: | |||
* | |||
* 隆波帕默尊者开示:XXXX | |||
*/ | |||
return ( | |||
'隆波帕默尊者开示:' + | |||
title | |||
); | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
6. 获取规范 URL | |||
===================================================== */ | ===================================================== */ | ||
function getCanonicalUrl() { | function getCanonicalUrl() { | ||
var | var canonical = | ||
mw.config.get( | mw.config.get( | ||
'wgCanonicalUrl' | 'wgCanonicalUrl' | ||
| 第519行: | 第471行: | ||
if ( | if (canonical) { | ||
return | return canonical; | ||
} | } | ||
/* | /* | ||
* | * 后备方案 | ||
*/ | */ | ||
var pageName = | var pageName = | ||
| 第550行: | 第502行: | ||
/* ===================================================== | /* ===================================================== | ||
7. 获取短网址 | |||
===================================================== */ | ===================================================== */ | ||
| 第556行: | 第508行: | ||
/* | /* | ||
* | * 当前页面已经有短网址 | ||
*/ | */ | ||
if (pageShortUrl) { | if (pageShortUrl) { | ||
| 第567行: | 第519行: | ||
/* | /* | ||
* | * 已经正在请求 | ||
*/ | */ | ||
if (shortUrlPromise) { | if (shortUrlPromise) { | ||
| 第579行: | 第531行: | ||
*/ | */ | ||
shortUrlPromise = | shortUrlPromise = | ||
new mw.Api().post({ | new mw.Api().post({ | ||
action: 'shortenurl', | action: | ||
'shortenurl', | |||
url: | url: | ||
getCanonicalUrl() | getCanonicalUrl() | ||
}) | }) | ||
.then( | |||
function (data) { | function (data) { | ||
/* | |||
* UrlShortener API | |||
* | |||
* { | |||
* shortenurl: { | |||
* shorturl: "..." | |||
* } | |||
* } | |||
*/ | |||
if ( | if ( | ||
data && | data && | ||
| 第605行: | 第570行: | ||
throw new Error( | throw new Error( | ||
'UrlShortener | 'UrlShortener 未返回 shorturl' | ||
); | ); | ||
} | } | ||
) | |||
.catch( | |||
function (error) { | function (error) { | ||
/* | |||
* 允许以后重新请求 | |||
*/ | |||
shortUrlPromise = | shortUrlPromise = | ||
null; | null; | ||
throw error; | throw error; | ||
| 第626行: | 第596行: | ||
/* ===================================================== | /* ===================================================== | ||
8. 获取选中文字 | |||
===================================================== */ | ===================================================== */ | ||
function | function getSelectionData() { | ||
var | 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 ( | if ( | ||
text.length > | |||
MAX_LENGTH | |||
) { | ) { | ||
return null; | |||
} | |||
var range = | |||
selection.getRangeAt(0); | |||
var container = | |||
range.commonAncestorContainer; | |||
/* | |||
* 如果是 Text Node, | |||
* 取得它的父元素。 | |||
*/ | |||
if ( | |||
container.nodeType === | |||
Node.TEXT_NODE | |||
) { | |||
container = | |||
container.parentElement; | |||
} | |||
return; | if (!container) { | ||
return null; | |||
} | } | ||
/* | /* | ||
* | * 只允许从正文中选择。 | ||
* | |||
* 使用 .mw-parser-output, | |||
* 不依赖 #mw-content-text, | |||
* 对不同 Skin 更兼容。 | |||
*/ | */ | ||
var content = | |||
container.closest( | |||
'.mw-parser-output' | |||
); | |||
if (!content) { | |||
return null; | |||
} | |||
return { | |||
text: | |||
normalizeSelectedText( | |||
text | |||
), | |||
range: | |||
range.cloneRange() | |||
}; | |||
} | |||
/* ===================================================== | |||
9. 清理选中文本 | |||
===================================================== */ | |||
function normalizeSelectedText( | |||
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(); | |||
} | |||
/* ===================================================== | |||
10. 生成最终引用文字 | |||
===================================================== */ | |||
function buildCitation( | |||
shortUrl | |||
) { | |||
var citationTitle = | |||
getCitationTitle(); | |||
return ( | |||
selectedText + | |||
'\n\n' + | |||
'----------------------------------------' + | |||
'\n\n' + | |||
'法谈摘自:Dhamma.cn 《' + | |||
citationTitle + | |||
'》' + | |||
'\n\n' + | |||
'该篇网址:' + | |||
shortUrl + | |||
'\n\n' + | |||
'转载请保持原文,随喜分享,Sadhu!' | |||
); | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
11. 复制到剪贴板 | |||
===================================================== */ | ===================================================== */ | ||
function | function copyText( | ||
text | |||
) { | |||
if ( | /* | ||
return; | * 现代浏览器 | ||
*/ | |||
if ( | |||
navigator.clipboard && | |||
window.isSecureContext | |||
) { | |||
return navigator.clipboard.writeText( | |||
text | |||
); | |||
} | } | ||
/* | |||
* 后备方案 | |||
*/ | |||
return fallbackCopy( | |||
text | |||
); | ); | ||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
12. 后备复制 | |||
===================================================== */ | ===================================================== */ | ||
function | function fallbackCopy( | ||
text | |||
) { | |||
return new Promise( | |||
function ( | |||
resolve, | |||
reject | |||
) { | |||
var textarea = | |||
document.createElement( | |||
'textarea' | |||
); | |||
textarea.value = | |||
text; | |||
textarea.setAttribute( | |||
'readonly', | |||
'' | |||
); | |||
textarea.style.position = | |||
'fixed'; | |||
textarea.style.left = | |||
'-9999px'; | |||
textarea.style.top = | |||
'0'; | |||
textarea.style.opacity = | |||
'0'; | |||
document.body.appendChild( | |||
textarea | |||
); | |||
textarea.focus(); | |||
textarea.select(); | |||
try { | |||
var success = | |||
document.execCommand( | |||
'copy' | |||
); | |||
document.body.removeChild( | |||
textarea | |||
); | |||
if (success) { | |||
resolve(); | |||
} else { | |||
reject( | |||
new Error( | |||
'复制失败' | |||
) | |||
); | ); | ||
} | |||
} catch (error) { | |||
document.body.removeChild( | |||
textarea | |||
); | ); | ||
reject(error); | |||
} | } | ||
); | |||
} | |||
); | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
13. 复制并附出处 | |||
===================================================== */ | ===================================================== */ | ||
function | function copyWithSource() { | ||
if (!selectedText) { | if (!selectedText) { | ||
| 第951行: | 第916行: | ||
notify( | |||
'正在生成 Dhamma.cn 短网址……' | '正在生成 Dhamma.cn 短网址……' | ||
); | ); | ||
| 第959行: | 第924行: | ||
.then( | .then( | ||
function ( | |||
shortUrl | |||
) { | |||
return copyText( | |||
buildCitation( | |||
shortUrl | |||
) | |||
); | |||
} | |||
) | |||
.then( | |||
function () { | |||
notify( | |||
'已复制该段开示及出处' | |||
' | |||
); | ); | ||
| 第1,004行: | 第949行: | ||
.catch( | .catch( | ||
function (error) { | function (error) { | ||
console.error( | console.error( | ||
'Dhamma.cn | 'Dhamma.cn 引用复制失败:', | ||
error | error | ||
); | ); | ||
/* | |||
* 短网址失败时, | |||
* 不偷偷复制长 URL。 | |||
*/ | |||
notify( | |||
'短网址生成失败,请稍后再试' | |||
' | |||
); | ); | ||
} | } | ||
); | ); | ||
| 第1,055行: | 第970行: | ||
/* ===================================================== | /* ===================================================== | ||
14. 显示 Popup | |||
===================================================== */ | ===================================================== */ | ||
function | function showPopup( | ||
range | |||
) { | |||
createPopup(); | |||
| 第1,076行: | 第988行: | ||
rect.height === 0 | rect.height === 0 | ||
) { | ) { | ||
return; | return; | ||
} | } | ||
| 第1,084行: | 第997行: | ||
/* | |||
* 必须先显示, | |||
* 才能取得实际宽高。 | |||
*/ | |||
var popupRect = | var popupRect = | ||
popup.getBoundingClientRect(); | popup.getBoundingClientRect(); | ||
| 第1,099行: | 第1,016行: | ||
var margin = 10; | var margin = | ||
10; | |||
| 第1,108行: | 第1,026行: | ||
left < margin | left < margin | ||
) { | ) { | ||
left = margin; | |||
left = | |||
margin; | |||
} | } | ||
| 第1,130行: | 第1,050行: | ||
/* | /* | ||
* | * 如果下方空间不足, | ||
* 改到选区上方。 | |||
*/ | */ | ||
if ( | if ( | ||
| 第1,147行: | 第1,068行: | ||
/* | /* | ||
* | * 顶部 | ||
*/ | */ | ||
if ( | if ( | ||
top < margin | top < margin | ||
) { | ) { | ||
top = margin; | |||
top = | |||
margin; | |||
} | } | ||
| 第1,160行: | 第1,083行: | ||
left + | left + | ||
window.scrollX | window.scrollX | ||
) + 'px'; | ) + | ||
'px'; | |||
| 第1,167行: | 第1,091行: | ||
top + | top + | ||
window.scrollY | window.scrollY | ||
) + | ) + | ||
'px'; | |||
' | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
15. 隐藏 Popup | |||
===================================================== */ | ===================================================== */ | ||
| 第1,203行: | 第1,113行: | ||
/* ===================================================== | /* ===================================================== | ||
16. 鼠标松开 | |||
===================================================== */ | ===================================================== */ | ||
function handleMouseUp() { | function handleMouseUp() { | ||
/* | |||
* 延迟一点, | |||
* 等浏览器完成 Selection。 | |||
*/ | |||
setTimeout( | setTimeout( | ||
function () { | function () { | ||
| 第1,245行: | 第1,130行: | ||
if (!data) { | if (!data) { | ||
return; | return; | ||
} | } | ||
| 第1,258行: | 第1,144行: | ||
}, | }, | ||
30 | |||
); | ); | ||
} | } | ||
| 第1,265行: | 第1,150行: | ||
/* ===================================================== | /* ===================================================== | ||
17. 点击其他地方 | |||
===================================================== */ | ===================================================== */ | ||
| 第1,277行: | 第1,162行: | ||
/* | |||
* 点击 Popup 内部不关闭。 | |||
*/ | |||
if ( | if ( | ||
popup.contains( | popup.contains( | ||
| 第1,282行: | 第1,170行: | ||
) | ) | ||
) { | ) { | ||
return; | return; | ||
} | } | ||
| 第1,291行: | 第1,180行: | ||
/* ===================================================== | /* ===================================================== | ||
18. ESC | |||
===================================================== */ | ===================================================== */ | ||
function handleKeyDown(event) { | function handleKeyDown( | ||
event | |||
) { | |||
if ( | if ( | ||
| 第1,307行: | 第1,198行: | ||
/* ===================================================== | /* ===================================================== | ||
19. 提示 | |||
===================================================== */ | ===================================================== */ | ||
function | function notify( | ||
message | |||
) { | |||
if ( | |||
typeof mw.notify === | |||
'function' | |||
) { | |||
mw.notify( | |||
message, | |||
{ | |||
type: 'info', | |||
autoHide: true | |||
} | |||
); | |||
} else { | |||
console.log( | |||
message | |||
); | |||
} | |||
} | } | ||
/* ===================================================== | /* ===================================================== | ||
20. 初始化 | |||
===================================================== */ | ===================================================== */ | ||
| 第1,323行: | 第1,234行: | ||
if (!shouldEnable()) { | if (!shouldEnable()) { | ||
return; | return; | ||
} | } | ||
| 第1,350行: | 第1,262行: | ||
window.addEventListener( | window.addEventListener( | ||
'scroll', | 'scroll', | ||
hidePopup, | |||
{ passive: true } | { | ||
passive: true | |||
} | |||
); | ); | ||
| 第1,357行: | 第1,271行: | ||
window.addEventListener( | window.addEventListener( | ||
'resize', | 'resize', | ||
hidePopup | |||
); | |||
console.log( | |||
'Dhamma QuoteCopy: initialized' | |||
); | ); | ||
} | } | ||
| 第1,363行: | 第1,282行: | ||
/* ===================================================== | /* ===================================================== | ||
21. 确保 MediaWiki API 已加载 | |||
===================================================== */ | ===================================================== */ | ||
mw.loader.using( | |||
[ | |||
'mediawiki.api' | |||
) { | ] | ||
).then( | |||
function () { | |||
init(); | |||
init | |||
} | |||
); | |||
})(); | })(); | ||
2026年9月19日 (六) 22:18的最新版本
/* =========================================================
Dhamma.cn
法谈划词引用
MediaWiki 1.43
功能:
1. 在隆波帕默尊者法谈正文中选择文字
2. 松开鼠标左键
3. 弹出唯一按钮:
“复制该段开示及出处”
4. 调用 UrlShortener
5. 自动生成短网址
6. 复制完整引用
示例:
觉知不是控制心,而是如实地知道身心……
----------------------------------------
法谈摘自:Dhamma.cn 《隆波帕默尊者开示:觉知的要领》
该篇网址:https://dhamma.cn/s/x9K2
转载请保持原文,随喜分享,Sadhu!
========================================================= */
(function () {
'use strict';
/* =====================================================
基本设置
===================================================== */
var MIN_LENGTH = 2;
var MAX_LENGTH = 8000;
var popup = null;
var selectedText = '';
/*
* 当前页面短网址缓存
*
* 同一页面只调用一次 UrlShortener。
*/
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;
}
/*
* 如果页面属于:
*
* 分类:隆波帕默尊者
*
* 自动启用。
*/
var categories =
mw.config.get(
'wgCategories'
) || [];
if (
categories.indexOf(
'隆波帕默尊者'
) !== -1
) {
return true;
}
/*
* 允许将来通过隐藏元数据
* 手动启用。
*/
if (
document.getElementById(
'dhamma-quote-metadata'
)
) {
return true;
}
return false;
}
/* =====================================================
2. 创建 Popup
===================================================== */
function createPopup() {
if (popup) {
return;
}
popup =
document.createElement(
'div'
);
popup.id =
'dhamma-quote-popup';
popup.setAttribute(
'role',
'dialog'
);
popup.setAttribute(
'aria-label',
'复制该段开示及出处'
);
/*
* 只有一个按钮。
*/
popup.innerHTML =
'<button ' +
'type="button" ' +
'class="dhamma-quote-button" ' +
'data-action="source">' +
'复制该段开示及出处' +
'</button>';
document.body.appendChild(
popup
);
/*
* 点击按钮时,
* 不让 Selection 因失焦消失。
*/
popup.addEventListener(
'mousedown',
function (event) {
event.preventDefault();
}
);
/*
* 点击按钮
*/
popup.addEventListener(
'click',
function (event) {
var button =
event.target.closest(
'.dhamma-quote-button'
);
if (!button) {
return;
}
copyWithSource();
}
);
}
/* =====================================================
3. 获取当前页面标题
===================================================== */
function getRawPageTitle() {
/*
* MediaWiki 当前页面标题
*/
var title =
mw.config.get(
'wgTitle'
);
if (title) {
return title.trim();
}
/*
* 后备方案
*/
var heading =
document.querySelector(
'#firstHeading, .mw-page-title-main'
);
if (heading) {
return heading
.textContent
.trim();
}
return document.title
.replace(
/\s*-\s*.*$/,
''
)
.trim();
}
/* =====================================================
4. 获取引用标题
===================================================== */
function getCitationTitle() {
/*
* 如果页面存在明确的引用 metadata,
* 优先使用。
*/
var metadata =
document.getElementById(
'dhamma-quote-metadata'
);
if (metadata) {
var customTitle =
metadata.getAttribute(
'data-title'
);
if (
customTitle &&
customTitle.trim()
) {
return normalizeCitationTitle(
customTitle
);
}
}
/*
* 自动从页面标题判断。
*/
var title =
getRawPageTitle();
return normalizeCitationTitle(
title
);
}
/* =====================================================
5. 标题清理
===================================================== */
function normalizeCitationTitle(
title
) {
if (!title) {
return '隆波帕默尊者开示';
}
title =
title.trim();
/*
* MediaWiki 页面标题中的下划线
*/
title =
title.replace(
/_/g,
' '
);
/*
* -------------------------------------------------
* 情况一:
*
* 《觉知的要领》-隆波帕默尊者-2026年...
*
* 提取第一个《...》
* -------------------------------------------------
*/
var bookMatch =
title.match(
/《([^》]+)》/
);
if (bookMatch) {
title =
bookMatch[1].trim();
}
/*
* -------------------------------------------------
* 情况二:
*
* 隆波帕默尊者开示:觉知的要领
* -------------------------------------------------
*/
if (
title.indexOf(
'隆波帕默尊者开示:'
) === 0
) {
return title;
}
/*
* -------------------------------------------------
* 去掉常见前缀
*
* D24.91
* D24.91-
* D24.91:
* -------------------------------------------------
*/
title =
title.replace(
/^D\d+(?:\.\d+)?[\s::._-]*/,
''
);
/*
* 去除“隆波帕默尊者”后缀
*/
title =
title.replace(
/[-–—_\s]*隆波帕默尊者.*$/u,
''
);
/*
* 去除常见日期后缀
*
* 例如:
* -2024年10月20日
* 2024年10月20日
*/
title =
title.replace(
/[-–—_\s]*\d{4}年\d{1,2}月\d{1,2}日.*$/u,
''
);
/*
* 去除多余的连接符
*/
title =
title.replace(
/^[-–—_\s]+|[-–—_\s]+$/g,
''
);
title =
title.trim();
/*
* 如果清理后为空,
* 使用原页面标题。
*/
if (!title) {
title =
getRawPageTitle();
}
/*
* 最终统一成:
*
* 隆波帕默尊者开示:XXXX
*/
return (
'隆波帕默尊者开示:' +
title
);
}
/* =====================================================
6. 获取规范 URL
===================================================== */
function getCanonicalUrl() {
var canonical =
mw.config.get(
'wgCanonicalUrl'
);
if (canonical) {
return canonical;
}
/*
* 后备方案
*/
var pageName =
mw.config.get(
'wgPageName'
);
if (
pageName &&
mw.util &&
mw.util.getUrl
) {
return mw.util.getUrl(
pageName
);
}
return window.location.href;
}
/* =====================================================
7. 获取短网址
===================================================== */
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) {
/*
* UrlShortener API
*
* {
* shortenurl: {
* shorturl: "..."
* }
* }
*/
if (
data &&
data.shortenurl &&
data.shortenurl.shorturl
) {
pageShortUrl =
data.shortenurl.shorturl;
return pageShortUrl;
}
throw new Error(
'UrlShortener 未返回 shorturl'
);
}
)
.catch(
function (error) {
/*
* 允许以后重新请求
*/
shortUrlPromise =
null;
throw error;
}
);
return shortUrlPromise;
}
/* =====================================================
8. 获取选中文字
===================================================== */
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;
/*
* 如果是 Text Node,
* 取得它的父元素。
*/
if (
container.nodeType ===
Node.TEXT_NODE
) {
container =
container.parentElement;
}
if (!container) {
return null;
}
/*
* 只允许从正文中选择。
*
* 使用 .mw-parser-output,
* 不依赖 #mw-content-text,
* 对不同 Skin 更兼容。
*/
var content =
container.closest(
'.mw-parser-output'
);
if (!content) {
return null;
}
return {
text:
normalizeSelectedText(
text
),
range:
range.cloneRange()
};
}
/* =====================================================
9. 清理选中文本
===================================================== */
function normalizeSelectedText(
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();
}
/* =====================================================
10. 生成最终引用文字
===================================================== */
function buildCitation(
shortUrl
) {
var citationTitle =
getCitationTitle();
return (
selectedText +
'\n\n' +
'----------------------------------------' +
'\n\n' +
'法谈摘自:Dhamma.cn 《' +
citationTitle +
'》' +
'\n\n' +
'该篇网址:' +
shortUrl +
'\n\n' +
'转载请保持原文,随喜分享,Sadhu!'
);
}
/* =====================================================
11. 复制到剪贴板
===================================================== */
function copyText(
text
) {
/*
* 现代浏览器
*/
if (
navigator.clipboard &&
window.isSecureContext
) {
return navigator.clipboard.writeText(
text
);
}
/*
* 后备方案
*/
return fallbackCopy(
text
);
}
/* =====================================================
12. 后备复制
===================================================== */
function fallbackCopy(
text
) {
return new Promise(
function (
resolve,
reject
) {
var textarea =
document.createElement(
'textarea'
);
textarea.value =
text;
textarea.setAttribute(
'readonly',
''
);
textarea.style.position =
'fixed';
textarea.style.left =
'-9999px';
textarea.style.top =
'0';
textarea.style.opacity =
'0';
document.body.appendChild(
textarea
);
textarea.focus();
textarea.select();
try {
var success =
document.execCommand(
'copy'
);
document.body.removeChild(
textarea
);
if (success) {
resolve();
} else {
reject(
new Error(
'复制失败'
)
);
}
} catch (error) {
document.body.removeChild(
textarea
);
reject(error);
}
}
);
}
/* =====================================================
13. 复制并附出处
===================================================== */
function copyWithSource() {
if (!selectedText) {
return;
}
notify(
'正在生成 Dhamma.cn 短网址……'
);
getShortUrl()
.then(
function (
shortUrl
) {
return copyText(
buildCitation(
shortUrl
)
);
}
)
.then(
function () {
notify(
'已复制该段开示及出处'
);
hidePopup();
}
)
.catch(
function (error) {
console.error(
'Dhamma.cn 引用复制失败:',
error
);
/*
* 短网址失败时,
* 不偷偷复制长 URL。
*/
notify(
'短网址生成失败,请稍后再试'
);
}
);
}
/* =====================================================
14. 显示 Popup
===================================================== */
function showPopup(
range
) {
createPopup();
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';
}
/* =====================================================
15. 隐藏 Popup
===================================================== */
function hidePopup() {
if (!popup) {
return;
}
popup.style.display =
'none';
}
/* =====================================================
16. 鼠标松开
===================================================== */
function handleMouseUp() {
/*
* 延迟一点,
* 等浏览器完成 Selection。
*/
setTimeout(
function () {
var data =
getSelectionData();
if (!data) {
return;
}
selectedText =
data.text;
showPopup(
data.range
);
},
30
);
}
/* =====================================================
17. 点击其他地方
===================================================== */
function handleDocumentMouseDown(
event
) {
if (!popup) {
return;
}
/*
* 点击 Popup 内部不关闭。
*/
if (
popup.contains(
event.target
)
) {
return;
}
hidePopup();
}
/* =====================================================
18. ESC
===================================================== */
function handleKeyDown(
event
) {
if (
event.key ===
'Escape'
) {
hidePopup();
}
}
/* =====================================================
19. 提示
===================================================== */
function notify(
message
) {
if (
typeof mw.notify ===
'function'
) {
mw.notify(
message,
{
type: 'info',
autoHide: true
}
);
} else {
console.log(
message
);
}
}
/* =====================================================
20. 初始化
===================================================== */
function init() {
if (!shouldEnable()) {
return;
}
createPopup();
document.addEventListener(
'mouseup',
handleMouseUp
);
document.addEventListener(
'mousedown',
handleDocumentMouseDown
);
document.addEventListener(
'keydown',
handleKeyDown
);
window.addEventListener(
'scroll',
hidePopup,
{
passive: true
}
);
window.addEventListener(
'resize',
hidePopup
);
console.log(
'Dhamma QuoteCopy: initialized'
);
}
/* =====================================================
21. 确保 MediaWiki API 已加载
===================================================== */
mw.loader.using(
[
'mediawiki.api'
]
).then(
function () {
init();
}
);
})();
