Card.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 |
无编辑摘要 |
||
| 第1行: | 第1行: | ||
/ | /** | ||
* MediaWiki 卡牌管理器 | |||
* 用于管理模块:卡牌/角色名的数据 | |||
*/ | |||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
const CardManager = { | |||
cards: [], | |||
currentCard: null, | |||
currentVariantIndex: null, | |||
characterName: '', | |||
// 初始化 | |||
init: function() { | |||
this.characterName = this.getCharacterName(); | |||
this.bindEvents(); | |||
this.loadCardModule(); | |||
}, | |||
// 从页面标题获取角色名 | |||
getCharacterName: function() { | |||
const match = mw.config.get('wgPageName').match(/模块:卡牌\/(.+)/); | |||
return match ? match[1] : ''; | |||
}, | |||
// 绑定事件 | |||
bindEvents: function() { | |||
$('#add-card-btn').on('click', () => this.addCard()); | |||
$('#add-variant-btn').on('click', () => this.addVariant()); | |||
$('#save-card-btn').on('click', () => this.saveCard()); | |||
$('#clear-form-btn').on('click', () => this.clearForm()); | |||
$('#delete-card-btn').on('click', () => this.deleteCard()); | |||
$('#delete-variant-btn').on('click', () => this.deleteVariant()); | |||
$('#export-btn').on('click', () => this.exportToModule()); | |||
$('#import-btn').on('click', () => this.importFromModule()); | |||
// 格式化按钮 | |||
$('#blue-text-btn').on('click', () => this.insertFormat('文本', '蓝')); | |||
$('#green-text-btn').on('click', () => this.insertFormat('文本', '绿')); | |||
$('#green-stroke-btn').on('click', () => this.insertFormat('描边', '绿')); | |||
$('#br-btn').on('click', () => this.insertBr()); | |||
// 卡牌选择 | |||
$('#card-list').on('click', '.card-item', (e) => { | |||
const index = $(e.currentTarget).data('index'); | |||
this.selectCard(index); | |||
}); | |||
// 变体选择 | |||
$('#variant-list').on('click', '.variant-item', (e) => { | |||
const index = $(e.currentTarget).data('index'); | |||
this.selectVariant(index); | |||
}); | |||
// 卡组类型变化 | |||
$('#deck-type').on('change', (e) => { | |||
const isVariant = $(e.target).val() === '灵光一闪'; | |||
this.toggleVariantFields(isVariant); | |||
}); | }); | ||
} | }, | ||
// 切换变体字段状态 | |||
toggleVariantFields: function(isVariant) { | |||
$('#card-name').prop('disabled', isVariant); | |||
$('#card-art').prop('disabled', isVariant); | |||
$('#card-attr').prop('disabled', isVariant); | |||
prop | $('#card-rarity').prop('disabled', isVariant); | ||
$('#card-derived').prop('disabled', isVariant); | |||
}, | |||
// 插入格式 | |||
insertFormat: function(type, color) { | |||
const textarea = document.getElementById('card-desc'); | |||
const start = textarea.selectionStart; | |||
const end = textarea.selectionEnd; | |||
const text = textarea.value; | |||
const selectedText = text.substring(start, end); | |||
const formatted = `{{${type}|${color}|${selectedText}}}`; | |||
textarea.value = text.substring(0, start) + formatted + text.substring(end); | |||
textarea.focus(); | |||
textarea.setSelectionRange(start + formatted.length - selectedText.length - 2, start + formatted.length - 2); | |||
}, | |||
// 插入换行 | |||
insertBr: function() { | |||
const textarea = document.getElementById('card-desc'); | |||
const start = textarea.selectionStart; | |||
const text = textarea.value; | |||
textarea.value = text.substring(0, start) + '<br>' + text.substring(start); | |||
textarea.focus(); | |||
textarea.setSelectionRange(start + 4, start + 4); | |||
}, | |||
// 获取表单数据 | |||
getFormData: function() { | |||
const variant = {}; | |||
const art = $('#card-art').val().trim(); | |||
if (art) variant.art = art; | |||
const deck = $('#deck-type').val().trim(); | |||
if (deck) variant['卡组'] = deck; | |||
const attr = $('#card-attr').val().trim(); | |||
if (attr) variant['属性'] = attr; | |||
const rarity = $('#card-rarity').val().trim(); | |||
if (rarity) variant['稀有度'] = rarity; | |||
const ap = $('#card-ap').val().trim(); | |||
if (ap) { | |||
variant['AP'] = ap.toUpperCase() === 'X' ? 'X' : parseInt(ap); | |||
} | } | ||
if ( | const mechanism = $('#card-mechanism').val().trim(); | ||
if (mechanism) variant['机制'] = mechanism; | |||
const cardType = $('#card-type').val().trim(); | |||
if (cardType) variant['类型'] = cardType; | |||
const desc = $('#card-desc').val().trim(); | |||
if (desc) variant['描述'] = desc; | |||
const derived = $('#card-derived').val().trim(); | |||
if (derived) variant['衍生卡牌'] = derived; | |||
return { | |||
name: $('#card-name').val().trim(), | |||
variants: [variant] | |||
}; | |||
}, | |||
// 设置表单数据 | |||
setFormData: function(card, variantIndex = 0) { | |||
$('#card-name').val(card.name); | |||
if (variantIndex < card.variants.length) { | |||
const variant = card.variants[variantIndex]; | |||
const isVariant = variant['卡组'] === '灵光一闪'; | |||
this.toggleVariantFields(isVariant); | |||
$('#card-art').val(variant.art || ''); | |||
$('#deck-type').val(variant['卡组'] || ''); | |||
$('#card-attr').val(variant['属性'] || ''); | |||
$('#card-rarity').val(variant['稀有度'] || ''); | |||
$('#card-ap').val(variant['AP'] !== undefined ? variant['AP'] : ''); | |||
$('#card-mechanism').val(variant['机制'] || ''); | |||
$('#card-type').val(variant['类型'] || ''); | |||
$('#card-desc').val(variant['描述'] || ''); | |||
$('#card-derived').val(variant['衍生卡牌'] || ''); | |||
} | } | ||
} | }, | ||
// 清空表单 | |||
clearForm: function() { | |||
$('#card-name').val(''); | |||
$('#card-art').val(''); | |||
$('#deck-type').val(''); | |||
$('#card-attr').val(''); | |||
$('#card-rarity').val(''); | |||
$('#card-ap').val(''); | |||
$('#card-mechanism').val(''); | |||
$('#card-type').val(''); | |||
$('#card-desc').val(''); | |||
$('#card-derived').val(''); | |||
this.toggleVariantFields(false); | |||
this.currentCard = null; | |||
this.currentVariantIndex = null; | |||
}, | |||
// | // 添加卡牌 | ||
( | addCard: function() { | ||
if ( | const cardData = this.getFormData(); | ||
if (!cardData.name) { | |||
mw.notify('卡牌名称不能为空!', {type: 'error'}); | |||
return; | |||
} | } | ||
this.cards.push(cardData); | |||
this.currentCard = cardData; | |||
this.currentVariantIndex = 0; | |||
this.updateCardList(); | |||
this.updateVariantList(); | |||
$ | this.updatePreview(); | ||
} | this.clearForm(); | ||
mw.notify(`卡牌 "${cardData.name}" 添加成功!`, {type: 'success'}); | |||
}, | |||
// 添加变体 | |||
addVariant: function() { | |||
if (!this.currentCard) { | |||
mw.notify('请先选择一个卡牌!', {type: 'error'}); | |||
return; | |||
} | |||
const variantData = this.getFormData(); | |||
const variant = variantData.variants[0]; | |||
variant['卡组'] = '灵光一闪'; | |||
this.currentCard.variants.push(variant); | |||
this.currentVariantIndex = this.currentCard.variants.length - 1; | |||
this.updateVariantList(); | |||
this.updatePreview(); | |||
mw.notify(`为 "${this.currentCard.name}" 添加变体成功!`, {type: 'success'}); | |||
}, | |||
// | // 保存卡牌 | ||
saveCard: function() { | |||
if (!this.currentCard || this.currentVariantIndex === null) { | |||
mw.notify('请先选择要保存的卡牌或变体!', {type: 'error'}); | |||
return; | |||
} | |||
const cardData = this.getFormData(); | |||
const isVariant = this.currentVariantIndex > 0; | |||
if ( | if (isVariant) { | ||
const variant = cardData.variants[0]; | |||
variant['卡组'] = '灵光一闪'; | |||
this.currentCard.variants[this.currentVariantIndex] = variant; | |||
} else { | } else { | ||
this.currentCard.name = cardData.name; | |||
this.currentCard.variants[0] = cardData.variants[0]; | |||
} | } | ||
this.updateCardList(); | |||
this.updateVariantList(); | |||
this.updatePreview(); | |||
mw.notify('数据保存成功!', {type: 'success'}); | |||
}, | |||
} | |||
// | // 删除卡牌 | ||
deleteCard: function() { | |||
if (!this.currentCard) { | |||
mw.notify('请先选择要删除的卡牌!', {type: 'error'}); | |||
return; | |||
} | |||
if (!confirm(`确定要删除卡牌 "${this.currentCard.name}" 吗?`)) { | |||
return; | |||
} | |||
const index = this.cards.indexOf(this.currentCard); | |||
this.cards.splice(index, 1); | |||
this.currentCard = null; | |||
this.currentVariantIndex = null; | |||
this.updateCardList(); | |||
this.updateVariantList(); | |||
this.updatePreview(); | |||
this.clearForm(); | |||
mw.notify('卡牌删除成功!', {type: 'success'}); | |||
}, | |||
return | // 删除变体 | ||
deleteVariant: function() { | |||
if (!this.currentCard || this.currentVariantIndex === null) { | |||
mw.notify('请先选择要删除的变体!', {type: 'error'}); | |||
return; | |||
} | |||
if (this.currentVariantIndex === 0) { | |||
mw.notify('不能删除主卡牌变体!', {type: 'error'}); | |||
return; | |||
} | |||
if (!confirm('确定要删除这个变体吗?')) { | |||
return; | |||
} | |||
this.currentCard.variants.splice(this.currentVariantIndex, 1); | |||
this.currentVariantIndex = 0; | |||
this.updateVariantList(); | |||
this.updatePreview(); | |||
this.setFormData(this.currentCard, 0); | |||
mw.notify('变体删除成功!', {type: 'success'}); | |||
}, | |||
// 选择卡牌 | |||
selectCard: function(index) { | |||
this.currentCard = this.cards[index]; | |||
this.currentVariantIndex = 0; | |||
this.updateVariantList(); | |||
this.setFormData(this.currentCard, 0); | |||
}, | |||
} | |||
// 选择变体 | |||
selectVariant: function(index) { | |||
if (!this.currentCard) return; | |||
this.currentVariantIndex = index; | |||
this.setFormData(this.currentCard, index); | |||
}, | |||
} | |||
// 更新卡牌列表 | |||
updateCardList: function() { | |||
const $list = $('#card-list'); | |||
$list.empty(); | |||
this.cards.forEach((card, index) => { | |||
const $item = $('<div>') | |||
.addClass('card-item') | |||
.attr('data-index', index) | |||
.text(card.name); | |||
} | |||
if (this.currentCard === card) { | |||
$item.addClass('active'); | |||
} | |||
$list.append($item); | |||
}); | |||
}, | |||
// 更新变体列表 | |||
updateVariantList: function() { | |||
const $list = $('#variant-list'); | |||
$list.empty(); | |||
if (this.currentCard) { | |||
this.currentCard.variants.forEach((variant, index) => { | |||
const deck = variant['卡组'] || '未知'; | |||
const label = index === 0 ? `主卡牌 (${deck})` : `变体 ${index} (灵光一闪)`; | |||
const $item = $('<div>') | |||
.addClass('variant-item') | |||
.attr('data-index', index) | |||
.text(label); | |||
if (this.currentVariantIndex === index) { | |||
$item.addClass('active'); | |||
} | |||
$list.append($item); | |||
}); | |||
} | } | ||
}, | |||
} | |||
// | // 生成Lua代码 | ||
generateLuaCode: function() { | |||
if (this.cards.length === 0) { | |||
return '-- 暂无数据'; | |||
} | |||
let lua = 'local p = {}\n\n'; | |||
if ( | // cardOrder | ||
lua += 'local cardOrder = {\n'; | |||
this.cards.forEach(card => { | |||
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') { | |||
lua += ` "${this.escapeLua(card.name)}",\n`; | |||
} | } | ||
}); | |||
lua += '}\n\n'; | |||
// card data | |||
lua += 'local card = {\n'; | |||
this.cards.forEach(card => { | |||
lua += ` ["${this.escapeLua(card.name)}"] = {\n`; | |||
card.variants.forEach(variant => { | |||
lua += ' {\n'; | |||
const fieldOrder = ['art', '卡组', '属性', '稀有度', 'AP', '机制', '类型', '描述', '衍生卡牌']; | |||
fieldOrder.forEach(field => { | |||
if (variant[field] !== undefined && variant[field] !== '') { | |||
const value = variant[field]; | |||
if (typeof value === 'string') { | |||
lua += ` ["${field}"] = "${this.escapeLua(value)}",\n`; | |||
} else { | |||
lua += ` ["${field}"] = ${value},\n`; | |||
} | |||
} | |||
}); | |||
lua += ' },\n'; | |||
}); | |||
lua += ' },\n'; | |||
}); | |||
lua += '}\n\n'; | |||
lua += 'p.card = card\n'; | |||
lua += 'p.cardOrder = cardOrder\n\n'; | |||
lua += 'return p\n'; | |||
return lua; | |||
}, | |||
// 转义Lua字符串 | |||
escapeLua: function(str) { | |||
if (typeof str !== 'string') return str; | |||
return str.replace(/\\/g, '\\\\') | |||
.replace(/"/g, '\\"') | |||
.replace(/\n/g, '\\n'); | |||
}, | |||
// 更新预览 | |||
updatePreview: function() { | |||
const code = this.generateLuaCode(); | |||
$('#code-preview').text(code); | |||
}, | |||
// 导出到模块 | |||
exportToModule: function() { | |||
if (this.cards.length === 0) { | |||
mw.notify('没有数据可导出!', {type: 'error'}); | |||
return; | |||
} | } | ||
const code = this.generateLuaCode(); | |||
const moduleName = `模块:卡牌/${this.characterName}`; | |||
// 使用MediaWiki API保存 | |||
new mw.Api().postWithToken('csrf', { | |||
action: 'edit', | |||
title: moduleName, | |||
text: code, | |||
summary: '更新卡牌数据', | |||
contentmodel: 'Scribunto' | |||
}).done(() => { | |||
mw.notify(`成功保存到 ${moduleName}`, {type: 'success'}); | |||
}).fail((err) => { | |||
mw.notify('保存失败: ' + err, {type: 'error'}); | |||
}); | |||
}, | |||
// 从模块导入 | |||
importFromModule: function() { | |||
const moduleName = prompt('请输入要导入的模块名称(如:妮雅):'); | |||
if (!moduleName) return; | |||
this.loadCardModule(moduleName); | |||
}, | |||
// 加载卡牌模块 | |||
action: ' | loadCardModule: function(characterName) { | ||
const name = characterName || this.characterName; | |||
if (!name) return; | |||
const moduleName = `模块:卡牌/${name}`; | |||
new mw.Api().get({ | |||
action: 'query', | |||
prop: 'revisions', | |||
titles: moduleName, | |||
rvprop: 'content', | |||
rvslots: 'main' | |||
}).done((data) => { | |||
const pages = data.query.pages; | |||
const page = pages[Object.keys(pages)[0]]; | |||
if (page.revisions) { | |||
const content = page.revisions[0].slots.main['*']; | |||
this.parseLuaCode(content); | |||
mw.notify(`成功从 ${moduleName} 导入数据`, {type: 'success'}); | |||
} else { | |||
mw.notify('模块不存在', {type: 'error'}); | |||
} | |||
}).fail(() => { | |||
mw.notify('加载失败', {type: 'error'}); | |||
}); | |||
}, | |||
// | // 解析Lua代码 | ||
content | parseLuaCode: function(content) { | ||
try { | |||
this.cards = []; | |||
// 提取cardOrder | |||
const orderMatch = content.match(/local\s+cardOrder\s*=\s*\{([^}]+)\}/s); | |||
const cardOrder = []; | |||
if (orderMatch) { | |||
const names = orderMatch[1].match(/"([^"]+)"/g); | |||
if (names) { | |||
names.forEach(name => { | |||
cardOrder.push(name.replace(/"/g, '')); | |||
}); | |||
} | |||
} | |||
// 提取card表 | |||
const cardMatch = content.match(/local\s+card\s*=\s*\{(.+)\}\s*p\.card/s); | |||
if (!cardMatch) { | |||
mw.notify('无法解析卡牌数据', {type: 'error'}); | |||
return; | |||
} | |||
const cardContent = cardMatch[1]; | |||
// 解析每个卡牌 | |||
const cardPattern = /\["([^"]+)"\]\s*=\s*\{/g; | |||
let match; | |||
const allCardNames = []; | |||
while ((match = cardPattern.exec(cardContent)) !== null) { | |||
const cardName = match[1]; | |||
if (!allCardNames.includes(cardName)) { | |||
allCardNames.push(cardName); | |||
} | |||
} | |||
// 按cardOrder排序 | |||
const sortedNames = []; | |||
cardOrder.forEach(name => { | |||
if (allCardNames.includes(name)) { | |||
sortedNames.push(name); | |||
allCardNames.splice(allCardNames.indexOf(name), 1); | |||
} | |||
}); | |||
sortedNames.push(...allCardNames); | |||
// 解析每个卡牌的变体 | |||
sortedNames.forEach(cardName => { | |||
const variants = this.extractCardVariants(cardContent, cardName); | |||
if (variants.length > 0) { | |||
this.cards.push({ | |||
name: cardName, | |||
variants: variants | |||
}); | |||
} | |||
}); | |||
this.updateCardList(); | |||
this.updateVariantList(); | |||
this.updatePreview(); | |||
this.clearForm(); | |||
} catch (e) { | |||
console.error('解析错误:', e); | |||
mw.notify('解析失败: ' + e.message, {type: 'error'}); | |||
} | |||
}, | |||
// | // 提取卡牌变体 | ||
content | extractCardVariants: function(content, cardName) { | ||
const variants = []; | |||
const escapedName = cardName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |||
const pattern = new RegExp(`\\["${escapedName}"\\]\\s*=\\s*\\{`, 'g'); | |||
const match = pattern.exec(content); | |||
if (!match) return variants; | |||
content | |||
let pos = match.index + match[0].length; | |||
let braceCount = 1; | |||
if ( | let variantStart = -1; | ||
let inString = false; | |||
let escape = false; | |||
content | while (pos < content.length && braceCount > 0) { | ||
const char = content[pos]; | |||
if (escape) { | |||
escape = false; | |||
pos++; | |||
continue; | |||
} | |||
if (char === '\\' && inString) { | |||
escape = true; | |||
pos++; | |||
continue; | |||
} | |||
if (char === '"') { | |||
inString = !inString; | |||
} | |||
if (!inString) { | |||
if (char === '{') { | |||
if (braceCount === 1 && variantStart === -1) { | |||
variantStart = pos + 1; | |||
} | |||
braceCount++; | |||
} else if (char === '}') { | |||
braceCount--; | |||
if (braceCount === 1 && variantStart !== -1) { | |||
const variantContent = content.substring(variantStart, pos); | |||
const variant = this.parseVariant(variantContent); | |||
variants.push(variant); | |||
variantStart = -1; | |||
} | } | ||
} | } | ||
} | } | ||
} | pos++; | ||
} | |||
return variants; | |||
} | }, | ||
content | // 解析变体 | ||
parseVariant: function(content) { | |||
const variant = {}; | |||
const fieldPattern = /\["([^"]+)"\]\s*=\s*("([^"\\]*(\\.[^"\\]*)*)"|(-?\d+|[XxA-Za-z]+))/g; | |||
let match; | |||
while ((match = fieldPattern.exec(content)) !== null) { | |||
const fieldName = match[1]; | |||
let value; | |||
if (match[2].startsWith('"')) { | |||
// 字符串值 | |||
value = match[3] | |||
.replace(/\\"/g, '"') | |||
.replace(/\\\\/g, '\\') | |||
.replace(/\\n/g, '\n'); | |||
} else { | |||
// 数字或其他值 | |||
const val = match[5]; | |||
if (/^-?\d+$/.test(val)) { | |||
value = parseInt(val); | |||
} else { | |||
value = val; | |||
} | |||
} | |||
variant[fieldName] = value; | |||
} | } | ||
} | |||
return variant; | |||
} | |||
}; | |||
// 页面加载完成后初始化 | |||
$(document).ready(() => { | |||
if (mw.config.get('wgPageName') === 'MediaWiki:Card') { | |||
CardManager.init(); | |||
} | |||
}); | }); | ||
window.CardManager = CardManager; | |||
})(); | })(); | ||
2025年10月2日 (四) 10:14的版本
/**
* MediaWiki 卡牌管理器
* 用于管理模块:卡牌/角色名的数据
*/
(function() {
'use strict';
const CardManager = {
cards: [],
currentCard: null,
currentVariantIndex: null,
characterName: '',
// 初始化
init: function() {
this.characterName = this.getCharacterName();
this.bindEvents();
this.loadCardModule();
},
// 从页面标题获取角色名
getCharacterName: function() {
const match = mw.config.get('wgPageName').match(/模块:卡牌\/(.+)/);
return match ? match[1] : '';
},
// 绑定事件
bindEvents: function() {
$('#add-card-btn').on('click', () => this.addCard());
$('#add-variant-btn').on('click', () => this.addVariant());
$('#save-card-btn').on('click', () => this.saveCard());
$('#clear-form-btn').on('click', () => this.clearForm());
$('#delete-card-btn').on('click', () => this.deleteCard());
$('#delete-variant-btn').on('click', () => this.deleteVariant());
$('#export-btn').on('click', () => this.exportToModule());
$('#import-btn').on('click', () => this.importFromModule());
// 格式化按钮
$('#blue-text-btn').on('click', () => this.insertFormat('文本', '蓝'));
$('#green-text-btn').on('click', () => this.insertFormat('文本', '绿'));
$('#green-stroke-btn').on('click', () => this.insertFormat('描边', '绿'));
$('#br-btn').on('click', () => this.insertBr());
// 卡牌选择
$('#card-list').on('click', '.card-item', (e) => {
const index = $(e.currentTarget).data('index');
this.selectCard(index);
});
// 变体选择
$('#variant-list').on('click', '.variant-item', (e) => {
const index = $(e.currentTarget).data('index');
this.selectVariant(index);
});
// 卡组类型变化
$('#deck-type').on('change', (e) => {
const isVariant = $(e.target).val() === '灵光一闪';
this.toggleVariantFields(isVariant);
});
},
// 切换变体字段状态
toggleVariantFields: function(isVariant) {
$('#card-name').prop('disabled', isVariant);
$('#card-art').prop('disabled', isVariant);
$('#card-attr').prop('disabled', isVariant);
$('#card-rarity').prop('disabled', isVariant);
$('#card-derived').prop('disabled', isVariant);
},
// 插入格式
insertFormat: function(type, color) {
const textarea = document.getElementById('card-desc');
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const text = textarea.value;
const selectedText = text.substring(start, end);
const formatted = `{{${type}|${color}|${selectedText}}}`;
textarea.value = text.substring(0, start) + formatted + text.substring(end);
textarea.focus();
textarea.setSelectionRange(start + formatted.length - selectedText.length - 2, start + formatted.length - 2);
},
// 插入换行
insertBr: function() {
const textarea = document.getElementById('card-desc');
const start = textarea.selectionStart;
const text = textarea.value;
textarea.value = text.substring(0, start) + '<br>' + text.substring(start);
textarea.focus();
textarea.setSelectionRange(start + 4, start + 4);
},
// 获取表单数据
getFormData: function() {
const variant = {};
const art = $('#card-art').val().trim();
if (art) variant.art = art;
const deck = $('#deck-type').val().trim();
if (deck) variant['卡组'] = deck;
const attr = $('#card-attr').val().trim();
if (attr) variant['属性'] = attr;
const rarity = $('#card-rarity').val().trim();
if (rarity) variant['稀有度'] = rarity;
const ap = $('#card-ap').val().trim();
if (ap) {
variant['AP'] = ap.toUpperCase() === 'X' ? 'X' : parseInt(ap);
}
const mechanism = $('#card-mechanism').val().trim();
if (mechanism) variant['机制'] = mechanism;
const cardType = $('#card-type').val().trim();
if (cardType) variant['类型'] = cardType;
const desc = $('#card-desc').val().trim();
if (desc) variant['描述'] = desc;
const derived = $('#card-derived').val().trim();
if (derived) variant['衍生卡牌'] = derived;
return {
name: $('#card-name').val().trim(),
variants: [variant]
};
},
// 设置表单数据
setFormData: function(card, variantIndex = 0) {
$('#card-name').val(card.name);
if (variantIndex < card.variants.length) {
const variant = card.variants[variantIndex];
const isVariant = variant['卡组'] === '灵光一闪';
this.toggleVariantFields(isVariant);
$('#card-art').val(variant.art || '');
$('#deck-type').val(variant['卡组'] || '');
$('#card-attr').val(variant['属性'] || '');
$('#card-rarity').val(variant['稀有度'] || '');
$('#card-ap').val(variant['AP'] !== undefined ? variant['AP'] : '');
$('#card-mechanism').val(variant['机制'] || '');
$('#card-type').val(variant['类型'] || '');
$('#card-desc').val(variant['描述'] || '');
$('#card-derived').val(variant['衍生卡牌'] || '');
}
},
// 清空表单
clearForm: function() {
$('#card-name').val('');
$('#card-art').val('');
$('#deck-type').val('');
$('#card-attr').val('');
$('#card-rarity').val('');
$('#card-ap').val('');
$('#card-mechanism').val('');
$('#card-type').val('');
$('#card-desc').val('');
$('#card-derived').val('');
this.toggleVariantFields(false);
this.currentCard = null;
this.currentVariantIndex = null;
},
// 添加卡牌
addCard: function() {
const cardData = this.getFormData();
if (!cardData.name) {
mw.notify('卡牌名称不能为空!', {type: 'error'});
return;
}
this.cards.push(cardData);
this.currentCard = cardData;
this.currentVariantIndex = 0;
this.updateCardList();
this.updateVariantList();
this.updatePreview();
this.clearForm();
mw.notify(`卡牌 "${cardData.name}" 添加成功!`, {type: 'success'});
},
// 添加变体
addVariant: function() {
if (!this.currentCard) {
mw.notify('请先选择一个卡牌!', {type: 'error'});
return;
}
const variantData = this.getFormData();
const variant = variantData.variants[0];
variant['卡组'] = '灵光一闪';
this.currentCard.variants.push(variant);
this.currentVariantIndex = this.currentCard.variants.length - 1;
this.updateVariantList();
this.updatePreview();
mw.notify(`为 "${this.currentCard.name}" 添加变体成功!`, {type: 'success'});
},
// 保存卡牌
saveCard: function() {
if (!this.currentCard || this.currentVariantIndex === null) {
mw.notify('请先选择要保存的卡牌或变体!', {type: 'error'});
return;
}
const cardData = this.getFormData();
const isVariant = this.currentVariantIndex > 0;
if (isVariant) {
const variant = cardData.variants[0];
variant['卡组'] = '灵光一闪';
this.currentCard.variants[this.currentVariantIndex] = variant;
} else {
this.currentCard.name = cardData.name;
this.currentCard.variants[0] = cardData.variants[0];
}
this.updateCardList();
this.updateVariantList();
this.updatePreview();
mw.notify('数据保存成功!', {type: 'success'});
},
// 删除卡牌
deleteCard: function() {
if (!this.currentCard) {
mw.notify('请先选择要删除的卡牌!', {type: 'error'});
return;
}
if (!confirm(`确定要删除卡牌 "${this.currentCard.name}" 吗?`)) {
return;
}
const index = this.cards.indexOf(this.currentCard);
this.cards.splice(index, 1);
this.currentCard = null;
this.currentVariantIndex = null;
this.updateCardList();
this.updateVariantList();
this.updatePreview();
this.clearForm();
mw.notify('卡牌删除成功!', {type: 'success'});
},
// 删除变体
deleteVariant: function() {
if (!this.currentCard || this.currentVariantIndex === null) {
mw.notify('请先选择要删除的变体!', {type: 'error'});
return;
}
if (this.currentVariantIndex === 0) {
mw.notify('不能删除主卡牌变体!', {type: 'error'});
return;
}
if (!confirm('确定要删除这个变体吗?')) {
return;
}
this.currentCard.variants.splice(this.currentVariantIndex, 1);
this.currentVariantIndex = 0;
this.updateVariantList();
this.updatePreview();
this.setFormData(this.currentCard, 0);
mw.notify('变体删除成功!', {type: 'success'});
},
// 选择卡牌
selectCard: function(index) {
this.currentCard = this.cards[index];
this.currentVariantIndex = 0;
this.updateVariantList();
this.setFormData(this.currentCard, 0);
},
// 选择变体
selectVariant: function(index) {
if (!this.currentCard) return;
this.currentVariantIndex = index;
this.setFormData(this.currentCard, index);
},
// 更新卡牌列表
updateCardList: function() {
const $list = $('#card-list');
$list.empty();
this.cards.forEach((card, index) => {
const $item = $('<div>')
.addClass('card-item')
.attr('data-index', index)
.text(card.name);
if (this.currentCard === card) {
$item.addClass('active');
}
$list.append($item);
});
},
// 更新变体列表
updateVariantList: function() {
const $list = $('#variant-list');
$list.empty();
if (this.currentCard) {
this.currentCard.variants.forEach((variant, index) => {
const deck = variant['卡组'] || '未知';
const label = index === 0 ? `主卡牌 (${deck})` : `变体 ${index} (灵光一闪)`;
const $item = $('<div>')
.addClass('variant-item')
.attr('data-index', index)
.text(label);
if (this.currentVariantIndex === index) {
$item.addClass('active');
}
$list.append($item);
});
}
},
// 生成Lua代码
generateLuaCode: function() {
if (this.cards.length === 0) {
return '-- 暂无数据';
}
let lua = 'local p = {}\n\n';
// cardOrder
lua += 'local cardOrder = {\n';
this.cards.forEach(card => {
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') {
lua += ` "${this.escapeLua(card.name)}",\n`;
}
});
lua += '}\n\n';
// card data
lua += 'local card = {\n';
this.cards.forEach(card => {
lua += ` ["${this.escapeLua(card.name)}"] = {\n`;
card.variants.forEach(variant => {
lua += ' {\n';
const fieldOrder = ['art', '卡组', '属性', '稀有度', 'AP', '机制', '类型', '描述', '衍生卡牌'];
fieldOrder.forEach(field => {
if (variant[field] !== undefined && variant[field] !== '') {
const value = variant[field];
if (typeof value === 'string') {
lua += ` ["${field}"] = "${this.escapeLua(value)}",\n`;
} else {
lua += ` ["${field}"] = ${value},\n`;
}
}
});
lua += ' },\n';
});
lua += ' },\n';
});
lua += '}\n\n';
lua += 'p.card = card\n';
lua += 'p.cardOrder = cardOrder\n\n';
lua += 'return p\n';
return lua;
},
// 转义Lua字符串
escapeLua: function(str) {
if (typeof str !== 'string') return str;
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n');
},
// 更新预览
updatePreview: function() {
const code = this.generateLuaCode();
$('#code-preview').text(code);
},
// 导出到模块
exportToModule: function() {
if (this.cards.length === 0) {
mw.notify('没有数据可导出!', {type: 'error'});
return;
}
const code = this.generateLuaCode();
const moduleName = `模块:卡牌/${this.characterName}`;
// 使用MediaWiki API保存
new mw.Api().postWithToken('csrf', {
action: 'edit',
title: moduleName,
text: code,
summary: '更新卡牌数据',
contentmodel: 'Scribunto'
}).done(() => {
mw.notify(`成功保存到 ${moduleName}`, {type: 'success'});
}).fail((err) => {
mw.notify('保存失败: ' + err, {type: 'error'});
});
},
// 从模块导入
importFromModule: function() {
const moduleName = prompt('请输入要导入的模块名称(如:妮雅):');
if (!moduleName) return;
this.loadCardModule(moduleName);
},
// 加载卡牌模块
loadCardModule: function(characterName) {
const name = characterName || this.characterName;
if (!name) return;
const moduleName = `模块:卡牌/${name}`;
new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: moduleName,
rvprop: 'content',
rvslots: 'main'
}).done((data) => {
const pages = data.query.pages;
const page = pages[Object.keys(pages)[0]];
if (page.revisions) {
const content = page.revisions[0].slots.main['*'];
this.parseLuaCode(content);
mw.notify(`成功从 ${moduleName} 导入数据`, {type: 'success'});
} else {
mw.notify('模块不存在', {type: 'error'});
}
}).fail(() => {
mw.notify('加载失败', {type: 'error'});
});
},
// 解析Lua代码
parseLuaCode: function(content) {
try {
this.cards = [];
// 提取cardOrder
const orderMatch = content.match(/local\s+cardOrder\s*=\s*\{([^}]+)\}/s);
const cardOrder = [];
if (orderMatch) {
const names = orderMatch[1].match(/"([^"]+)"/g);
if (names) {
names.forEach(name => {
cardOrder.push(name.replace(/"/g, ''));
});
}
}
// 提取card表
const cardMatch = content.match(/local\s+card\s*=\s*\{(.+)\}\s*p\.card/s);
if (!cardMatch) {
mw.notify('无法解析卡牌数据', {type: 'error'});
return;
}
const cardContent = cardMatch[1];
// 解析每个卡牌
const cardPattern = /\["([^"]+)"\]\s*=\s*\{/g;
let match;
const allCardNames = [];
while ((match = cardPattern.exec(cardContent)) !== null) {
const cardName = match[1];
if (!allCardNames.includes(cardName)) {
allCardNames.push(cardName);
}
}
// 按cardOrder排序
const sortedNames = [];
cardOrder.forEach(name => {
if (allCardNames.includes(name)) {
sortedNames.push(name);
allCardNames.splice(allCardNames.indexOf(name), 1);
}
});
sortedNames.push(...allCardNames);
// 解析每个卡牌的变体
sortedNames.forEach(cardName => {
const variants = this.extractCardVariants(cardContent, cardName);
if (variants.length > 0) {
this.cards.push({
name: cardName,
variants: variants
});
}
});
this.updateCardList();
this.updateVariantList();
this.updatePreview();
this.clearForm();
} catch (e) {
console.error('解析错误:', e);
mw.notify('解析失败: ' + e.message, {type: 'error'});
}
},
// 提取卡牌变体
extractCardVariants: function(content, cardName) {
const variants = [];
const escapedName = cardName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const pattern = new RegExp(`\\["${escapedName}"\\]\\s*=\\s*\\{`, 'g');
const match = pattern.exec(content);
if (!match) return variants;
let pos = match.index + match[0].length;
let braceCount = 1;
let variantStart = -1;
let inString = false;
let escape = false;
while (pos < content.length && braceCount > 0) {
const char = content[pos];
if (escape) {
escape = false;
pos++;
continue;
}
if (char === '\\' && inString) {
escape = true;
pos++;
continue;
}
if (char === '"') {
inString = !inString;
}
if (!inString) {
if (char === '{') {
if (braceCount === 1 && variantStart === -1) {
variantStart = pos + 1;
}
braceCount++;
} else if (char === '}') {
braceCount--;
if (braceCount === 1 && variantStart !== -1) {
const variantContent = content.substring(variantStart, pos);
const variant = this.parseVariant(variantContent);
variants.push(variant);
variantStart = -1;
}
}
}
pos++;
}
return variants;
},
// 解析变体
parseVariant: function(content) {
const variant = {};
const fieldPattern = /\["([^"]+)"\]\s*=\s*("([^"\\]*(\\.[^"\\]*)*)"|(-?\d+|[XxA-Za-z]+))/g;
let match;
while ((match = fieldPattern.exec(content)) !== null) {
const fieldName = match[1];
let value;
if (match[2].startsWith('"')) {
// 字符串值
value = match[3]
.replace(/\\"/g, '"')
.replace(/\\\\/g, '\\')
.replace(/\\n/g, '\n');
} else {
// 数字或其他值
const val = match[5];
if (/^-?\d+$/.test(val)) {
value = parseInt(val);
} else {
value = val;
}
}
variant[fieldName] = value;
}
return variant;
}
};
// 页面加载完成后初始化
$(document).ready(() => {
if (mw.config.get('wgPageName') === 'MediaWiki:Card') {
CardManager.init();
}
});
window.CardManager = CardManager;
})();