User:魔琴/gadgets/quickredirectplus/index.js
外观
注意:保存之后,你必须清除浏览器缓存才能看到做出的更改。Google Chrome、Firefox、Microsoft Edge及Safari:按住⇧ Shift键并单击工具栏的“刷新”按钮。参阅Help:绕过浏览器缓存以获取更多帮助。
/* <nowiki>
* QR+ 快速创建重定向
* 此脚本由 ChatGPT 生成
* License: CC0 1.0 Universal
* <https://creativecommons.org/publicdomain/zero/1.0/deed.en>
*/
(function () {
var api = new mw.Api();
function RedirectDialog(config) {
RedirectDialog.parent.call(this, config);
}
OO.inheritClass(RedirectDialog, OO.ui.ProcessDialog);
RedirectDialog.static.name = 'redirectDialog';
RedirectDialog.static.title = 'QR+快速创建重定向页面';
RedirectDialog.static.actions = [
{ action: 'cancel', label: '取消', flags: 'safe' },
{ action: 'create', label: '创建', flags: ['primary', 'progressive'] }
];
RedirectDialog.static.size = 'large';
RedirectDialog.prototype.initialize = function () {
RedirectDialog.parent.prototype.initialize.call(this);
var panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.targetInput = new OO.ui.MultilineTextInputWidget({ placeholder: '每行一个目标页面名(回车分隔)', rows: 5 });
this.targetInput.$input.attr('rows', 5).css('min-height', '7.5em');
this.sectionInput = new OO.ui.TextInputWidget();
this.templateInput = new OO.ui.TextInputWidget({ placeholder: '不需「重定向」三字。每类用空格分开,如错字 繁简 模板' });
this.summaryInput = new OO.ui.TextInputWidget();
this.templateHint = $('<div>').addClass('qrp-template-hints').css({ 'margin-top': '0.5em', 'font-size': '90%', 'white-space': 'normal' });
this.targetHint = $('<div>').addClass('qrp-target-hints').css({ 'margin-top': '0.5em', 'font-size': '90%', 'white-space': 'normal' });
var apiCheck = new mw.Api();
var templateField = new OO.ui.FieldLayout(this.templateInput, { label: '重定向分类(可选)', align: 'top' });
var targetField = new OO.ui.FieldLayout(this.targetInput, { label: '将哪个页面重定向到此?(每行一个)', align: 'top' });
var fieldset = new OO.ui.FieldsetLayout({ label: '重定向设置' });
fieldset.addItems([
targetField,
new OO.ui.FieldLayout(this.sectionInput, { label: '到哪个章节?(可选)', align: 'top' }),
templateField,
new OO.ui.FieldLayout(this.summaryInput, { label: '编辑摘要(可选)', align: 'top' })
]);
templateField.$element.append(this.templateHint);
targetField.$element.append(this.targetHint);
// 状态与顺序
this._qrp_templateStatusMap = this._qrp_templateStatusMap || {};
this._qrp_templateItems = this._qrp_templateItems || [];
this._qrp_targetStatusMap = this._qrp_targetStatusMap || {};
this._qrp_targetItems = this._qrp_targetItems || [];
// IME flags
this._qrp_templateComposing = false;
this._qrp_targetComposing = false;
var makeBox = function (name, state, options) {
options = options || {};
var kind = options.kind || 'template';
var pageTitle = options.pageTitle || name;
var colors = {
template: {
exists: { border: 'green', color: 'green' },
missing: { border: 'red', color: 'red' },
fail: { border: 'red', color: 'red' },
pending: { border: '#999', color: '#666', opacity: 0.9 }
},
target: {
exists: { border: 'red', color: 'red' },
missing: { border: 'green', color: 'green' },
fail: { border: 'red', color: 'red' },
pending: { border: '#999', color: '#666', opacity: 0.9 }
}
};
var style = colors[kind] && colors[kind][state] ? colors[kind][state] : { border: '#ccc', color: '#333' };
var href = mw.util.getUrl(pageTitle);
var $a = $('<a>').attr({ href: href, target: '_blank', rel: 'noopener noreferrer' }).css({ 'text-decoration': 'none' });
var $s = $('<span>').text(name).css({ 'display': 'inline-block', 'border': '1px solid ' + style.border, 'border-radius': '4px', 'padding': '2px 6px', 'margin': '2px', 'font-size': '90%', 'background-color': '#fff', 'color': style.color, 'cursor': 'pointer' });
if (style.opacity) $s.css('opacity', style.opacity);
if (state === 'pending') $s.append($('<span>').text(' …').css({ 'font-weight': 'bold', 'color': style.color }));
$a.append($s);
return $a;
};
var renderTemplates = function () {
if (this._qrp_templateComposing) return; // 在 IME 输入时跳过渲染
var existList = [], missingList = [], invalidList = [], failList = [];
for (var i = 0; i < this._qrp_templateItems.length; i++) {
var it = this._qrp_templateItems[i];
var st = this._qrp_templateStatusMap[it];
if (st === 'exists') existList.push(it);
else if (st === 'missing') missingList.push(it);
else if (st === 'invalid') invalidList.push(it);
else if (st === 'fail') failList.push(it);
}
this.templateHint.empty();
if (existList.length) {
var $row = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('可用:'));
for (var j = 0; j < existList.length; j++) $row.append(makeBox(existList[j], 'exists', { kind: 'template', pageTitle: 'Template:' + existList[j] + '重定向' }));
this.templateHint.append($row);
}
if (missingList.length) {
var $row2 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('错误:模板不存在!'));
for (var k = 0; k < missingList.length; k++) $row2.append(makeBox(missingList[k], 'missing', { kind: 'template', pageTitle: 'Template:' + missingList[k] + '重定向' }));
this.templateHint.append($row2);
}
if (invalidList.length) {
var $rowInvalid = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('标题无效:'));
for (var m = 0; m < invalidList.length; m++) $rowInvalid.append(makeBox(invalidList[m], 'fail', { kind: 'template', pageTitle: 'Template:' + invalidList[m] + '重定向' }));
this.templateHint.append($rowInvalid);
}
if (failList.length) {
var $row3 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('查询失败: '));
for (var l = 0; l < failList.length; l++) $row3.append(makeBox(failList[l], 'fail', { kind: 'template', pageTitle: 'Template:' + failList[l] + '重定向' }));
this.templateHint.append($row3);
}
var pendingList = [];
for (var p = 0; p < this._qrp_templateItems.length; p++) { var itp = this._qrp_templateItems[p]; if (this._qrp_templateStatusMap[itp] === 'pending') pendingList.push(itp); }
if (pendingList.length) { var $row4 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('查询中:')); pendingList.forEach(function (itm) { $row4.append(makeBox(itm, 'pending', { kind: 'template', pageTitle: 'Template:' + itm + '重定向' })); }); this.templateHint.append($row4); }
try { if (typeof this.updateSize === 'function') this.updateSize(); } catch (e) { }
try { this.$body.css({ 'overflow': 'auto' }); } catch (e) { }
}.bind(this);
var renderTargets = function () {
if (this._qrp_targetComposing) return;
var existList = [], missingList = [], invalidList = [], failList = [];
for (var i = 0; i < this._qrp_targetItems.length; i++) {
var it = this._qrp_targetItems[i];
var st = this._qrp_targetStatusMap[it];
if (st === 'exists') existList.push(it);
else if (st === 'missing') missingList.push(it);
else if (st === 'invalid') invalidList.push(it);
else if (st === 'fail') failList.push(it);
}
this.targetHint.empty();
if (missingList.length) {
var $row = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('可创建:'));
for (var j = 0; j < missingList.length; j++) $row.append(makeBox(missingList[j], 'missing', { kind: 'target', pageTitle: missingList[j] }));
this.targetHint.append($row);
}
if (invalidList.length) {
var $rowInvalid = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('标题无效:'));
for (var m2 = 0; m2 < invalidList.length; m2++) $rowInvalid.append(makeBox(invalidList[m2], 'fail', { kind: 'target', pageTitle: invalidList[m2] }));
this.targetHint.append($rowInvalid);
}
if (existList.length) { var $row2 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('错误:目标页面已存在!')); for (var k = 0; k < existList.length; k++) $row2.append(makeBox(existList[k], 'exists', { kind: 'target', pageTitle: existList[k] })); this.targetHint.append($row2); }
if (failList.length) { var $row3 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('查询失败:')); for (var l = 0; l < failList.length; l++) $row3.append(makeBox(failList[l], 'fail', { kind: 'target', pageTitle: failList[l] })); this.targetHint.append($row3); }
var pendingList = [];
for (var p = 0; p < this._qrp_targetItems.length; p++) { var itp = this._qrp_targetItems[p]; if (this._qrp_targetStatusMap[itp] === 'pending') pendingList.push(itp); }
if (pendingList.length) { var $row4 = $('<div>').css({ 'margin-bottom': '0.25em' }).append($('<strong>').text('查询中:')); pendingList.forEach(function (itm) { $row4.append(makeBox(itm, 'pending', { kind: 'target', pageTitle: itm })); }); this.targetHint.append($row4); }
try { if (typeof this.updateSize === 'function') this.updateSize(); } catch (e) { }
try { this.$body.css({ 'overflow': 'auto' }); } catch (e) { }
}.bind(this);
// composition handlers:在 IME 输入时跳过渲染与查询
this.templateInput.$input.on('compositionstart.qrp', function () { this._qrp_templateComposing = true; }.bind(this));
this.templateInput.$input.on('compositionend.qrp', function () { this._qrp_templateComposing = false; this.templateInput.$input.trigger('input.qrp'); }.bind(this));
this.targetInput.$input.on('compositionstart.qrp', function () { this._qrp_targetComposing = true; }.bind(this));
this.targetInput.$input.on('compositionend.qrp', function () { this._qrp_targetComposing = false; this.targetInput.$input.trigger('input.qrp'); }.bind(this));
// helper: 使用 API 返回的 converted / normalized 信息将原始请求 title 映射到返回的页面
function handleQueryResponse(requestTitles, data, mapResultFn) {
requestTitles = requestTitles || [];
var pages = (data && data.query && data.query.pages) ? data.query.pages : {};
var converted = (data && data.query && data.query.converted) ? data.query.converted : [];
var normalized = (data && data.query && data.query.normalized) ? data.query.normalized : [];
var interwiki = (data && data.query && data.query.interwiki) ? data.query.interwiki : [];
// 构造转换 map: fromOriginal -> toConverted
var convMap = {};
converted.forEach(function (c) { convMap[c.from] = c.to; });
// normalized map: from -> to
var normMap = {};
normalized.forEach(function (n) { normMap[n.from] = n.to; });
// interwiki 集合(以 API 返回的 title 为键)
var interwikiSet = {};
interwiki.forEach(function (iw) { if (iw && iw.title) interwikiSet[iw.title] = true; });
// 将 pages 转成 title -> page 对象映射(方便查找)
var titleToPage = {};
Object.keys(pages).forEach(function (pid) { var pg = pages[pid]; if (pg && pg.title) titleToPage[pg.title] = pg; });
// 对每个请求 title,决定它对应的 page(如果有)或是否为 invalid/interwiki
requestTitles.forEach(function (req) {
var looked = convMap[req] || normMap[req] || req; // 先用 converted 再用 normalized
var pg = titleToPage[looked];
if (!pg) {
// 作为兜底,尝试把下划线/空格标准化
var alt = looked.replace(/_/g, ' ');
pg = titleToPage[alt] || titleToPage[alt.replace(/ /g, '_')];
}
if (pg) {
// API 明确返回了 page 对象
if (pg.invalid) {
// 标为 invalid
mapResultFn(req, { invalid: true });
} else {
// 返回原始 page 对象,调用方会根据 pg.missing 判断 exists/missing
mapResultFn(req, pg);
}
} else {
// 没有返回对应 page,检查 interwiki
if (interwikiSet[req] || interwikiSet[convMap[req]] || interwikiSet[normMap[req]] || interwikiSet[looked]) {
// 把 interwiki 当作 invalid(按需求显示为“标题无效”)
mapResultFn(req, { invalid: true });
} else {
// 没有匹配,视为 missing
mapResultFn(req, undefined);
}
}
});
}
// template input handler
this.templateInput.$input.off('input.qrp').on('input.qrp', (function () {
var raw = this.templateInput.getValue().trim();
var newItems = raw ? raw.split(/\s+/).filter(Boolean) : [];
var removed = this._qrp_templateItems.filter(function (it) { return newItems.indexOf(it) === -1; });
removed.forEach(function (it) { delete this._qrp_templateStatusMap[it]; }, this);
this._qrp_templateItems = newItems.slice();
var toQuery = [];
newItems.forEach(function (item) { if (!this._qrp_templateStatusMap.hasOwnProperty(item) || this._qrp_templateStatusMap[item] === 'pending') { this._qrp_templateStatusMap[item] = 'pending'; toQuery.push(item); } }, this);
if (this._qrp_templateComposing) return; // 正在 IME 输入,延后处理
renderTemplates();
if (toQuery.length > 0) {
// 为每个 item 构造请求标题(Template:NAME重定向)并保存映射
var requestTitles = toQuery.map(function (it) { return 'Template:' + it + '重定向'; });
var requestMap = {};
toQuery.forEach(function (it, idx) { requestMap[requestTitles[idx]] = it; });
apiCheck.get({ action: 'query', titles: requestTitles, converttitles: 1 })
.done(function (data) {
// 使用 helper 将响应映射回原始请求
handleQueryResponse(requestTitles, data, function (reqTitle, pg) {
var itemName = requestMap[reqTitle];
if (pg && pg.invalid) {
// invalid 或 interwiki
this._qrp_templateStatusMap[itemName] = 'invalid';
return;
}
if (pg === undefined) {
// 未返回对应 page,则视为 missing
this._qrp_templateStatusMap[itemName] = 'missing';
return;
}
var exists = (pg.missing === undefined);
this._qrp_templateStatusMap[itemName] = exists ? 'exists' : 'missing';
}.bind(this));
// 对仍处于 pending 的条目标为 missing
toQuery.forEach(function (it) { if (this._qrp_templateStatusMap[it] === 'pending') this._qrp_templateStatusMap[it] = 'missing'; }, this);
renderTemplates();
}.bind(this)).fail(function () { toQuery.forEach(function (it) { this._qrp_templateStatusMap[it] = 'fail'; }, this); renderTemplates(); }.bind(this));
}
}).bind(this));
// target input handler
this.targetInput.$input.off('input.qrp').on('input.qrp', (function () {
var raw = this.targetInput.getValue();
var lines = raw.split(/\r?\n/).map(function (s) { return s.trim(); }).filter(Boolean);
var newItems = lines;
var removed = this._qrp_targetItems.filter(function (it) { return newItems.indexOf(it) === -1; });
removed.forEach(function (it) { delete this._qrp_targetStatusMap[it]; }, this);
this._qrp_targetItems = newItems.slice();
var toQuery = [];
newItems.forEach(function (item) { if (!this._qrp_targetStatusMap.hasOwnProperty(item) || this._qrp_targetStatusMap[item] === 'pending') { this._qrp_targetStatusMap[item] = 'pending'; toQuery.push(item); } }, this);
if (this._qrp_targetComposing) return;
renderTargets();
if (toQuery.length > 0) {
var requestTitles = toQuery.slice(); // 页面名原样请求
var requestMap = {};
toQuery.forEach(function (it, idx) { requestMap[requestTitles[idx]] = it; });
apiCheck.get({ action: 'query', titles: requestTitles, converttitles: 1 })
.done(function (data) {
handleQueryResponse(requestTitles, data, function (reqTitle, pg) {
var itemName = requestMap[reqTitle];
if (pg === undefined) {
// 未返回对应 page,则视为 missing
this._qrp_targetStatusMap[itemName] = 'missing';
return;
}
// 如果被标为 invalid(bad title / interwiki),视为不可创建(失败)
if (pg && pg.invalid) { this._qrp_targetStatusMap[itemName] = 'invalid'; return; }
var exists = (pg.missing === undefined);
this._qrp_targetStatusMap[itemName] = exists ? 'exists' : 'missing';
}.bind(this));
toQuery.forEach(function (it) { if (this._qrp_targetStatusMap[it] === 'pending') this._qrp_targetStatusMap[it] = 'missing'; }, this);
renderTargets();
}.bind(this)).fail(function () { toQuery.forEach(function (it) { this._qrp_targetStatusMap[it] = 'fail'; }, this); renderTargets(); }.bind(this));
}
}).bind(this));
panel.$element.append(fieldset.$element);
this.$body.append(panel.$element);
try { this.$body.css({ 'max-height': '60vh', 'overflow': 'auto' }); } catch (e) { }
};
RedirectDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'cancel') { return new OO.ui.Process(function () { dialog.close(); }); }
if (action === 'create') {
return new OO.ui.Process().next(function () {
var rawTitles = dialog.targetInput.getValue();
var titles = rawTitles.split(/\r?\n/).map(function (s) { return s.trim(); }).filter(Boolean);
if (!titles.length) { mw.notify('请填写至少一个目标页面名称(每行一个)。', { type: 'error' }); return; }
var current = mw.config.get('wgPageName').replace(/_/g, ' ');
var section = dialog.sectionInput.getValue().trim(); current = section ? current + '#' + section : current;
var raw = dialog.templateInput.getValue().trim();
// 过滤模板:如果模板被检测为 invalid(在 statusMap 中为 'invalid'),则跳过该模板并提示
var rawForUse = '';
if (raw) {
var tplItemsTmp = raw.split(/\s+/).filter(Boolean);
var usedTplsTmp = [];
tplItemsTmp.forEach(function (it) {
if (dialog._qrp_templateStatusMap[it] === 'fail' || dialog._qrp_templateStatusMap[it] === 'invalid') {
mw.notify('跳过不可用的模板:' + it, { type: 'error' });
} else {
usedTplsTmp.push(it);
}
});
rawForUse = usedTplsTmp.join(' ');
}
raw = rawForUse; // 接下来脚本内部关于 raw 的处理会使用过滤后的值,避免把不可用模板写入页面
var summary = dialog.summaryInput.getValue().trim(); summary = summary ? summary + ' // ' : summary;
var allowedTitles = [];
var skipped = [];
titles.forEach(function (t) {
var st = dialog._qrp_targetStatusMap[t];
if (st === 'fail' || st === 'invalid') { skipped.push({title: t, reason: 'invalid_or_interwiki'}); }
else if (st === 'exists') { skipped.push({title: t, reason: 'exists'}); }
else { allowedTitles.push(t); }
});
if (skipped.length) {
skipped.forEach(function (it) {
if (it.reason === 'exists') mw.notify('该页面已经存在:' + it.title, { type: 'error' });
else mw.notify('跳过无效或跨站点链接(interwiki)页面:' + it.title, { type: 'error' });
});
}
if (!allowedTitles.length) { mw.notify('没有可创建的目标页面(已全部跳过)。', { type: 'error' }); return; }
var chain = $.Deferred().resolve().promise();
allowedTitles.forEach(function (title) {
chain = chain.then(function () {
var lines = ['#REDIRECT [[' + current + ']]']; if (raw) { raw.split(/\s+/).filter(Boolean).forEach(function (item) { if (lines.length === 1) lines.push('', '{{Redirect category shell|'); lines.push('{{' + item + '重定向}}'); }); if (lines.length > 2) lines.push('}}'); }
var d = $.Deferred(); api.post({ action: 'edit', title: title, text: lines.join('\n'), summary: summary + '[[User:魔琴/gadgets/quickredirectplus|QR+快速重定向]]到[[' + current + ']]', createonly: true, token: mw.user.tokens.get('csrfToken') })
.done(function () { mw.notify('重定向创建成功:' + title, { type: 'success' }); d.resolve(); })
.fail(function (jqXHR, textStatus, errorThrown) { var code = jqXHR && jqXHR.responseJSON && jqXHR.responseJSON.error && jqXHR.responseJSON.error.code; if (code === 'articleexists') { mw.notify('该页面已经存在:' + title, { type: 'error' }); } else { mw.notify('错误(' + title + '):' + (code || errorThrown.error.code + ': ' + errorThrown.error.info || textStatus || '未知错误'), { type: 'error' }); } d.resolve(); });
return d.promise();
});
});
return chain.then(function () { dialog.close(); });
});
}
return RedirectDialog.parent.prototype.getActionProcess.apply(this, arguments);
};
// 将 WM 与 Dialog 设为单例,避免多次创建并支持复用
var qrPlusWindowManager = null;
var qrPlusDialog = null;
function openRedirectDialog() {
if (!qrPlusWindowManager) {
qrPlusWindowManager = new OO.ui.WindowManager();
$('body').append(qrPlusWindowManager.$element);
}
if (!qrPlusDialog) {
qrPlusDialog = new RedirectDialog();
qrPlusWindowManager.addWindows([qrPlusDialog]);
// 初始化时暴露并绑定 close 清理 current reference
try {
window.QRPlus_currentDialog = qrPlusDialog;
qrPlusDialog.on('close', function () {
try { if (window.QRPlus_currentDialog === qrPlusDialog) window.QRPlus_currentDialog = null; } catch (e) {}
});
} catch (e) { /* ignore */ }
}
// 如果 dialog 已存在,无论是否曾经关闭过,都直接打开(复用实例)
qrPlusWindowManager.openWindow(qrPlusDialog);
// 再次确保 current reference(在 dialog 再次打开时设置)
try {
window.QRPlus_currentDialog = qrPlusDialog;
} catch (e) { /* ignore */ }
}
function showRedirectButton() { var button = new OO.ui.ButtonWidget({ label: 'QR+快速创建重定向', icon: 'articleRedirect' }); button.on('click', openRedirectDialog); $('#siteSub').append($('<div>').append(button.$element)); }
//
// === 新增整合:注入 ToolsRedirect 按钮并处理事件传递(可选增强)
//
(function () {
// 辅助:从 ToolsRedirect 的对话框容器收集已勾选的页面标题(优先 data('page-title'))
function collectCheckedFromToolsRedirectContainer($container) {
var titles = [];
$container.find('input[type=checkbox]:checked').each(function () {
var dt = $(this).data && $(this).data('page-title');
if (!dt) {
var $row = $(this).closest('p,dd,li,div');
dt = $row.find('a[title]').first().attr('title') || $row.text();
}
if (dt && typeof dt === 'string') {
titles.push(dt.replace(/_/g, ' ').trim());
}
});
// fallback: 若容器中没有 checkbox,则尝试查找带 .new class 的新建链接
if (!titles.length) {
$container.find('a.new[title]').each(function () {
var t = $(this).attr('title') || $(this).text();
if (t) titles.push(t.replace(/_/g, ' ').trim());
});
}
// 去重并保留顺序
var seen = {};
return titles.filter(function (t) { if (seen[t]) return false; seen[t] = true; return true; });
}
// 把 titles 合并到 dialog 的 targetInput
function importIntoDialog(dialog, titles) {
if (!dialog || !titles || !titles.length) return 0;
try {
var targetInput = dialog.targetInput;
if (!targetInput || typeof targetInput.getValue !== 'function' || typeof targetInput.setValue !== 'function') return 0;
var cur = (targetInput.getValue() || '').split(/\r?\n/).map(function (s) { return s.trim(); }).filter(Boolean);
var added = 0;
titles.forEach(function (t) { if (cur.indexOf(t) === -1) { cur.push(t); added++; } });
if (added) {
targetInput.setValue(cur.join('\n'));
// 触发输入事件以触发内部处理/渲染
try { targetInput.$input.trigger('input.qrp'); } catch (e) {}
// 更新对话框尺寸(如果有该方法)
try { if (typeof dialog.updateSize === 'function') dialog.updateSize(); } catch (e) {}
// 把光标聚焦到输入框,便于用户立即看到并编辑
try { targetInput.$input.focus(); } catch (e) {}
}
return added;
} catch (e) {
return 0;
}
}
// 处理来自 ToolsRedirect 的自定义事件(或按钮直接 dispatch)
function onImportEvent(ev) {
var titles = (ev && ev.detail && Array.isArray(ev.detail)) ? ev.detail : [];
// 若当前 dialog 已打开并可见,直接导入
if (window.QRPlus_currentDialog && window.QRPlus_currentDialog.targetInput && window.QRPlus_currentDialog.$element && window.QRPlus_currentDialog.$element.is(':visible')) {
var addedNow = importIntoDialog(window.QRPlus_currentDialog, titles);
if (addedNow) mw.notify('已自动导入 ' + addedNow + ' 条候选到 QR+。', { type: 'success' });
else mw.notify('未导入新候选(可能已存在或无候选)。', { type: 'info' });
return;
}
// 否则保存为 pending 并打开 QR+;openRedirectDialog 会创建或复用 dialog
window.QRPlus_pendingImport = titles || [];
openRedirectDialog();
// 更稳健的等待逻辑:
// - 等待 QR+ 实例存在并且内部 targetInput 已准备好且对话框可见
// - 最多等待 5 秒(5000ms),检测间隔 80ms
var waited = 0, interval = 80, timeout = 5000;
var poll = setInterval(function () {
var dialog = window.QRPlus_currentDialog || qrPlusDialog;
var canImport = false;
try {
if (dialog && dialog.targetInput && typeof dialog.targetInput.setValue === 'function') {
// 要求对话框元素在 DOM 中且可见(避免在未 attach 或不可见时设置值)
if (dialog.$element && dialog.$element.is(':visible')) {
canImport = true;
} else {
// 额外判断:如果 WindowManager 有 currentWindow 属性并且等于 dialog
try {
if (qrPlusWindowManager && qrPlusWindowManager.currentWindow && qrPlusWindowManager.currentWindow === dialog) {
canImport = true;
}
} catch (e) {}
}
}
} catch (e) { canImport = false; }
if (canImport) {
clearInterval(poll);
var added = importIntoDialog(dialog, window.QRPlus_pendingImport || []);
if (added) mw.notify('已自动导入 ' + added + ' 条候选到 QR+。', { type: 'success' });
else mw.notify('未导入新候选(可能已存在或无候选)。', { type: 'info' });
window.QRPlus_pendingImport = null;
return;
}
waited += interval;
if (waited >= timeout) {
clearInterval(poll);
// 超时仍未就绪:保留 pendingImport(用户手动打开 QR+ 时会触发导入逻辑)
mw.notify('等待 QR+ 就绪超时,请手动打开 QR+(或稍后重试导入)。', { type: 'warn' });
}
}, interval);
}
// 监听自定义事件(从 ToolsRedirect 按钮或其它脚本派发)
window.addEventListener('QRPlus.ImportFromToolsRedirect', onImportEvent);
// MutationObserver 注入按钮到 ToolsRedirect 对话框
var INJECTED_FLAG = 'qrplus-button-injected';
function injectButtonIntoDialog($dlg) {
if ($dlg.data(INJECTED_FLAG)) return;
var $tabArea = $dlg.find('.tab-redirect').first();
if (!$tabArea.length) $tabArea = $dlg; // 回退
var $btn = $('<button type="button" class="mw-ui-button mw-ui-progressive">')
.text('在 QR+ 打开并导入(勾选项)')
.css({ 'margin-left': '8px', 'margin-bottom': '6px' });
$btn.on('click', function (e) {
e.preventDefault();
var selected = collectCheckedFromToolsRedirectContainer($dlg);
// 先关闭 ToolsRedirect 的对话框,避免同时存在两个模态窗口导致界面完全被锁定
try {
if ($dlg && typeof $dlg.dialog === 'function') {
$dlg.dialog('close');
}
} catch (err) {
// ignore
}
// 等待 dialog 完全关闭,再打开 QR+ 并派发事件(短延迟确保 UI 状态恢复)
setTimeout(function () {
try {
window.dispatchEvent(new CustomEvent('QRPlus.ImportFromToolsRedirect', { detail: selected }));
} catch (err) {
// IE fallback for CustomEvent
try {
var ev = document.createEvent('CustomEvent');
ev.initCustomEvent('QRPlus.ImportFromToolsRedirect', true, true, selected);
window.dispatchEvent(ev);
} catch (e2) {
// 最后退回:直接调用 openRedirectDialog 并把 titles 放到 pending
window.QRPlus_pendingImport = selected;
openRedirectDialog();
}
}
mw.notify('已向 QR+ 发送 ' + (selected.length || 0) + ' 条已勾选候选(若 QR+ 支持自动导入,将自动填入)。', { type: 'info' });
}, 120);
});
$tabArea.prepend($btn);
$dlg.data(INJECTED_FLAG, true);
}
// 观察 DOM,注入到新出现的 dialog-redirect
var mo = new MutationObserver(function (mutations) {
mutations.forEach(function (m) {
(m.addedNodes || []).forEach(function (node) {
if (node.nodeType !== 1) return;
var $n = $(node);
if ($n.hasClass('dialog-redirect')) {
injectButtonIntoDialog($n);
} else {
var $found = $n.find('.dialog-redirect');
if ($found.length) $found.each(function () { injectButtonIntoDialog($(this)); });
}
});
});
});
mo.observe(document.body, { childList: true, subtree: true });
// 如果 dialog 已存在(脚本稍后加载),立即注入
$(function () {
$('.dialog-redirect').each(function () { injectButtonIntoDialog($(this)); });
});
})();
//
// === 结束新增整合段落
//
$(function () {
mw.loader.using(['mediawiki.util', 'mediawiki.api', 'ext.gadget.site-lib'/* ,'ext.gadget.HanAssist'*/, 'oojs-ui-windows'], function () {
var count = 0;
var alwaysShow = mw.config.get('alwaysShowQuickRedirectPlus') || (window.alwaysShowQuickRedirectPlus === true);
if (mw.config.get('wgAction') === 'view' && !mw.config.get('wgIsRedirect')) {
mw.util.addPortletLink('p-cactions', '#', 'QR+快速创建重定向', 'ca-quickredirectplus', '使用快速对话框创建重定向', null, null);
$('#ca-quickredirectplus').click(function (e) { if (count === 0) { showRedirectButton(); count++; } openRedirectDialog(); });
// 如果全域标志为真,页面加载时立即显示大按钮
if (alwaysShow) { showRedirectButton(); count++; }
}
});
});
})();
// </nowiki>