MediaWiki:Partner.js
来自卡厄思梦境WIKI
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
/**
* MediaWiki伙伴管理器
* 用于创建、编辑和管理伙伴页面
*/
(function() {
'use strict';
// 只在MediaWiki:Partner页面运行
if (mw.config.get('wgPageName') !== 'MediaWiki:Partner') {
return;
}
var PartnerManager = {
api: new mw.Api(),
currentPartner: null,
partnerList: [],
// 模板字段定义
templateFields: {
basic: [
{ name: 'id', label: 'ID', type: 'text' },
{ name: '名称', label: '名称', type: 'text' },
{
name: '稀有度',
label: '稀有度',
type: 'select',
options: ['', '5', '4']
},
{
name: '职业',
label: '职业',
type: 'select',
options: ['', '决斗家', '先锋', '游侠', '猎人', '心灵术士', '控制师']
},
{ name: '实装日期', label: '实装日期', type: 'text' }
],
image: [
{ name: 'size', label: 'Size', type: 'text' },
{ name: 'right', label: 'Right', type: 'text' },
{ name: 'bottom', label: 'Bottom', type: 'text' }
],
voice: [
{ name: '声优_韩', label: '韩语声优', type: 'text' },
{ name: '声优_日', label: '日语声优', type: 'text' },
{ name: '声优_英', label: '英语声优', type: 'text' },
{ name: '声优_中', label: '中文声优', type: 'text' }
],
profile: [
{ name: '种族', label: '种族', type: 'text' },
{
name: '性别',
label: '性别',
type: 'select',
options: ['', '男', '女']
},
{ name: '生日', label: '生日', type: 'text' },
{ name: '异能', label: '异能', type: 'text' },
{
name: '所属势力',
label: '所属势力',
type: 'text',
toolbar: [
{ label: '换行', action: 'insertText', text: '<br>' }
]
},
{
name: '档案',
label: '档案',
type: 'textarea',
toolbar: [
{ label: '加粗', action: 'wrapSelection', before: '<b>', after: '</b>' },
{ label: '换行', action: 'insertText', text: '<br>' }
]
}
],
skills: [
{ name: '被动', label: '被动技能', type: 'text' },
{ name: '被动_描述', label: '被动描述', type: 'textarea' },
{ name: '自我意识技能', label: '自我意识技能', type: 'text' },
{ name: '自我意识技能_消耗', label: '自我意识技能消耗', type: 'text' },
{
name: '自我意识技能_描述',
label: '自我意识技能描述',
type: 'textarea',
toolbar: [
{ label: '换行', action: 'insertText', text: '<br>' },
{ label: '蓝色文本', action: 'wrapSelection', before: '{{文本|蓝|', after: '}}' },
{ label: '词典', action: 'wrapSelection', before: '{{词典|', after: '}}' }
]
}
],
stats: [
{ name: '攻击力_1级', label: '攻击力(1级)', type: 'text' },
{ name: '攻击力_成长', label: '攻击力成长', type: 'text' },
{ name: '防御力_1级', label: '防御力(1级)', type: 'text' },
{ name: '防御力_成长', label: '防御力成长', type: 'text' },
{ name: 'HP_1级', label: 'HP(1级)', type: 'text' },
{ name: 'HP_成长', label: 'HP成长', type: 'text' }
]
},
// 初始化
init: function() {
this.createUI();
this.loadPartnerList();
},
// 创建UI
createUI: function() {
var container = document.createElement('div');
container.className = 'agent-manager';
container.innerHTML = `
<div class="agent-section">
<h3>伙伴管理器</h3>
<div id="partner-message"></div>
<div class="agent-tabs">
<div class="agent-tab active" data-tab="list">伙伴列表</div>
<div class="agent-tab" data-tab="edit">编辑/新建</div>
<div class="agent-tab" data-tab="batch">批量操作</div>
</div>
<div id="tab-list" class="agent-tab-content active">
<div class="agent-button" id="refresh-list">刷新列表</div>
<div class="agent-button" id="create-new">新建伙伴</div>
<div id="partner-list" class="agent-loading">加载中...</div>
</div>
<div id="tab-edit" class="agent-tab-content">
<div id="editor-container"></div>
</div>
<div id="tab-batch" class="agent-tab-content">
<div class="agent-form-group">
<label>批量操作</label>
<div class="agent-button" id="export-all">导出所有伙伴</div>
<div class="agent-button secondary" id="check-templates">检查模板完整性</div>
</div>
<div id="batch-result"></div>
</div>
</div>
`;
var content = document.getElementById('mw-content-text');
content.innerHTML = '';
content.appendChild(container);
this.bindEvents();
},
// 绑定事件
bindEvents: function() {
var self = this;
// 标签切换
document.querySelectorAll('.agent-tab').forEach(function(tab) {
tab.addEventListener('click', function() {
var tabName = this.getAttribute('data-tab');
self.switchTab(tabName);
});
});
// 刷新列表
document.getElementById('refresh-list').addEventListener('click', function() {
self.loadPartnerList();
});
// 新建伙伴
document.getElementById('create-new').addEventListener('click', function() {
self.createNewPartner();
});
// 导出所有
document.getElementById('export-all').addEventListener('click', function() {
self.exportAll();
});
// 检查模板
document.getElementById('check-templates').addEventListener('click', function() {
self.checkTemplates();
});
},
// 切换标签
switchTab: function(tabName) {
document.querySelectorAll('.agent-tab').forEach(function(tab) {
tab.classList.remove('active');
});
document.querySelectorAll('.agent-tab-content').forEach(function(content) {
content.classList.remove('active');
});
document.querySelector('[data-tab="' + tabName + '"]').classList.add('active');
document.getElementById('tab-' + tabName).classList.add('active');
},
// 加载伙伴列表
loadPartnerList: function() {
var self = this;
var listContainer = document.getElementById('partner-list');
listContainer.innerHTML = '<div class="agent-loading">加载中...</div>';
this.api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:伙伴',
cmlimit: 500,
format: 'json'
}).done(function(data) {
self.partnerList = data.query.categorymembers;
self.renderPartnerList();
}).fail(function() {
self.showMessage('加载失败,请检查分类是否存在', 'error');
});
},
// 渲染伙伴列表
renderPartnerList: function() {
var listContainer = document.getElementById('partner-list');
if (this.partnerList.length === 0) {
listContainer.innerHTML = '<div class="agent-message info">暂无伙伴,点击"新建伙伴"开始创建</div>';
return;
}
var html = '<div class="agent-grid">';
this.partnerList.forEach(function(partner) {
html += '<div class="agent-list-item" data-title="' + mw.html.escape(partner.title) + '">' +
mw.html.escape(partner.title) + '</div>';
});
html += '</div>';
listContainer.innerHTML = html;
var self = this;
document.querySelectorAll('.agent-list-item').forEach(function(item) {
item.addEventListener('click', function() {
var title = this.getAttribute('data-title');
self.loadPartner(title);
});
});
},
// 加载伙伴数据
loadPartner: function(title) {
var self = this;
this.showMessage('加载中...', 'info');
this.api.get({
action: 'query',
titles: title,
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
format: 'json'
}).done(function(data) {
var pages = data.query.pages;
var pageId = Object.keys(pages)[0];
if (pageId === '-1') {
self.showMessage('页面不存在', 'error');
return;
}
var content = pages[pageId].revisions[0].slots.main['*'];
self.currentPartner = {
title: title,
content: content
};
self.switchTab('edit');
self.renderEditor();
self.showMessage('已加载: ' + title, 'success');
}).fail(function(error) {
console.error('加载失败:', error);
self.showMessage('加载失败', 'error');
});
},
// 创建新伙伴
createNewPartner: function() {
this.currentPartner = {
title: '',
content: this.generateTemplate({})
};
this.switchTab('edit');
this.renderEditor();
this.showMessage('请填写伙伴信息', 'info');
},
// 渲染编辑器
renderEditor: function() {
var self = this;
var container = document.getElementById('editor-container');
var data = this.parseTemplate(this.currentPartner.content);
var html = '<div class="agent-form-group">';
html += '<label>页面标题</label>';
html += '<input type="text" class="agent-input" id="page-title" value="' +
mw.html.escape(this.currentPartner.title || '') + '" placeholder="例如: 伙伴/李四">';
html += '</div>';
// 基础信息
html += '<h4>基础信息</h4>';
html += '<div class="agent-field-row">';
this.templateFields.basic.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'basic');
});
html += '</div>';
// 图片位置
html += '<h4>图片位置</h4>';
html += '<div class="agent-field-row">';
this.templateFields.image.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'image');
});
html += '</div>';
// 声优信息
html += '<h4>声优信息</h4>';
html += '<div class="agent-field-row">';
this.templateFields.voice.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'voice');
});
html += '</div>';
// 角色档案
html += '<h4>角色档案</h4>';
this.templateFields.profile.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'profile');
});
// 技能信息
html += '<h4>技能信息</h4>';
this.templateFields.skills.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'skills');
});
// 属性数值
html += '<h4>属性数值</h4>';
html += '<div class="agent-field-row">';
this.templateFields.stats.forEach(function(field) {
html += self.renderFieldWithUniqueId(field, data[field.name] || '', 'stats');
});
html += '</div>';
// 操作按钮
html += '<div style="margin-top: 20px;">';
html += '<div class="agent-button" id="save-partner">保存伙伴</div>';
html += '<div class="agent-button secondary" id="preview-partner">预览</div>';
html += '<div class="agent-button secondary" id="view-source">查看源码</div>';
if (this.currentPartner.title) {
html += '<div class="agent-button secondary" id="view-page">查看页面</div>';
}
html += '</div>';
html += '<div id="preview-container" style="margin-top: 20px;"></div>';
container.innerHTML = html;
// 绑定工具栏事件
this.bindToolbarEvents();
// 绑定保存事件
document.getElementById('save-partner').addEventListener('click', function() {
self.savePartner();
});
// 绑定预览事件
document.getElementById('preview-partner').addEventListener('click', function() {
self.previewPartner();
});
// 绑定查看源码事件
document.getElementById('view-source').addEventListener('click', function() {
self.viewSource();
});
// 绑定查看页面事件
if (this.currentPartner.title) {
document.getElementById('view-page').addEventListener('click', function() {
window.open(mw.util.getUrl(self.currentPartner.title), '_blank');
});
}
},
// 使用唯一ID渲染字段
renderFieldWithUniqueId: function(field, value, groupName) {
var escapedValue = mw.html.escape(String(value));
// 使用分组名和字段名生成唯一ID
var fieldId = 'field-' + groupName + '-' + encodeURIComponent(field.name).replace(/%/g, '_');
var html = '<div class="agent-form-group">';
html += '<label>' + mw.html.escape(field.label) + '</label>';
// 渲染工具栏
if (field.toolbar && field.toolbar.length > 0) {
html += '<div class="editor-toolbar" data-target="' + fieldId + '">';
field.toolbar.forEach(function(button, index) {
html += '<span class="toolbar-button" data-action="' + button.action + '" ';
if (button.text) {
html += 'data-text="' + mw.html.escape(button.text) + '" ';
}
if (button.before) {
html += 'data-before="' + mw.html.escape(button.before) + '" ';
}
if (button.after) {
html += 'data-after="' + mw.html.escape(button.after) + '" ';
}
html += '>' + mw.html.escape(button.label) + '</span>';
});
html += '</div>';
}
if (field.type === 'select') {
html += '<select class="agent-select" id="' + fieldId + '" data-field="' + mw.html.escape(field.name) + '">';
field.options.forEach(function(option) {
var selected = (value === option) ? ' selected' : '';
html += '<option value="' + mw.html.escape(option) + '"' + selected + '>' +
mw.html.escape(option || '请选择') + '</option>';
});
html += '</select>';
} else if (field.type === 'textarea') {
html += '<textarea class="agent-textarea" id="' + fieldId + '" data-field="' +
mw.html.escape(field.name) + '">' + escapedValue + '</textarea>';
} else {
html += '<input type="text" class="agent-input" id="' + fieldId + '" data-field="' +
mw.html.escape(field.name) + '" value="' + escapedValue + '">';
}
html += '</div>';
return html;
},
// 绑定工具栏事件
bindToolbarEvents: function() {
var self = this;
document.querySelectorAll('.toolbar-button').forEach(function(button) {
button.addEventListener('click', function() {
var toolbar = this.parentElement;
var targetId = toolbar.getAttribute('data-target');
var targetElement = document.getElementById(targetId);
if (!targetElement) {
console.error('找不到目标元素:', targetId);
return;
}
var action = this.getAttribute('data-action');
if (action === 'insertText') {
var text = self.unescapeHtml(this.getAttribute('data-text'));
self.insertTextAtCursor(targetElement, text);
} else if (action === 'wrapSelection') {
var before = self.unescapeHtml(this.getAttribute('data-before'));
var after = self.unescapeHtml(this.getAttribute('data-after'));
self.wrapSelection(targetElement, before, after);
}
targetElement.focus();
});
});
},
// 在光标位置插入文本
insertTextAtCursor: function(element, text) {
var startPos = element.selectionStart;
var endPos = element.selectionEnd;
var scrollTop = element.scrollTop;
var value = element.value;
element.value = value.substring(0, startPos) + text + value.substring(endPos);
// 设置光标位置
element.selectionStart = element.selectionEnd = startPos + text.length;
element.scrollTop = scrollTop;
// 触发 change 事件
var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
},
// 包裹选中文本
wrapSelection: function(element, before, after) {
var startPos = element.selectionStart;
var endPos = element.selectionEnd;
var scrollTop = element.scrollTop;
var value = element.value;
var selectedText = value.substring(startPos, endPos);
// 如果没有选中文本,使用默认文本
if (selectedText === '') {
selectedText = '请选择文本';
}
var replacement = before + selectedText + after;
element.value = value.substring(0, startPos) + replacement + value.substring(endPos);
// 选中替换后的内容
element.selectionStart = startPos;
element.selectionEnd = startPos + replacement.length;
element.scrollTop = scrollTop;
// 触发 change 事件
var event = new Event('input', { bubbles: true });
element.dispatchEvent(event);
},
// 反转义 HTML 实体
unescapeHtml: function(text) {
var textarea = document.createElement('textarea');
textarea.innerHTML = text;
return textarea.value;
},
// 解析模板
parseTemplate: function(content) {
var data = {};
// 查找伙伴模板
var templateStart = content.indexOf('{{伙伴');
var templateEnd = content.lastIndexOf('}}');
if (templateStart === -1 || templateEnd === -1) {
return data;
}
// 提取模板内容
var templateContent = content.substring(templateStart, templateEnd + 2);
// 移除开头的 {{伙伴 和结尾的 }}
templateContent = templateContent.replace(/^\{\{伙伴\s*/, '').replace(/\s*\}\}$/, '');
// 按行分割
var lines = templateContent.split('\n');
for (var i = 0; i < lines.length; i++) {
var line = lines[i].trim();
// 跳过空行和只有|的行
if (!line || line === '|') {
continue;
}
// 移除开头的 |
if (line.charAt(0) === '|') {
line = line.substring(1);
}
// 查找第一个 = 的位置
var equalPos = line.indexOf('=');
if (equalPos > 0) {
var key = line.substring(0, equalPos).trim();
var value = line.substring(equalPos + 1).trim();
if (key) {
data[key] = value;
}
}
}
return data;
},
// 生成模板(只输出非空字段)
generateTemplate: function(data) {
var self = this;
var template = '{{面包屑|伙伴图鉴}}\n{{伙伴\n';
// 辅助函数:添加字段(只添加非空字段)
var addFields = function(fields) {
var hasContent = false;
var output = '';
fields.forEach(function(field) {
var value = data[field.name] || '';
// 只有当值不为空时才输出该字段
if (value.trim() !== '') {
output += '|' + field.name + '=' + value + '\n';
hasContent = true;
}
});
if (hasContent) {
output += '\n';
}
return output;
};
// 基础信息
template += addFields(this.templateFields.basic);
// 图片位置
template += addFields(this.templateFields.image);
// 声优信息
template += addFields(this.templateFields.voice);
// 角色档案
template += addFields(this.templateFields.profile);
// 技能信息
template += addFields(this.templateFields.skills);
// 属性数值
template += addFields(this.templateFields.stats);
// 移除末尾多余的空行
template = template.replace(/\n+$/, '\n');
template += '}}\n[[分类:伙伴]]';
return template;
},
// 收集表单数据
collectFormData: function() {
var data = {};
document.querySelectorAll('[data-field]').forEach(function(input) {
var field = input.getAttribute('data-field');
data[field] = input.value;
});
return data;
},
// 保存伙伴
savePartner: function() {
var self = this;
var title = document.getElementById('page-title').value.trim();
if (!title) {
this.showMessage('请输入页面标题', 'error');
return;
}
var data = this.collectFormData();
var content = this.generateTemplate(data);
this.showMessage('保存中...', 'info');
this.api.postWithToken('csrf', {
action: 'edit',
title: title,
text: content,
summary: '通过伙伴管理器更新',
format: 'json'
}).done(function() {
self.showMessage('保存成功!', 'success');
self.currentPartner.title = title;
self.currentPartner.content = content;
self.loadPartnerList();
}).fail(function(code, error) {
console.error('保存失败:', code, error);
self.showMessage('保存失败: ' + (error.error ? error.error.info : '未知错误'), 'error');
});
},
// 预览
previewPartner: function() {
var data = this.collectFormData();
var content = this.generateTemplate(data);
var preview = document.getElementById('preview-container');
preview.innerHTML = '<h4>预览</h4><pre style="background: #f5f5f5; padding: 10px; overflow-x: auto; white-space: pre-wrap; word-wrap: break-word;">' +
mw.html.escape(content) + '</pre>';
},
// 查看源码
viewSource: function() {
var data = this.collectFormData();
var content = this.generateTemplate(data);
var preview = document.getElementById('preview-container');
preview.innerHTML = '<h4>Wiki源码</h4>' +
'<textarea class="agent-textarea" style="min-height: 400px;">' +
mw.html.escape(content) + '</textarea>';
},
// 导出所有
exportAll: function() {
var self = this;
var result = document.getElementById('batch-result');
result.innerHTML = '<div class="agent-loading">导出中...</div>';
var exportData = [];
var promises = [];
this.partnerList.forEach(function(partner) {
var promise = self.api.get({
action: 'query',
titles: partner.title,
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
format: 'json'
}).then(function(data) {
var pages = data.query.pages;
var pageId = Object.keys(pages)[0];
if (pageId !== '-1') {
var content = pages[pageId].revisions[0].slots.main['*'];
exportData.push({
title: partner.title,
content: content
});
}
});
promises.push(promise);
});
Promise.all(promises).then(function() {
var json = JSON.stringify(exportData, null, 2);
result.innerHTML = '<h4>导出结果 (' + exportData.length + '个伙伴)</h4>' +
'<textarea class="agent-textarea" style="min-height: 400px;">' +
mw.html.escape(json) + '</textarea>';
});
},
// 检查模板完整性
checkTemplates: function() {
var self = this;
var result = document.getElementById('batch-result');
result.innerHTML = '<div class="agent-loading">检查中...</div>';
var issues = [];
var promises = [];
this.partnerList.forEach(function(partner) {
var promise = self.api.get({
action: 'query',
titles: partner.title,
prop: 'revisions',
rvprop: 'content',
rvslots: 'main',
format: 'json'
}).then(function(data) {
var pages = data.query.pages;
var pageId = Object.keys(pages)[0];
if (pageId !== '-1') {
var content = pages[pageId].revisions[0].slots.main['*'];
var parsedData = self.parseTemplate(content);
var missingFields = [];
// 只检查关键字段
var requiredFields = [
{ name: 'id', label: 'ID' },
{ name: '名称', label: '名称' },
{ name: '稀有度', label: '稀有度' },
{ name: '职业', label: '职业' }
];
requiredFields.forEach(function(field) {
if (!parsedData[field.name] || parsedData[field.name].trim() === '') {
missingFields.push(field.label);
}
});
if (missingFields.length > 0) {
issues.push({
title: partner.title,
missing: missingFields
});
}
}
});
promises.push(promise);
});
Promise.all(promises).then(function() {
var html = '<h4>模板完整性检查结果</h4>';
if (issues.length === 0) {
html += '<div class="agent-message success">所有伙伴模板完整!</div>';
} else {
html += '<div class="agent-message error">发现 ' + issues.length + ' 个页面存在问题:</div>';
html += '<div style="max-height: 400px; overflow-y: auto;">';
issues.forEach(function(issue) {
html += '<div style="margin: 10px 0; padding: 10px; background: white; border: 1px solid #ddd;">';
html += '<strong>' + mw.html.escape(issue.title) + '</strong><br>';
html += '缺失字段: ' + mw.html.escape(issue.missing.join(', '));
html += '</div>';
});
html += '</div>';
}
result.innerHTML = html;
});
},
// 显示消息
showMessage: function(message, type) {
var container = document.getElementById('partner-message');
container.innerHTML = '<div class="agent-message ' + type + '">' + mw.html.escape(message) + '</div>';
setTimeout(function() {
container.innerHTML = '';
}, 5000);
}
};
// 页面加载完成后初始化
$(function() {
PartnerManager.init();
});
})();