✨🎊 Postal Wiki 三周年! 🎊✨
🎂 2026.09.11 — 邮路三载,百科同行
用户:Hehua/filepreview.js:修订间差异
来自Postal Wiki
< 用户:Hehua
无编辑摘要 |
[IPE-NEXT] Quick edit |
||
| (未显示同一用户的4个中间版本) | |||
| 第16行: | 第16行: | ||
if (!window.mw || !mw.loader) return; | if (!window.mw || !mw.loader) return; | ||
mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () { | mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () { | ||
var CONFIG = { | var CONFIG = { | ||
delay: 400, | delay: 400, | ||
maxPopupWidth: 480, | |||
maxPopupWidth: | maxPopupHeight: 540 | ||
maxPopupHeight: | |||
}; | }; | ||
var state = { fetchTimer: null, hideTimer: null, popup: null }; | var state = { fetchTimer: null, hideTimer: null, popup: null }; | ||
/* ---------- localized namespace for File (NS 6) ---------- */ | |||
function getFileNSNames() { | |||
var names = ['File', 'Image']; | |||
var fmt = mw.config.get('wgFormattedNamespaces') || {}; | |||
if (fmt[6] && names.indexOf(fmt[6]) === -1) names.push(fmt[6]); | |||
var aliases = mw.config.get('wgNamespaceAliases') || {}; | |||
for (var a in aliases) { if (aliases[a] === 6 && names.indexOf(a) === -1) names.push(a); } | |||
return names; | |||
} | |||
var fileNSNames = getFileNSNames(); | |||
var fileNSRe = new RegExp('^(' + fileNSNames.map(function (n) { return n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('|') + '):(.+)$', 'i'); | |||
var fileCSSsel = fileNSNames.map(function (n) { return 'a[title^="' + n + ':"]'; }).join(','); | |||
/* ---------- utils ---------- */ | |||
function esc(s) { return mw.html.escape(String(s)); } | |||
function fmtBytes(b) { | |||
if (!b) return '?'; | |||
if (b < 1024) return b + ' B'; | |||
if (b < 1048576) return (b / 1024).toFixed(1) + ' KB'; | |||
return (b / 1048576).toFixed(1) + ' MB'; | |||
} | |||
function getFileTitle(link) { | |||
var t = link.getAttribute('title') || ''; | |||
var m = t.match(fileNSRe); | |||
if (m) return 'File:' + m[2]; | |||
var h = link.getAttribute('href') || ''; | |||
m = h.match(/[?&]title=(File|Image):([^&]+)/); | |||
if (m) return 'File:' + decodeURIComponent(m[2].replace(/\+/g, ' ')); | |||
return null; | |||
} | |||
/* ---------- popup ---------- */ | /* ---------- popup ---------- */ | ||
| 第47行: | 第75行: | ||
document.body.appendChild(el); | document.body.appendChild(el); | ||
state.popup = el; | state.popup = el; | ||
el.addEventListener('mouseenter', function () { | el.addEventListener('mouseenter', function () { clearTimeout(state.hideTimer); }); | ||
el.addEventListener('mouseleave', function () { state.hideTimer = setTimeout(hidePopup, 300); }); | |||
el.addEventListener('mouseleave', function () { | |||
} | } | ||
return state.popup; | return state.popup; | ||
| 第75行: | 第99行: | ||
/* ---------- api + render ---------- */ | /* ---------- api + render ---------- */ | ||
function fetchData(title) { | function fetchData(title) { | ||
return new mw.Api().get({ | return new mw.Api().get({ | ||
| 第100行: | 第107行: | ||
tllimit: 50, | tllimit: 50, | ||
iiprop: 'url|size|user|comment|timestamp|extmetadata', | iiprop: 'url|size|user|comment|timestamp|extmetadata', | ||
format: 'json' | format: 'json' | ||
}); | }); | ||
| 第116行: | 第122行: | ||
var ii = page.imageinfo && page.imageinfo[0]; | var ii = page.imageinfo && page.imageinfo[0]; | ||
if (ii) { | if (ii) { | ||
var meta = []; | var meta = []; | ||
if (ii.width && ii.height) meta.push(ii.width + '\u00d7' + ii.height + ' (' + fmtBytes(ii.size) + ')'); | if (ii.width && ii.height) meta.push(ii.width + '\u00d7' + ii.height + ' (' + fmtBytes(ii.size) + ')'); | ||
| 第143行: | 第145行: | ||
if (page.templates && page.templates.length) { | if (page.templates && page.templates.length) { | ||
parts.push('<div style="margin-top:8px;font-weight:bold;font-size:13px;">\u6a21\u677f(\u542b\u7248\u6743/\u8bb8\u53ef)</div>'); | parts.push('<div style="margin-top:8px;font-weight:bold;font-size:13px;">\u6a21\u677f(\u542b\u7248\u6743/\u8bb8\u53ef)</div>'); | ||
parts.push('<div style="margin:2px 0 0;max-height: | parts.push('<div style="margin:2px 0 0;max-height:140px;overflow-y:auto;">'); | ||
page.templates.slice(0, 40).forEach(function (t) { | page.templates.slice(0, 40).forEach(function (t) { | ||
parts.push('<span style="display:inline-block;background:#f0f2f5;border:1px solid #d4d8dd;border-radius:3px;padding:1px 5px;margin:1px 2px;font:12px monospace;color:#333;">{{' + esc(t.title.replace(/^Template:/, '')) + '}}</span>'); | parts.push('<span style="display:inline-block;background:#f0f2f5;border:1px solid #d4d8dd;border-radius:3px;padding:1px 5px;margin:1px 2px;font:12px monospace;color:#333;">{{' + esc(t.title.replace(/^Template:/, '')) + '}}</span>'); | ||
| 第182行: | 第184行: | ||
function scanLinks() { | function scanLinks() { | ||
document.querySelectorAll(fileCSSsel).forEach(bindLink); | |||
} | } | ||
/* ---------- | /* ---------- init ---------- */ | ||
var observer = new MutationObserver( | var observer = new MutationObserver(scanLinks); | ||
observer.observe(document.body, { childList: true, subtree: true }); | observer.observe(document.body, { childList: true, subtree: true }); | ||
scanLinks(); | scanLinks(); | ||
window.addEventListener('scroll', hidePopup, true); | window.addEventListener('scroll', hidePopup, true); | ||
}); | }); | ||
})(); | })(); | ||
2026年7月23日 (四) 07:07的最新版本
/**
* FilePreview — 文件链接悬停预览脚本
* 鼠标悬停在 File: 蓝色链接上时,弹出浮窗显示:
* - 缩略图预览
* - 文件信息(尺寸、大小、上传者)
* - 分类 (categories)
* - 版权/许可模板 (templates)
*
* 安装方式:
* 将本脚本的内容复制到 User:<你的名字>/common.js
* 或通过 importScript('User:你的名字/FilePreview.js'); 引入
*/
(function () {
'use strict';
if (!window.mw || !mw.loader) return;
mw.loader.using(['mediawiki.api', 'mediawiki.util'], function () {
var CONFIG = {
delay: 400,
maxPopupWidth: 480,
maxPopupHeight: 540
};
var state = { fetchTimer: null, hideTimer: null, popup: null };
/* ---------- localized namespace for File (NS 6) ---------- */
function getFileNSNames() {
var names = ['File', 'Image'];
var fmt = mw.config.get('wgFormattedNamespaces') || {};
if (fmt[6] && names.indexOf(fmt[6]) === -1) names.push(fmt[6]);
var aliases = mw.config.get('wgNamespaceAliases') || {};
for (var a in aliases) { if (aliases[a] === 6 && names.indexOf(a) === -1) names.push(a); }
return names;
}
var fileNSNames = getFileNSNames();
var fileNSRe = new RegExp('^(' + fileNSNames.map(function (n) { return n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }).join('|') + '):(.+)$', 'i');
var fileCSSsel = fileNSNames.map(function (n) { return 'a[title^="' + n + ':"]'; }).join(',');
/* ---------- utils ---------- */
function esc(s) { return mw.html.escape(String(s)); }
function fmtBytes(b) {
if (!b) return '?';
if (b < 1024) return b + ' B';
if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
return (b / 1048576).toFixed(1) + ' MB';
}
function getFileTitle(link) {
var t = link.getAttribute('title') || '';
var m = t.match(fileNSRe);
if (m) return 'File:' + m[2];
var h = link.getAttribute('href') || '';
m = h.match(/[?&]title=(File|Image):([^&]+)/);
if (m) return 'File:' + decodeURIComponent(m[2].replace(/\+/g, ' '));
return null;
}
/* ---------- popup ---------- */
function getPopup() {
if (!state.popup) {
var el = document.createElement('div');
el.id = 'filepreview-popup';
el.style.cssText = [
'display:none;position:fixed;z-index:9999',
'background:#fff',
'border:1px solid #c8ccd1',
'border-radius:6px',
'box-shadow:0 4px 16px rgba(0,0,0,0.2)',
'padding:14px',
'max-width:' + CONFIG.maxPopupWidth + 'px',
'max-height:' + CONFIG.maxPopupHeight + 'px',
'overflow-y:auto',
'font:13px/1.5 sans-serif',
'color:#222'
].join(';');
document.body.appendChild(el);
state.popup = el;
el.addEventListener('mouseenter', function () { clearTimeout(state.hideTimer); });
el.addEventListener('mouseleave', function () { state.hideTimer = setTimeout(hidePopup, 300); });
}
return state.popup;
}
function showPopup(link, x, y, html) {
var popup = getPopup();
popup.innerHTML = html;
popup.style.display = 'block';
var rect = popup.getBoundingClientRect();
var left = Math.max(6, Math.min(x + 12, window.innerWidth - rect.width - 6));
var top = Math.max(6, Math.min(y + 12, window.innerHeight - rect.height - 6));
popup.style.left = left + 'px';
popup.style.top = top + 'px';
}
function hidePopup() {
clearTimeout(state.hideTimer);
clearTimeout(state.fetchTimer);
if (state.popup) state.popup.style.display = 'none';
}
/* ---------- api + render ---------- */
function fetchData(title) {
return new mw.Api().get({
action: 'query',
titles: title,
prop: 'categories|templates|imageinfo',
cllimit: 50,
tllimit: 50,
iiprop: 'url|size|user|comment|timestamp|extmetadata',
format: 'json'
});
}
function render(data, link, x, y) {
var pages = data.query && data.query.pages;
if (!pages) { showPopup(link, x, y, '<div style="color:#c00;">\u65e0\u6cd5\u83b7\u53d6\u6570\u636e</div>'); return; }
var id = Object.keys(pages)[0], page = pages[id];
if (id === '-1' || !page) { showPopup(link, x, y, '<div style="color:#888;">\u6587\u4ef6\u9875\u4e0d\u5b58\u5728</div>'); return; }
var parts = [];
parts.push('<div style="font-weight:bold;font-size:14px;margin-bottom:6px;word-break:break-all;">' + esc(page.title) + '</div>');
var ii = page.imageinfo && page.imageinfo[0];
if (ii) {
var meta = [];
if (ii.width && ii.height) meta.push(ii.width + '\u00d7' + ii.height + ' (' + fmtBytes(ii.size) + ')');
if (ii.user) meta.push('\u4e0a\u4f20\u8005: ' + esc(ii.user));
if (meta.length) parts.push('<div style="color:#555;margin-bottom:4px;font-size:12px;">' + meta.join(' | ') + '</div>');
if (ii.extmetadata) {
var lf = ii.extmetadata.LicenseShortName || ii.extmetadata.License || ii.extmetadata.Copyright;
if (lf) parts.push('<div style="color:#555;margin-bottom:4px;font-size:12px;">\u8bb8\u53ef: ' + esc(String(lf.value || lf)) + '</div>');
}
}
if (page.categories && page.categories.length) {
parts.push('<div style="margin-top:8px;font-weight:bold;font-size:13px;">\u5206\u7c7b</div><ul style="margin:2px 0 0 14px;padding:0;">');
page.categories.slice(0, 25).forEach(function (c) {
parts.push('<li style="padding:1px 0;font-size:12px;">\u2022 ' + esc(c.title.replace(/^Category:/, '')) + '</li>');
});
if (page.categories.length > 25) parts.push('<li style="color:#888;font-size:12px;">\u2026\u8fd8\u6709' + (page.categories.length - 25) + '\u4e2a</li>');
parts.push('</ul>');
} else {
parts.push('<div style="margin-top:6px;color:#888;font-size:12px;">\u65e0\u5206\u7c7b</div>');
}
if (page.templates && page.templates.length) {
parts.push('<div style="margin-top:8px;font-weight:bold;font-size:13px;">\u6a21\u677f(\u542b\u7248\u6743/\u8bb8\u53ef)</div>');
parts.push('<div style="margin:2px 0 0;max-height:140px;overflow-y:auto;">');
page.templates.slice(0, 40).forEach(function (t) {
parts.push('<span style="display:inline-block;background:#f0f2f5;border:1px solid #d4d8dd;border-radius:3px;padding:1px 5px;margin:1px 2px;font:12px monospace;color:#333;">{{' + esc(t.title.replace(/^Template:/, '')) + '}}</span>');
});
if (page.templates.length > 40) parts.push('<span style="color:#888;font-size:11px;">\u2026+' + (page.templates.length - 40) + '</span>');
parts.push('</div>');
}
parts.push('<div style="margin-top:10px;border-top:1px solid #eaecf0;padding-top:6px;text-align:right;font-size:12px;">' +
'<a href="' + mw.util.getUrl(page.title) + '" target="_blank">\u6253\u5f00\u6587\u4ef6\u9875 \u2192</a></div>');
showPopup(link, x, y, parts.join(''));
}
/* ---------- bind ---------- */
function bindLink(link) {
if (link._filePreview) return;
link._filePreview = true;
link.addEventListener('mouseenter', function (e) {
clearTimeout(state.fetchTimer);
clearTimeout(state.hideTimer);
var title = getFileTitle(link);
if (!title) return;
var x = e.clientX, y = e.clientY;
state.fetchTimer = setTimeout(function () {
showPopup(link, x, y, '<div style="color:#888;">\u52a0\u8f7d\u4e2d\u2026 ' + esc(title) + '</div>');
fetchData(title).then(function (d) { render(d, link, x, y); })
.catch(function (err) { showPopup(link, x, y, '<div style="color:#c00;">\u5931\u8d25: ' + esc(String(err)) + '</div>'); });
}, CONFIG.delay);
});
link.addEventListener('mouseleave', function () {
clearTimeout(state.fetchTimer);
state.hideTimer = setTimeout(hidePopup, 200);
});
}
function scanLinks() {
document.querySelectorAll(fileCSSsel).forEach(bindLink);
}
/* ---------- init ---------- */
var observer = new MutationObserver(scanLinks);
observer.observe(document.body, { childList: true, subtree: true });
scanLinks();
window.addEventListener('scroll', hidePopup, true);
});
})();