MediaWiki:Card.js
来自卡厄思梦境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';
// 加载CSS
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css');
// 卡牌管理器类
class CardManager {
constructor() {
this.currentFighter = '';
this.cards = {};
this.currentCard = null;
this.fighters = [];
// 默认数据
this.defaultData = {
order: [],
ego: ''
};
// 当前编辑的卡牌数据
this.cardData = {
name: '',
displayname: '',
art: '',
group: '',
rarity: '',
god: '',
ap: '',
type: '攻击',
dict: '',
desc_global: '',
sub: '',
isinspiration: false,
isgod_inspiration: false,
inspirations: [],
god_inspirations: {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
}
};
}
// 初始化
async init() {
await this.loadFighters();
this.render();
this.bindEvents();
}
// 加载战斗员列表
async loadFighters() {
try {
const api = new mw.Api();
const response = await api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:战斗员',
cmlimit: 500
});
this.fighters = response.query.categorymembers.map(page => page.title);
} catch (error) {
console.error('加载战斗员列表失败:', error);
this.fighters = [];
}
}
// 渲染主界面
render() {
const container = this.createDiv('card-manager');
// 左侧面板
const leftPanel = this.createDiv('card-input-panel');
leftPanel.appendChild(this.renderFighterSelect());
leftPanel.appendChild(this.renderDefaultInfo());
leftPanel.appendChild(this.renderCardInput());
// 中间面板
const middlePanel = this.createDiv('card-list-panel');
middlePanel.appendChild(this.renderCardLists());
// 右侧面板
const rightPanel = this.createDiv('card-preview-panel');
rightPanel.appendChild(this.renderPreview());
container.appendChild(leftPanel);
container.appendChild(middlePanel);
container.appendChild(rightPanel);
// 添加到页面
const content = document.getElementById('mw-content-text');
if (content) {
content.innerHTML = '';
content.appendChild(container);
}
}
// 创建div元素
createDiv(className, text = '') {
const div = document.createElement('div');
if (className) div.className = className;
if (text) div.textContent = text;
return div;
}
// 渲染战斗员选择
renderFighterSelect() {
const container = this.createDiv('form-group');
const label = this.createDiv('form-label', '当前战斗员');
container.appendChild(label);
const select = this.createCustomSelect(
this.fighters,
this.currentFighter,
(value) => {
this.currentFighter = value;
this.loadFighterData();
}
);
container.appendChild(select);
return container;
}
// 渲染默认信息区
renderDefaultInfo() {
const container = this.createDiv('default-info-section');
const title = this.createDiv('section-title', '默认信息');
container.appendChild(title);
// 卡牌顺序
const orderGroup = this.createDiv('form-group');
const orderLabel = this.createDiv('form-label', '卡牌顺序(card.order)');
const orderInput = this.createInput('text', this.defaultData.order.join(','), (value) => {
this.defaultData.order = value.split(',').map(s => s.trim()).filter(s => s);
this.updatePreview();
});
orderGroup.appendChild(orderLabel);
orderGroup.appendChild(orderInput);
container.appendChild(orderGroup);
// 属性
const egoGroup = this.createDiv('form-group');
const egoLabel = this.createDiv('form-label', '属性(ego)');
const egoInput = this.createInput('text', this.defaultData.ego, (value) => {
this.defaultData.ego = value;
this.updatePreview();
});
egoGroup.appendChild(egoLabel);
egoGroup.appendChild(egoInput);
container.appendChild(egoGroup);
return container;
}
// 渲染卡牌输入区
renderCardInput() {
const container = this.createDiv();
const title = this.createDiv('section-title', '卡牌数据');
container.appendChild(title);
// 卡牌名称
container.appendChild(this.createFormGroup('卡牌名称',
this.createInput('text', this.cardData.name, (value) => {
this.cardData.name = value;
this.updatePreview();
})
));
// 显示名称
container.appendChild(this.createFormGroup('显示名称(displayname)',
this.createInput('text', this.cardData.displayname, (value) => {
this.cardData.displayname = value;
this.updatePreview();
})
));
// 图片
container.appendChild(this.createFormGroup('图片(art)',
this.createInput('text', this.cardData.art, (value) => {
this.cardData.art = value;
this.updatePreview();
})
));
// 卡组
container.appendChild(this.createFormGroup('卡组(group)',
this.createGroupSelect()
));
// 稀有度
container.appendChild(this.createFormGroup('稀有度(rarity)',
this.createRaritySelect()
));
// 神明
container.appendChild(this.createFormGroup('神明',
this.createGodSelect()
));
// AP
container.appendChild(this.createFormGroup('AP',
this.createInput('text', this.cardData.ap, (value) => {
this.cardData.ap = value;
this.updatePreview();
})
));
// 卡牌类型
container.appendChild(this.createFormGroup('卡牌类型(type)',
this.createTypeSelect()
));
// 机制
container.appendChild(this.createFormGroup('机制(dict)',
this.createInput('text', this.cardData.dict, (value) => {
this.cardData.dict = value;
this.updatePreview();
})
));
// 描述
const descGroup = this.createDiv('form-group');
const descLabel = this.createDiv('form-label', '描述(desc_global)');
descGroup.appendChild(descLabel);
descGroup.appendChild(this.createFormatButtons());
descGroup.appendChild(this.createTextarea(this.cardData.desc_global, (value) => {
this.cardData.desc_global = value;
this.updatePreview();
}));
container.appendChild(descGroup);
// 衍生卡牌
container.appendChild(this.createFormGroup('衍生卡牌(sub)',
this.createInput('text', this.cardData.sub, (value) => {
this.cardData.sub = value;
this.updatePreview();
})
));
// 是否存在灵光一闪
container.appendChild(this.createFormGroup('是否存在灵光一闪',
this.createCheckbox(this.cardData.isinspiration, (checked) => {
this.cardData.isinspiration = checked;
this.updatePreview();
})
));
// 是否存在神光一闪
container.appendChild(this.createFormGroup('是否存在神光一闪',
this.createCheckbox(this.cardData.isgod_inspiration, (checked) => {
this.cardData.isgod_inspiration = checked;
this.updatePreview();
})
));
// 按钮组
const btnGroup = this.createDiv('btn-group');
const saveBtn = this.createDiv('btn btn-primary', '保存卡牌');
saveBtn.onclick = () => this.saveCard();
const newBtn = this.createDiv('btn btn-success', '新建卡牌');
newBtn.onclick = () => this.newCard();
btnGroup.appendChild(saveBtn);
btnGroup.appendChild(newBtn);
container.appendChild(btnGroup);
return container;
}
// 创建表单组
createFormGroup(label, input) {
const group = this.createDiv('form-group');
const labelDiv = this.createDiv('form-label', label);
group.appendChild(labelDiv);
group.appendChild(input);
return group;
}
// 创建自定义下拉框
createCustomSelect(options, selected, onChange) {
const container = this.createDiv('custom-select');
const display = this.createDiv('select-display', selected || '请选择...');
const arrow = this.createDiv('select-arrow', '▼');
display.appendChild(arrow);
const dropdown = this.createDiv('select-dropdown');
options.forEach(option => {
const optionDiv = this.createDiv('select-option', option);
if (option === selected) {
optionDiv.classList.add('selected');
}
optionDiv.onclick = () => {
display.childNodes[0].textContent = option;
dropdown.querySelectorAll('.select-option').forEach(o => o.classList.remove('selected'));
optionDiv.classList.add('selected');
dropdown.classList.remove('active');
onChange(option);
};
dropdown.appendChild(optionDiv);
});
display.onclick = () => {
dropdown.classList.toggle('active');
};
// 点击外部关闭
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) {
dropdown.classList.remove('active');
}
});
container.appendChild(display);
container.appendChild(dropdown);
return container;
}
// 创建输入框
createInput(type, value, onChange) {
const input = this.createDiv('custom-input');
input.contentEditable = true;
input.textContent = value;
input.oninput = () => onChange(input.textContent);
return input;
}
// 创建文本域
createTextarea(value, onChange) {
const textarea = this.createDiv('custom-textarea');
textarea.contentEditable = true;
textarea.innerHTML = value.replace(/<br>/g, '\n');
textarea.oninput = () => onChange(textarea.textContent);
return textarea;
}
// 创建格式化按钮
createFormatButtons() {
const container = this.createDiv('format-buttons');
const buttons = [
{ label: '蓝色文本', class: 'blue', template: '{{文本|蓝|%s}}' },
{ label: '绿色文本', class: 'green', template: '{{文本|绿|%s}}' },
{ label: '绿色描边', class: 'green', template: '{{描边|绿|%s}}' },
{ label: '词典', class: '', template: '{{词典|%s}}' },
{ label: '换行', class: '', template: '<br>' }
];
buttons.forEach(btn => {
const button = this.createDiv(`format-btn ${btn.class}`, btn.label);
button.onclick = () => {
const textarea = button.parentElement.nextElementSibling;
this.insertTemplate(textarea, btn.template);
};
container.appendChild(button);
});
return container;
}
// 插入模板
insertTemplate(textarea, template) {
const selection = window.getSelection();
const selectedText = selection.toString();
if (template === '<br>') {
const text = textarea.textContent;
const pos = this.getCaretPosition(textarea);
textarea.textContent = text.slice(0, pos) + '\n' + text.slice(pos);
} else {
const result = template.replace('%s', selectedText || '选择文字');
if (selectedText) {
document.execCommand('insertText', false, result);
} else {
const text = textarea.textContent;
const pos = this.getCaretPosition(textarea);
textarea.textContent = text.slice(0, pos) + result + text.slice(pos);
}
}
textarea.dispatchEvent(new Event('input'));
}
// 获取光标位置
getCaretPosition(element) {
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
const preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
return preCaretRange.toString().length;
}
return 0;
}
// 创建复选框
createCheckbox(checked, onChange) {
const container = this.createDiv('checkbox-group');
const checkbox = this.createDiv('custom-checkbox');
if (checked) checkbox.classList.add('checked');
const label = this.createDiv('', '是');
checkbox.onclick = () => {
checkbox.classList.toggle('checked');
const isChecked = checkbox.classList.contains('checked');
label.textContent = isChecked ? '是' : '否';
onChange(isChecked);
};
container.appendChild(checkbox);
container.appendChild(label);
return container;
}
// 创建卡组选择
createGroupSelect() {
const container = this.createDiv('group-options');
const groups = ['自我意识技能', '起始卡牌', '独特卡牌', '灵光一闪', '神光一闪'];
groups.forEach(group => {
const option = this.createDiv('group-option', group);
if (this.cardData.group === group) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.group-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.group = group;
this.updatePreview();
};
container.appendChild(option);
});
return container;
}
// 创建稀有度选择
createRaritySelect() {
const container = this.createDiv('rarity-options');
const rarities = ['白', '蓝', '橙', '彩'];
rarities.forEach(rarity => {
const option = this.createDiv('rarity-option', rarity);
if (this.cardData.rarity === rarity) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.rarity-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.rarity = rarity;
this.updatePreview();
};
container.appendChild(option);
});
return container;
}
// 创建神明选择
createGodSelect() {
const container = this.createDiv('god-options');
const gods = ['circen', 'diallos', 'nihilum', 'secred', 'vitor'];
gods.forEach(god => {
const option = this.createDiv('god-option', god);
if (this.cardData.god === god) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.god-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.god = god;
this.updatePreview();
};
container.appendChild(option);
});
return container;
}
// 创建类型选择
createTypeSelect() {
const container = this.createDiv('type-options');
const types = ['攻击', '技能', '强化'];
types.forEach(type => {
const option = this.createDiv('type-option', type);
if (this.cardData.type === type) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.type-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.type = type;
this.updatePreview();
};
container.appendChild(option);
});
return container;
}
// 渲染卡牌列表
renderCardLists() {
const container = this.createDiv();
// 卡牌列表
const cardListTitle = this.createDiv('panel-title', '卡牌列表');
container.appendChild(cardListTitle);
const cardListContainer = this.createDiv('list-container');
cardListContainer.id = 'card-list';
this.updateCardList(cardListContainer);
container.appendChild(cardListContainer);
return container;
}
// 更新卡牌列表
updateCardList(container) {
container.innerHTML = '';
if (Object.keys(this.cards).length === 0) {
container.appendChild(this.createDiv('list-empty', '暂无卡牌'));
return;
}
Object.keys(this.cards).forEach(name => {
const card = this.cards[name];
const item = this.createDiv('card-list-item');
const info = this.createDiv();
const nameDiv = this.createDiv('card-list-item-name', name);
const detailDiv = this.createDiv('card-list-item-info',
`${card.type || '攻击'} | AP:${card.ap || 0} | ${card.rarity || ''}`
);
info.appendChild(nameDiv);
info.appendChild(detailDiv);
const deleteBtn = this.createDiv('delete-btn', '删除');
deleteBtn.onclick = (e) => {
e.stopPropagation();
if (confirm(`确定要删除卡牌"${name}"吗?`)) {
delete this.cards[name];
this.updateCardList(container);
this.updatePreview();
// 如果删除的是当前编辑的卡牌,清空表单
if (this.currentCard === name) {
this.newCard();
}
}
};
item.appendChild(info);
item.appendChild(deleteBtn);
item.onclick = () => {
this.loadCard(name);
container.querySelectorAll('.card-list-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
};
container.appendChild(item);
});
}
// 渲染预览
renderPreview() {
const container = this.createDiv();
const title = this.createDiv('panel-title', 'Lua代码预览');
container.appendChild(title);
const preview = this.createDiv('preview-code');
preview.id = 'code-preview';
container.appendChild(preview);
this.updatePreview();
// 添加复制按钮
const copyBtn = this.createDiv('btn btn-primary', '复制代码');
copyBtn.style.marginTop = '10px';
copyBtn.onclick = () => this.copyCode();
container.appendChild(copyBtn);
return container;
}
// 更新预览
updatePreview() {
const preview = document.getElementById('code-preview');
if (!preview) return;
preview.textContent = this.generateLuaCode();
}
// 生成Lua代码
generateLuaCode() {
let code = 'local card = {}\n\n';
// 生成order
if (this.defaultData.order.length > 0) {
code += `card.order = { "${this.defaultData.order.join(',')}" }\n\n`;
}
// 生成info
code += 'card.info = {\n';
if (this.defaultData.ego) {
code += ` ego = "${this.defaultData.ego}",\n`;
}
code += '}\n\n';
// 生成各个卡牌
Object.keys(this.cards).forEach(name => {
const card = this.cards[name];
code += this.generateCardCode(name, card);
});
code += 'return card';
return code;
}
// 生成单个卡牌代码
generateCardCode(name, card) {
let code = `card["${name}"] = {\n`;
code += ' base = {\n';
// 基础属性
if (card.displayname) {
code += ` displayname = "${card.displayname}",\n`;
}
if (card.art) {
code += ` art = "${card.art}",\n`;
}
if (card.group) {
code += ` group = "${card.group}",\n`;
}
if (card.rarity) {
code += ` rarity = "${card.rarity}",\n`;
}
if (card.ap) {
code += ` ap = ${isNaN(card.ap) ? `"${card.ap}"` : card.ap},\n`;
}
if (card.type) {
code += ` type = "${card.type}",\n`;
}
if (card.dict) {
code += ` dict = "${card.dict}",\n`;
}
if (card.desc_global) {
const desc = card.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"');
code += ` desc_global = "${desc}",\n`;
}
if (card.sub) {
code += ` sub = "${card.sub}",\n`;
}
if (card.isinspiration) {
code += ` isinspiration = 1,\n`;
}
if (card.isgod_inspiration) {
code += ` isgod_inspiration = 1,\n`;
}
code += ' },\n';
// 变体数据
if (card.isinspiration || card.isgod_inspiration) {
code += ' var = {\n';
// 灵光一闪
if (card.isinspiration && card.inspirations && card.inspirations.length > 0) {
code += ' inspiration = {\n';
card.inspirations.forEach(insp => {
code += ' {\n';
if (insp.ap !== undefined) {
code += ` ap = ${isNaN(insp.ap) ? `"${insp.ap}"` : insp.ap},\n`;
}
if (insp.type) {
code += ` type = "${insp.type}",\n`;
}
if (insp.desc_global) {
const desc = insp.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"');
code += ` desc_global = "${desc}",\n`;
}
code += ' },\n';
});
code += ' },\n';
}
// 神光一闪
if (card.isgod_inspiration && card.god_inspirations) {
code += ' god_inspiration = {\n';
const gods = ['circen', 'diallos', 'nihilum', 'secred', 'vitor'];
gods.forEach(god => {
if (card.god_inspirations[god] && card.god_inspirations[god].length > 0) {
code += ` ${god} = {\n`;
card.god_inspirations[god].forEach(insp => {
code += ' {\n';
if (insp.ap !== undefined) {
code += ` ap = ${isNaN(insp.ap) ? `"${insp.ap}"` : insp.ap},\n`;
}
if (insp.type) {
code += ` type = "${insp.type}",\n`;
}
if (insp.desc_global) {
const desc = insp.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"');
code += ` desc_global = "${desc}",\n`;
}
code += ' },\n';
});
code += ' },\n';
}
});
code += ' },\n';
}
code += ' },\n';
}
code += '}\n\n';
return code;
}
// 保存卡牌
saveCard() {
if (!this.cardData.name) {
alert('请输入卡牌名称!');
return;
}
// 克隆当前卡牌数据
const cardToSave = JSON.parse(JSON.stringify(this.cardData));
// 保存到cards对象
this.cards[cardToSave.name] = cardToSave;
this.currentCard = cardToSave.name;
// 更新order
if (!this.defaultData.order.includes(cardToSave.name)) {
this.defaultData.order.push(cardToSave.name);
}
// 更新列表和预览
const listContainer = document.getElementById('card-list');
if (listContainer) {
this.updateCardList(listContainer);
}
this.updatePreview();
alert('卡牌保存成功!');
}
// 新建卡牌
newCard() {
this.currentCard = null;
this.cardData = {
name: '',
displayname: '',
art: '',
group: '',
rarity: '',
god: '',
ap: '',
type: '攻击',
dict: '',
desc_global: '',
sub: '',
isinspiration: false,
isgod_inspiration: false,
inspirations: [],
god_inspirations: {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
}
};
// 重新渲染输入区
const leftPanel = document.querySelector('.card-input-panel');
if (leftPanel) {
leftPanel.innerHTML = '';
leftPanel.appendChild(this.renderFighterSelect());
leftPanel.appendChild(this.renderDefaultInfo());
leftPanel.appendChild(this.renderCardInput());
}
// 清除列表选中状态
const listContainer = document.getElementById('card-list');
if (listContainer) {
listContainer.querySelectorAll('.card-list-item').forEach(i => i.classList.remove('active'));
}
}
// 加载卡牌
loadCard(name) {
if (!this.cards[name]) return;
this.currentCard = name;
this.cardData = JSON.parse(JSON.stringify(this.cards[name]));
// 重新渲染输入区
const leftPanel = document.querySelector('.card-input-panel');
if (leftPanel) {
leftPanel.innerHTML = '';
leftPanel.appendChild(this.renderFighterSelect());
leftPanel.appendChild(this.renderDefaultInfo());
leftPanel.appendChild(this.renderCardInput());
}
}
// 加载战斗员数据
async loadFighterData() {
if (!this.currentFighter) return;
try {
const api = new mw.Api();
const moduleName = `模块:卡牌/${this.currentFighter}`;
const response = await api.get({
action: 'query',
prop: 'revisions',
titles: moduleName,
rvprop: 'content',
rvslots: 'main'
});
const pages = response.query.pages;
const page = Object.values(pages)[0];
if (page.revisions) {
const content = page.revisions[0].slots.main['*'];
this.parseLuaCode(content);
// 重新渲染
this.render();
}
} catch (error) {
console.error('加载战斗员数据失败:', error);
}
}
// 解析Lua代码(简化版)
parseLuaCode(code) {
// 这里应该实现完整的Lua代码解析
// 由于复杂度较高,这里只做基本的解析示例
// 解析order
const orderMatch = code.match(/card\.order\s*=\s*{\s*"([^"]*)"\s*}/);
if (orderMatch) {
this.defaultData.order = orderMatch[1].split(',').map(s => s.trim());
}
// 解析ego
const egoMatch = code.match(/ego\s*=\s*"([^"]*)"/);
if (egoMatch) {
this.defaultData.ego = egoMatch[1];
}
// 这里应该继续解析各个卡牌的数据
// 由于Lua语法复杂,建议使用专门的Lua解析器
}
// 保存到Mediawiki
async saveToMediawiki() {
if (!this.currentFighter) {
alert('请先选择战斗员!');
return;
}
const code = this.generateLuaCode();
const moduleName = `模块:卡牌/${this.currentFighter}`;
try {
const api = new mw.Api();
await api.postWithToken('csrf', {
action: 'edit',
title: moduleName,
text: code,
summary: '通过卡牌管理器更新',
contentmodel: 'Scribunto'
});
alert('保存成功!');
} catch (error) {
console.error('保存失败:', error);
alert('保存失败:' + error);
}
}
// 复制代码
copyCode() {
const code = this.generateLuaCode();
// 创建临时文本域
const textarea = document.createElement('textarea');
textarea.value = code;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('代码已复制到剪贴板!');
}
// 绑定事件
bindEvents() {
// 可以在这里添加全局事件监听
}
}
// 等待页面加载完成
if (mw.config.get('wgPageName') === 'Special:Card' ||
window.location.search.includes('cardmanager=1')) {
mw.loader.using(['mediawiki.api']).then(() => {
const manager = new CardManager();
manager.init();
});
}
// 添加导航链接(可选)
mw.loader.using(['mediawiki.util']).then(() => {
mw.util.addPortletLink(
'p-tb',
'?cardmanager=1',
'卡牌管理器',
't-cardmanager',
'打开卡牌管理器'
);
});
})();