这是林泊百科的迁移技术测试站点,所有改动不会同步到林泊百科上,此站点将会在正式迁移后关闭,请勿存放重要数据
MediaWiki:密码盘.js
来自Limbo Wiki
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
(function ($) {
'use strict';
/* 逗号分隔串 -> 数组 */
function splitList(str) {
if (!str) { return []; }
var out = [];
$.each(String(str).split(','), function (_, s) {
var v = $.trim(s);
if (v) { out.push(v); }
});
return out;
}
/* 解析 correct */
function parseAnswers(str) {
if (!str) { return []; }
var groups = [];
$.each(String(str).split(';'), function (_, part) {
var p = $.trim(part);
if (!p) { return; }
var list = splitList(p);
if (list.length) { groups.push(list); }
});
return groups;
}
/* 解析 target:与 correct 的组一一对应 */
function parseTargets(str) {
if (!str) { return []; }
var groups = [];
$.each(String(str).split(';'), function (_, part) {
var p = $.trim(part);
if (!p) { return; }
var list = splitList(p);
if (list.length) { groups.push(list); }
});
return groups;
}
/* 解析 layout;无效返回 null */
function parseLayout(str) {
if (!str) { return null; }
var out = [];
$.each(String(str).split(','), function (_, s) {
var n = parseInt($.trim(s), 10);
if (!isNaN(n) && n > 0) { out.push(n); }
});
return out.length ? out : null;
}
/* 键名消毒,防注入选择器 */
function normalizeKey(k) {
return String(k || '').replace(/[^A-Za-z0-9_-]/g, '');
}
function setup(pad) {
var $pad = $(pad);
if ($pad.data('pdInit')) { return; }
$pad.data('pdInit', true);
var answerGroups = parseAnswers($pad.attr('data-answer'));
var targetGroups = parseTargets($pad.attr('data-target'));
var $msg = $pad.find('.pd-msg');
/* 提示文案:取模板内已按 wikitext 解析好的 span 内容,缺省回退默认 */
var $okEl = $pad.find('.pd-okmsg');
var $errEl = $pad.find('.pd-errmsg');
var okMsg = $okEl.length ? $okEl.html() : '✓ 密码正确';
var errMsg = $errEl.length ? $errEl.html() : '✗ 密码错误,请重试';
if (!answerGroups.length) {
$msg.text('未配置密码:请设置 correct 参数').attr('class', 'pd-msg pd-msg-err');
return;
}
/* 位数:缺省取第一个密码长度;要求所有密码位数一致 */
var length = parseInt($pad.attr('data-length'), 10);
if (isNaN(length) || length < 1) { length = answerGroups[0].length; }
for (var g = 0; g < answerGroups.length; g++) {
if (answerGroups[g].length !== length) {
$msg.text('配置错误:各密码位数不一致').attr('class', 'pd-msg pd-msg-err');
return;
}
}
/* 符号:用户未填 symbols 时,默认 QWERTY 键盘 26 个大写字母 */
var symbolsRaw = $.trim($pad.attr('data-symbols') || '');
var symbolsProvided = symbolsRaw !== '';
var symbols = splitList(symbolsRaw);
if (!symbols.length) {
symbols = [
'Q','W','E','R','T','Y','U','I','O','P',
'A','S','D','F','G','H','J','K','L',
'Z','X','C','V','B','N','M'
];
}
/* 布局:显式填写 layout 优先;
未填时——默认键盘用 QWERTY 三行 [10,9,7],自定义符号用单行(自动折行) */
var layout = parseLayout($pad.attr('data-layout'));
if (!layout && !symbolsProvided) {
layout = [10, 9, 7];
}
var input = [];
var timer = null;
var $display = $pad.find('.pd-display');
var $keys = $pad.find('.pd-keys');
/* 槽位 */
$display.empty();
var slots = [];
for (var i = 0; i < length; i++) {
slots.push($('<span class="pd-slot"></span>').appendTo($display));
}
/* 按键按 layout 分行 */
var rows = [];
if (layout) {
var idx = 0;
for (var r = 0; r < layout.length; r++) {
var row = [];
for (var c = 0; c < layout[r]; c++) {
if (idx < symbols.length) { row.push(symbols[idx]); idx++; }
}
rows.push(row);
}
while (idx < symbols.length) { rows[rows.length - 1].push(symbols[idx]); idx++; }
} else {
rows.push(symbols.slice());
}
/* 创建单个按键 */
function addKey($row, sym) {
$('<button type="button" class="pd-key"></button>')
.text(sym)
.on('click', function () { press(sym); })
.appendTo($row);
}
/* 生成按键 */
$keys.empty();
for (r = 0; r < rows.length; r++) {
var $row = $('<div class="pd-row"></div>').appendTo($keys);
for (c = 0; c < rows[r].length; c++) {
addKey($row, rows[r][c]);
}
}
/* 控制行:退格 + 清空 */
var $ctrl = $('<div class="pd-row pd-ctrl-row"></div>').appendTo($keys);
$('<button type="button" class="pd-key pd-ctl" title="退格(删除上一个符号)">⌫</button>')
.on('click', backspace).appendTo($ctrl);
$('<button type="button" class="pd-key pd-ctl" title="清空全部输入">清空</button>')
.on('click', clearAll).appendTo($ctrl);
function render() {
for (var j = 0; j < slots.length; j++) {
if (j < input.length) {
slots[j].text(input[j]).addClass('pd-slot-filled');
} else {
slots[j].text('').removeClass('pd-slot-filled');
}
}
}
function setMsg(text, kind) {
$msg.html(text || '').attr('class', 'pd-msg' + (kind ? ' pd-msg-' + kind : ''));
}
/* 解锁第 groupIdx 组目标:限定在页面内容区内查找 .pd-secret-<键> */
function revealTargets(groupIdx) {
if (groupIdx < 0 || groupIdx >= targetGroups.length) { return; }
var keys = targetGroups[groupIdx];
if (!keys.length) { return; }
var $scope = $pad.closest('.mw-parser-output').first();
if (!$scope.length) { $scope = $(document); }
for (var k = 0; k < keys.length; k++) {
var key = normalizeKey(keys[k]);
if (!key) { continue; }
$scope.find('.pd-secret-' + key)
.removeClass('pd-secret-locked')
.addClass('pd-secret-open')
.css('display', '');
}
}
function press(sym) {
/* 取消待执行的自动清空,避免误清新输入 */
if (timer) { clearTimeout(timer); timer = null; $pad.removeClass('pd-shake'); }
if (input.length >= length) { return; }
input.push(sym);
render();
/* 不在此清空消息,提示保留到下一次判定 */
if (input.length === length) { check(); }
}
/* 查找与当前输入完全一致的密码组,返回组下标;无则 -1 */
function findMatch() {
for (var gi = 0; gi < answerGroups.length; gi++) {
if (answerGroups[gi].length !== input.length) { continue; }
var ok = true;
for (var k = 0; k < answerGroups[gi].length; k++) {
if (input[k] !== answerGroups[gi][k]) { ok = false; break; }
}
if (ok) { return gi; }
}
return -1;
}
function check() {
var hit = findMatch();
if (hit >= 0) {
/* 命中:解锁对应内容,不锁定;自动清空输入以便继续,但消息保留 */
$pad.addClass('pd-open');
revealTargets(hit);
setMsg(okMsg, 'ok');
timer = setTimeout(function () {
input = [];
render();
}, 800);
} else {
setMsg(errMsg, 'err');
$pad.addClass('pd-shake');
timer = setTimeout(function () {
$pad.removeClass('pd-shake');
input = [];
render();
}, 1000);
}
}
function backspace() {
if (timer) { clearTimeout(timer); timer = null; $pad.removeClass('pd-shake'); }
if (input.length > 0) { input.pop(); render(); }
}
function clearAll() {
if (timer) { clearTimeout(timer); timer = null; $pad.removeClass('pd-shake'); }
input = [];
render();
}
render();
}
function init($root) {
($root || $(document)).find('.pd-pad').each(function () { setup(this); });
}
/* 初始化:DOM 就绪 + wikipage.content 钩子(兼容动态加载内容) */
$(function () { init($(document)); });
if (window.mw && mw.hook) { mw.hook('wikipage.content').add(function ($c) { init($c); }); }
})(jQuery);
