MediaWiki:Common.js
跳转到导航
跳转到搜索
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Internet Explorer或Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5
- Opera:按 Ctrl-F5。
/* =====================================================================
帝鹰维基 · 全站脚本 v13
模块:① 顶部主导航 ② 管理员可视化面板(直传图片+文字样式)
③ ♪ 帝鹰电台 ④ 💬 聊天室 ⑤ 📞 联系入口(联系窗口 + 🤖 AI 咨询窗口)
===================================================================== */
(function () {
/* ---------- ① 顶部主导航:改入口就在 links 列表里,一行一个 ---------- */
var links = [
['主站', 'Main_Page'],
['成员专区', '帝鹰核心成员'],
['组织章程', '组织章程'],
['史料归档', '聊天室记录归档'],
['分类', 'Special:Categories']
];
function buildNav() {
if (document.getElementById('dy-topnav')) return;
var nav = document.createElement('nav');
nav.id = 'dy-topnav';
links.forEach(function (it) {
var a = document.createElement('a');
a.href = mw.util.getUrl(it[1]);
a.textContent = it[0];
nav.appendChild(a);
});
var pageBase = document.getElementById('mw-page-base');
if (pageBase && pageBase.parentNode) {
pageBase.parentNode.insertBefore(nav, pageBase);
} else {
document.body.insertBefore(nav, document.body.firstChild);
}
}
/* ---------- ② 管理员专属:可视化编辑面板(右下角浮动条) ----------
功能:🖼️ 本页直接上传图片(不跳转原生上传页,传完显示文件名可直接填卡片)
🎨 文字样式(字色/字号/字体可视化选择 → 源代码编辑器内直接包裹所选文字,
可视化编辑器内则生成 {{彩色文字}}/{{字体样式}} 模板代码一键复制)
仅 sysop(管理员)可见,访客不加载。 */
function isAdmin() {
return (mw.config.get('wgUserGroups') || []).indexOf('sysop') !== -1;
}
/* 通用小弹窗:标题 + 内容HTML + 关闭 */
function dyDialog(title, bodyHtml) {
var ov = document.getElementById('dy-dlg');
if (ov) ov.remove();
ov = document.createElement('div');
ov.id = 'dy-dlg';
ov.innerHTML =
'<div class="dy-dlg-card">' +
'<div class="dy-dlg-bar"><span>' + title + '</span>' +
'<button id="dy-dlg-close">✕</button></div>' +
'<div class="dy-dlg-body">' + bodyHtml + '</div>' +
'</div>';
document.body.appendChild(ov);
ov.addEventListener('click', function (e) { if (e.target === ov) ov.remove(); });
ov.querySelector('#dy-dlg-close').addEventListener('click', function () { ov.remove(); });
return ov;
}
/* 把代码写进源代码编辑器当前光标/选区(源代码编辑模式专用) */
function insertToSourceEditor(code) {
var ta = document.getElementById('wpTextbox1');
if (!ta) return false;
var s = ta.selectionStart, e = ta.selectionEnd;
ta.value = ta.value.slice(0, s) + code + ta.value.slice(e);
ta.selectionStart = ta.selectionEnd = s + code.length;
ta.focus();
return true;
}
/* 🎨 文字样式弹窗:颜色+字号+字体 → 编辑器包裹 / 模板代码 */
function openTextDialog() {
var sel = '';
var ta = document.getElementById('wpTextbox1');
if (ta && ta.selectionStart !== ta.selectionEnd) {
sel = ta.value.slice(ta.selectionStart, ta.selectionEnd);
}
var colors = ['#b3261e', '#c0392b', '#e67e22', '#8a7434', '#2f7a3f', '#1f6f8b', '#3f51b5', '#7b1fa2', '#23281f', '#ffffff'];
var sw = colors.map(function (c) {
return '<button type="button" class="dy-sw" data-c="' + c + '" style="background:' + c + '"></button>';
}).join('');
var sizes = ['12px', '14px', '15px', '17px', '20px', '24px', '30px', '36px'].map(function (s) {
return '<option value="' + s + '">' + s + '</option>';
}).join('');
var fonts = '<option value="">(不换字体)</option><option value="KaiTi">楷体</option>' +
'<option value="SimSun">宋体</option><option value="SimHei">黑体</option>' +
'<option value="FangSong">仿宋</option><option value="STZhongsong">中宋</option>';
var ov = dyDialog('🎨 文字样式(字色 / 字号 / 字体)',
'<div class="dy-frow"><label>文字</label><input id="dy-f-text" value="' + sel.replace(/"/g, '"') + '" placeholder="要美化的文字"></div>' +
'<div class="dy-frow"><label>颜色</label><div>' + sw +
'<input type="color" id="dy-f-color" value="#b3261e" title="自定义颜色"></div></div>' +
'<div class="dy-frow"><label>字号</label><select id="dy-f-size"><option value="">(默认大小)</option>' + sizes + '</select></div>' +
'<div class="dy-frow"><label>字体</label><select id="dy-f-font">' + fonts + '</select></div>' +
'<button id="dy-f-ok" class="dy-fbtn">应用到编辑器</button>' +
'<div id="dy-f-tip" class="dy-ftip"></div>');
// 色块点选 → 同步到取色器
ov.querySelectorAll('.dy-sw').forEach(function (b) {
b.addEventListener('click', function () { ov.querySelector('#dy-f-color').value = b.getAttribute('data-c'); });
});
ov.querySelector('#dy-f-ok').addEventListener('click', function () {
var text = ov.querySelector('#dy-f-text').value || '文字';
var color = ov.querySelector('#dy-f-color').value;
var size = ov.querySelector('#dy-f-size').value;
var font = ov.querySelector('#dy-f-font').value;
var style = '';
if (color) style += 'color:' + color + ';';
if (size) style += 'font-size:' + size + ';';
if (font) style += 'font-family:' + font + ';';
if (!style) { alert('请至少选择一种样式'); return; }
// 源代码编辑器:直接包裹所选文字,所见即所得
if (document.getElementById('wpTextbox1')) {
insertToSourceEditor('<span style="' + style + '">' + text + '</span>');
ov.remove();
return;
}
// ★ 可视化编辑器(VE):直接往编辑区插入带样式的文字(所见即所得),
// 不再让用户去插模板——这是之前「字色切换没成功显示」的根因。
// 关键:注解必须先经文档 store.hash() 注册,线性数据里放 hash 引用。
var veSurface = window.ve && ve.init && ve.init.target && ve.init.target.surface;
if (veSurface) {
try {
veSurface.getView().focus(); // 聚焦编辑区,插入到当前光标
var veModel = veSurface.getModel();
var veAnn = ve.dm.annotationFactory.create('textStyle/span',
{ attributes: { style: style } });
var veHash = veModel.getDocument().getStore().hash(veAnn);
var veData = [];
for (var vi = 0; vi < text.length; vi++) veData.push([text[vi], [veHash]]);
veModel.getFragment().insertContent(veData);
ov.remove();
return;
} catch (err) { /* VE 插入失败则走下面的模板代码兜底 */ }
}
// 普通页面(非编辑状态):生成模板代码 + 使用说明
var wikitext = font
? '{{字体样式|字体=' + font + '|字号=' + (size || '') + '|文字=' + text + '}}'
: '{{彩色文字|颜色=' + color + '|字号=' + (size || '') + '|文字=' + text + '}}';
var tip = ov.querySelector('#dy-f-tip');
tip.innerHTML = '请先进入编辑状态再点「🎨 文字样式」即可直接插入。<br>' +
'生成代码(已复制):<code>' + wikitext.replace(/</g, '<') + '</code>';
try { navigator.clipboard.writeText(wikitext); } catch (e) {}
});
}
/* 🖼️ 本页直接上传弹窗:选文件 → mw.Api 上传 → 显示文件名用法 */
function openUploadDialog() {
var ov = dyDialog('🖼️ 上传图片 / 头像(本页直传)',
'<div class="dy-frow"><label>选择文件</label><input type="file" id="dy-u-file" accept="image/*"></div>' +
'<div class="dy-frow"><label>文件名</label><input id="dy-u-name" placeholder="留空=自动用原文件名(建议英文/拼音)"></div>' +
'<button id="dy-u-ok" class="dy-fbtn">开始上传</button>' +
'<div id="dy-u-tip" class="dy-ftip">上传完成后会显示文件名;把文件名填进卡片模板的 头像= 参数即可换头像。</div>');
ov.querySelector('#dy-u-ok').addEventListener('click', function () {
var f = ov.querySelector('#dy-u-file').files[0];
var tip = ov.querySelector('#dy-u-tip');
if (!f) { alert('请先选择图片文件'); return; }
var name = ov.querySelector('#dy-u-name').value.trim() || f.name;
if (!/\.(png|jpe?g|gif|webp|svg)$/i.test(name)) {
name += '.' + (f.name.split('.').pop() || 'jpg'); // 补扩展名
}
name = name.replace(/[^\w.-\u4e00-\u9fa5]/g, '_'); // 清理非法字符
tip.textContent = '上传中…(' + name + ')';
new mw.Api().upload(f, {
filename: name,
comment: '管理员页面直传(帝鹰维基)',
ignorewarnings: true
}).done(function (data) {
var fn = data && data.upload && data.upload.filename ? data.upload.filename : name;
tip.innerHTML = '✅ 上传成功!文件名:<b>' + fn + '</b><br>' +
'用法:编辑主页卡片,把 头像= 参数改成 <code>' + fn + '</code> 即可;' +
'文章里插入用 <code>[[' + fn + ']]</code>';
}).fail(function (err) {
tip.innerHTML = '❌ 上传失败:' + (err && err.error ? err.error.info : JSON.stringify(err));
});
});
}
/* 面板本体:右下角浮动条(替代旧「上传图片」跳转按钮) */
function addAdminPanel() {
if (!isAdmin()) return; // 仅管理员可见
if (document.getElementById('dy-admintool')) return;
var bar = document.createElement('div');
bar.id = 'dy-admintool';
bar.innerHTML =
'<span class="dy-at-cap">⚙️ 管理面板</span>' +
'<button id="dy-at-upload">🖼️ 上传图片</button>' +
'<button id="dy-at-text">🎨 文字样式</button>' +
'<a id="dy-at-wiki" href="' + mw.util.getUrl('Special:Upload') + '" target="_blank" title="原生上传页(备用)">原生上传</a>';
document.body.appendChild(bar);
bar.querySelector('#dy-at-upload').addEventListener('click', openUploadDialog);
bar.querySelector('#dy-at-text').addEventListener('click', openTextDialog);
}
/* ---------- ③ ♪ 帝鹰电台(侧边栏模块) ----------
曲库来自 /music-list.js(服务器定时生成,window.DY_MUSIC 数组:
[{id,title,src},...]),音频文件在本站 /music/ 目录,跨页不断播由
浏览器自行决定;每个页面独立实例,点击播放即出声。 */
function addRadio(panel, logo) {
if (document.getElementById('dy-radio')) return;
var box = document.createElement('div');
box.id = 'dy-radio';
box.innerHTML =
'<div class="dy-mod-h">♪ 帝鹰电台</div>' +
'<div id="dy-r-title">曲库加载中…</div>' +
'<div id="dy-r-ctl">' +
'<button data-act="prev" title="上一首">⏮</button>' +
'<button data-act="play" title="播放/暂停">▶</button>' +
'<button data-act="next" title="下一首">⏭</button>' +
'</div>';
if (logo && logo.nextSibling) panel.insertBefore(box, logo.nextSibling);
else panel.appendChild(box);
var list = [], idx = 0, audio = null, playing = false;
var titleEl = box.querySelector('#dy-r-title');
var playBtn = box.querySelector('[data-act="play"]');
function ensureAudio() {
if (audio) return;
audio = new Audio();
audio.addEventListener('ended', function () { next(1); });
audio.addEventListener('playing', function () { playing = true; playBtn.textContent = '⏸'; });
audio.addEventListener('pause', function () { playing = false; playBtn.textContent = '▶'; });
}
function show() {
titleEl.textContent = list.length ? ('▶ ' + (idx + 1) + '. ' + list[idx].title) : '曲库为空';
}
function play() {
if (!list.length) return;
ensureAudio();
var want = list[idx].src;
if (audio.src !== location.origin + want && audio.getAttribute('src') !== want) {
audio.src = want; // 首次/切歌:设置源
}
if (playing) { audio.pause(); } else { audio.play(); }
}
function next(step) {
if (!list.length) return;
idx = (idx + step + list.length) % list.length;
if (audio) { audio.src = list[idx].src; audio.play(); }
show();
}
box.addEventListener('click', function (e) {
var act = e.target.getAttribute && e.target.getAttribute('data-act');
if (act === 'play') play();
else if (act === 'next') next(1);
else if (act === 'prev') next(-1);
});
// 曲库脚本:/music-list.js 内容为 window.DY_MUSIC=[...]
var s = document.createElement('script');
s.src = '/music-list.js';
s.onload = function () {
list = (window.DY_MUSIC || []).filter(function (m) { return m && m.src; });
show();
};
s.onerror = function () { titleEl.textContent = '电台曲库暂不可用'; };
document.head.appendChild(s);
}
/* ---------- ④ 💬 帝鹰聊天室(侧边栏入口 + 全屏弹层) ----------
后端 = 同站 /chat/(api.php + index.html 自托管聊天室)。
主内容区不放聊天室,只有侧边栏这一个入口。 */
function addChat(panel, radio) {
if (document.getElementById('dy-chat')) return;
var box = document.createElement('div');
box.id = 'dy-chat';
box.innerHTML =
'<div class="dy-mod-h">💬 帝鹰聊天室</div>' +
'<button id="dy-chat-open">进入聊天室</button>';
if (radio && radio.nextSibling) panel.insertBefore(box, radio.nextSibling);
else panel.appendChild(box);
box.querySelector('#dy-chat-open').addEventListener('click', function () {
var ov = document.getElementById('dy-chat-ov');
if (!ov) {
ov = document.createElement('div');
ov.id = 'dy-chat-ov';
ov.innerHTML =
'<div class="dy-chat-frame">' +
'<div class="dy-chat-bar"><span>帝鹰聊天室</span>' +
'<button id="dy-chat-close">✕ 关闭</button></div>' +
'<iframe src="/chat/index.html" title="帝鹰聊天室"></iframe>' +
'</div>';
document.body.appendChild(ov);
ov.querySelector('#dy-chat-close').addEventListener('click', function () { ov.hidden = true; });
ov.addEventListener('click', function (e) { if (e.target === ov) ov.hidden = true; }); // 点空白处也可关
}
ov.hidden = false;
});
}
/* ---------- ⑤ 📞 联系入口(侧边栏模块):联系窗口 + 🤖 AI 咨询窗口 ---------- */
function addContact(panel, chatBox) {
if (document.getElementById('dy-contact')) return;
var box = document.createElement('div');
box.id = 'dy-contact';
box.innerHTML =
'<div class="dy-mod-h">📞 联系入口</div>' +
'<button id="dy-contact-open">📞 联系窗口</button>' +
'<button id="dy-ai-open">🤖 咨询窗口</button>';
if (chatBox && chatBox.nextSibling) panel.insertBefore(box, chatBox.nextSibling);
else panel.appendChild(box);
/* 联系窗口:管理事务通过管理员账号登录后台处理;即时交流走聊天室 */
box.querySelector('#dy-contact-open').addEventListener('click', function () {
var ov = dyDialog('📞 联系窗口',
'<div class="dy-ct-item"><b>站务/词条管理:</b>管理员账号密码登录本站即可直接处理</div>' +
'<div class="dy-ct-item"><b>即时交流:</b>帝鹰聊天室(全员实时在线)</div>' +
'<div class="dy-ct-item"><b>咨询求助:</b>侧边栏「🤖 咨询窗口」AI 助手随时解答</div>' +
'<button id="dy-ct-chat" class="dy-fbtn">💬 前往帝鹰聊天室</button>');
ov.querySelector('#dy-ct-chat').addEventListener('click', function () {
ov.remove();
var btn = document.getElementById('dy-chat-open');
if (btn) btn.click();
});
});
/* 咨询窗口:AI 对话(后端 /ai.php,密钥在服务端) */
box.querySelector('#dy-ai-open').addEventListener('click', function () {
var ov = dyDialog('🤖 咨询窗口 · 帝鹰维基 AI 助手',
'<div id="dy-ai-log">' +
'<div class="dy-ai-msg bot">你好!我是帝鹰维基咨询助手 🦅<br>可以问我:词条在哪找 / 怎么入会 / 本站怎么用…</div>' +
'</div>' +
'<div class="dy-ai-input"><input id="dy-ai-text" placeholder="输入你的问题…">' +
'<button id="dy-ai-send">发送</button></div>');
var log = ov.querySelector('#dy-ai-log');
var input = ov.querySelector('#dy-ai-text');
var history = [];
function bubble(cls, text) {
var d = document.createElement('div');
d.className = 'dy-ai-msg ' + cls;
d.textContent = text;
log.appendChild(d);
log.scrollTop = log.scrollHeight;
return d;
}
function send() {
var text = input.value.trim();
if (!text) return;
input.value = '';
bubble('user', text);
var loading = bubble('bot', '思考中…');
fetch('/ai.php', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message: text, history: history }) })
.then(function (r) { return r.json(); })
.then(function (j) {
loading.remove();
var reply = j && j.ok ? (j.reply || '(AI 未返回内容)') : ('出错了:' + (j && j.error || '未知错误'));
bubble('bot', reply);
history.push({ role: 'user', content: text }, { role: 'assistant', content: reply });
if (history.length > 16) history = history.slice(-16); // 只保留最近 8 轮
})
.catch(function (e) { loading.remove(); bubble('bot', '网络异常:' + e.message); });
}
ov.querySelector('#dy-ai-send').addEventListener('click', send);
input.addEventListener('keydown', function (e) { if (e.key === 'Enter') send(); });
setTimeout(function () { input.focus(); }, 100);
});
}
/* ---------- 装配入口 ---------- */
function buildSide(panel) {
var logo = document.getElementById('p-logo');
addRadio(panel, logo);
addChat(panel, document.getElementById('dy-radio'));
addContact(panel, document.getElementById('dy-chat'));
}
function boot() {
buildNav();
addAdminPanel();
var panel = document.getElementById('mw-panel');
if (panel) buildSide(panel);
}
if (window.jQuery && mw && mw.util) {
$(boot);
} else {
document.addEventListener('DOMContentLoaded', boot);
}
})();