Card.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 |
无编辑摘要 |
||
| 第1行: | 第1行: | ||
/ | // MediaWiki:Card.js | ||
// 卡牌管理系统 | |||
(function() { | (function() { | ||
'use strict'; | 'use strict'; | ||
// 检查是否在正确的页面 | |||
if (mw.config.get('wgPageName') !== 'MediaWiki:Card') { | |||
return; | |||
} | |||
// 卡牌数据存储 | |||
var cardData = { | |||
cards: [], | cards: [], | ||
currentCard: null, | currentCard: null, | ||
currentVariantIndex: null, | currentVariantIndex: null | ||
}; | |||
// 字段定义 | |||
var fieldDefinitions = { | |||
cardOrder: ['art', '卡组', '属性', '稀有度', 'AP', '机制', '类型', '描述', '衍生卡牌'], | |||
deckTypes: ['', '起始卡牌', '独特卡牌', '灵光一闪', '衍生卡牌', '神光一闪'], | |||
attributes: ['', '热情', '秩序', '正义', '本能', '虚无'], | |||
rarities: ['', '白', '蓝', '橙', '彩'], | |||
types: ['', '攻击', '技能', '强化', '状态异常'] | |||
}; | |||
// 创建下拉选择框 | |||
function createDropdown(options, value, placeholder) { | |||
var container = $('<div>').addClass('card-dropdown-container'); | |||
var input = $('<input>') | |||
.addClass('card-dropdown-input') | |||
.attr('placeholder', placeholder || '点击选择或输入...') | |||
.val(value || ''); | |||
var dropdown = $('<div>').addClass('card-dropdown-list').hide(); | |||
options.forEach(function(opt) { | |||
$('<div>') | |||
.addClass('card-dropdown-item') | |||
.text(opt || '(空)') | |||
.click(function() { | |||
input.val(opt); | |||
dropdown.hide(); | |||
}) | |||
.appendTo(dropdown); | |||
}); | |||
input.on('focus click', function(e) { | |||
e.stopPropagation(); | |||
$('.card-dropdown-list').hide(); | |||
dropdown.show(); | |||
}); | |||
$(document).click(function() { | |||
dropdown.hide(); | |||
}); | |||
container.append(input, dropdown); | |||
return container; | |||
} | |||
// 创建表单字段 | |||
function createFormField(label, inputElement) { | |||
return $('<div>').addClass('card-form-field') | |||
.append($('<div>').addClass('card-form-label').text(label)) | |||
.append($('<div>').addClass('card-form-input').append(inputElement)); | |||
} | |||
// 初始化界面 | |||
function initUI() { | |||
var container = $('<div>').attr('id', 'card-editor-container'); | |||
// 左侧:输入区 | |||
var leftPanel = $('<div>').addClass('card-panel card-input-panel'); | |||
var formGroup = $('<div>').addClass('card-group').append($('<div>').addClass('card-group-title').text('卡牌数据')); | |||
// 创建表单字段 | |||
var nameInput = $('<input>').attr('id', 'card-name').attr('placeholder', '请输入卡牌名称...'); | |||
formGroup.append(createFormField('卡牌名称:', nameInput)); | |||
var deckDropdown = createDropdown(fieldDefinitions.deckTypes, '', '点击选择或输入...'); | |||
formGroup.append(createFormField('卡组类型:', deckDropdown)); | |||
var artContainer = $('<div>').addClass('card-art-container'); | |||
var artInput = $('<input>').attr('id', 'card-art').attr('placeholder', '输入图片文件名...'); | |||
artContainer.append(artInput); | |||
formGroup.append(createFormField('图片文件:', artContainer)); | |||
var attrDropdown = createDropdown(fieldDefinitions.attributes, '', '点击选择...'); | |||
formGroup.append(createFormField('属性:', attrDropdown)); | |||
var rarityDropdown = createDropdown(fieldDefinitions.rarities, '', '点击选择...'); | |||
formGroup.append(createFormField('稀有度:', rarityDropdown)); | |||
var apInput = $('<input>').attr('id', 'card-ap').attr('placeholder', '输入AP数值...'); | |||
formGroup.append(createFormField('AP (行动点):', apInput)); | |||
var typeDropdown = createDropdown(fieldDefinitions.types, '', '点击选择...'); | |||
formGroup.append(createFormField('卡牌类型:', typeDropdown)); | |||
var mechanismInput = $('<input>').attr('id', 'card-mechanism').attr('placeholder', '请输入卡牌机制...'); | |||
formGroup.append(createFormField('卡牌机制:', mechanismInput)); | |||
// 描述区域 | |||
var descContainer = $('<div>').addClass('card-desc-container'); | |||
var descHeader = $('<div>').addClass('card-desc-header') | |||
.append($('<span>').text('卡牌描述:')) | |||
.append($('<div>').addClass('card-desc-buttons') | |||
.append($('<div>').addClass('card-button card-button-small card-button-blue').text('蓝色文本').click(function() { insertTextFormat('蓝'); })) | |||
.append($('<div>').addClass('card-button card-button-small card-button-green').text('绿色文本').click(function() { insertTextFormat('绿'); })) | |||
.append($('<div>').addClass('card-button card-button-small card-button-stroke').text('绿色描边').click(insertStrokeFormat)) | |||
.append($('<div>').addClass('card-button card-button-small card-button-br').text('插入换行').click(insertBr)) | |||
); | |||
var descTextarea = $('<textarea>').attr('id', 'card-desc').attr('placeholder', '请输入卡牌描述...'); | |||
descContainer.append(descHeader, descTextarea); | |||
formGroup.append(descContainer); | |||
var derivedInput = $('<input>').attr('id', 'card-derived').attr('placeholder', '请输入衍生卡牌...'); | |||
formGroup.append(createFormField('衍生卡牌:', derivedInput)); | |||
// | leftPanel.append(formGroup); | ||
// 按钮区 | |||
var buttonContainer = $('<div>').addClass('card-button-container'); | |||
buttonContainer.append( | |||
$('<div>').addClass('card-button card-button-add').text('添加卡牌').click(addCard), | |||
$('<div>').addClass('card-button card-button-add').text('添加变体').click(addVariant), | |||
$('<div>').addClass('card-button').text('保存数据').click(saveData), | |||
$('<div>').addClass('card-button').text('清空表单').click(clearForm) | |||
); | |||
leftPanel.append(buttonContainer); | |||
// 中间:列表区 | |||
var middlePanel = $('<div>').addClass('card-panel card-list-panel'); | |||
var cardListGroup = $('<div>').addClass('card-group') | |||
.append($('<div>').addClass('card-group-title').text('卡牌列表')) | |||
.append($('<div>').attr('id', 'card-list').addClass('card-list')); | |||
middlePanel.append(cardListGroup); | |||
var variantListGroup = $('<div>').addClass('card-group') | |||
.append($('<div>').addClass('card-group-title').text('变体列表')) | |||
.append($('<div>').attr('id', 'variant-list').addClass('card-list')); | |||
middlePanel.append(variantListGroup); | |||
var deleteButtons = $('<div>').addClass('card-button-container'); | |||
deleteButtons.append( | |||
$('<div>').addClass('card-button card-button-delete').text('删除卡牌').click(deleteCard), | |||
$('<div>').addClass('card-button card-button-delete').text('删除变体').click(deleteVariant) | |||
); | |||
middlePanel.append(deleteButtons); | |||
// 右侧:代码显示区 | |||
var rightPanel = $('<div>').addClass('card-panel card-code-panel'); | |||
var codeGroup = $('<div>').addClass('card-group') | |||
.append($('<div>').addClass('card-group-title').text('Lua代码预览')) | |||
.append($('<textarea>').attr('id', 'code-display').addClass('card-code-display').attr('readonly', true)); | |||
rightPanel.append(codeGroup); | |||
var codeButtons = $('<div>').addClass('card-button-container'); | |||
codeButtons.append( | |||
$('<div>').addClass('card-button').text('复制代码').click(copyCode), | |||
$('<div>').addClass('card-button').text('加载数据').click(loadFromWiki), | |||
$('<div>').addClass('card-button').text('保存到Wiki').click(saveToWiki) | |||
); | |||
rightPanel.append(codeButtons); | |||
container.append(leftPanel, middlePanel, rightPanel); | |||
$('#mw-content-text').empty().append(container); | |||
// | // 初始化拖拽排序 | ||
initSortable(); | |||
} | |||
// 初始化拖拽排序 | |||
function initSortable() { | |||
mw.loader.using('jquery.ui', function() { | |||
$('#card-list').sortable({ | |||
update: function(event, ui) { | |||
updateCardOrder(); | |||
} | } | ||
}); | }); | ||
}); | |||
} | |||
// 文本格式化函数 | |||
function insertTextFormat(color) { | |||
var textarea = $('#card-desc')[0]; | |||
var start = textarea.selectionStart; | |||
var end = textarea.selectionEnd; | |||
} | var text = textarea.value; | ||
var selectedText = text.substring(start, end); | |||
var newText = text.substring(0, start) + | |||
'{{文本|' + color + '|' + selectedText + '}}' + | |||
text.substring(end); | |||
textarea.value = newText; | |||
textarea.focus(); | |||
textarea.setSelectionRange(start + 6 + color.length, start + 6 + color.length + selectedText.length); | |||
} | |||
function insertStrokeFormat() { | |||
var textarea = $('#card-desc')[0]; | |||
var start = textarea.selectionStart; | |||
var end = textarea.selectionEnd; | |||
var text = textarea.value; | |||
var selectedText = text.substring(start, end); | |||
var newText = text.substring(0, start) + | |||
'{{描边|绿|' + selectedText + '}}' + | |||
text.substring(end); | |||
textarea.value = newText; | |||
textarea.focus(); | |||
textarea.setSelectionRange(start + 7, start + 7 + selectedText.length); | |||
} | |||
function insertBr() { | |||
var textarea = $('#card-desc')[0]; | |||
var pos = textarea.selectionStart; | |||
var text = textarea.value; | |||
var newText = text.substring(0, pos) + '<br>' + text.substring(pos); | |||
textarea.value = newText; | |||
textarea.focus(); | |||
textarea.setSelectionRange(pos + 4, pos + 4); | |||
} | |||
// 获取表单数据 | |||
function getFormData() { | |||
var variant = {}; | |||
var art = $('#card-art').val().trim(); | |||
if (art) variant.art = art; | |||
var deck = $('.card-dropdown-container').eq(0).find('input').val().trim(); | |||
if (deck) variant['卡组'] = deck; | |||
var attr = $('.card-dropdown-container').eq(1).find('input').val().trim(); | |||
if (attr) variant['属性'] = attr; | |||
var rarity = $('.card-dropdown-container').eq(2).find('input').val().trim(); | |||
if (rarity) variant['稀有度'] = rarity; | |||
var ap = $('#card-ap').val().trim(); | |||
if (ap) { | |||
variant.AP = isNaN(ap) ? ap : parseInt(ap); | |||
} | |||
var type = $('.card-dropdown-container').eq(3).find('input').val().trim(); | |||
if (type) variant['类型'] = type; | |||
var mechanism = $('#card-mechanism').val().trim(); | |||
if (mechanism) variant['机制'] = mechanism; | |||
var desc = $('#card-desc').val().trim(); | |||
if (desc) variant['描述'] = desc; | |||
var derived = $('#card-derived').val().trim(); | |||
if (derived) variant['衍生卡牌'] = derived; | |||
return { | |||
name: $('#card-name').val().trim(), | |||
variants: [variant] | |||
}; | |||
} | |||
// 设置表单数据 | |||
function setFormData(card, variantIndex) { | |||
variantIndex = variantIndex || 0; | |||
$('#card-name').val(card.name); | |||
if (variantIndex < card.variants.length) { | |||
var variant = card.variants[variantIndex]; | |||
var isVariant = variant['卡组'] === '灵光一闪'; | |||
// 设置字段是否可编辑 | |||
$('#card-name').prop('disabled', isVariant); | |||
$('#card-art').prop('disabled', isVariant); | |||
$('.card-dropdown-container').eq(1).find('input').prop('disabled', isVariant); | |||
$('.card-dropdown-container').eq(2).find('input').prop('disabled', isVariant); | |||
$('#card-derived').prop('disabled', isVariant); | |||
// 设置值 | |||
$('#card-art').val(variant.art || ''); | |||
$('.card-dropdown-container').eq(0).find('input').val(variant['卡组'] || ''); | |||
$('.card-dropdown-container').eq(1).find('input').val(variant['属性'] || ''); | |||
$('.card-dropdown-container').eq(2).find('input').val(variant['稀有度'] || ''); | |||
$('#card-ap').val(variant.AP !== undefined ? variant.AP : ''); | |||
$('.card-dropdown-container').eq(3).find('input').val(variant['类型'] || ''); | |||
$('#card-mechanism').val(variant['机制'] || ''); | |||
$('#card-desc').val(variant['描述'] || ''); | |||
$('#card-derived').val(variant['衍生卡牌'] || ''); | |||
} | |||
} | |||
// 清空表单 | |||
function clearForm() { | |||
$('#card-name').val('').prop('disabled', false); | |||
$('#card-art').val('').prop('disabled', false); | |||
$('#card-ap').val(''); | |||
$('#card-mechanism').val(''); | |||
$('#card-desc').val(''); | |||
$('#card-derived').val('').prop('disabled', false); | |||
$('.card-dropdown-input').val('').prop('disabled', false); | |||
// | cardData.currentCard = null; | ||
cardData.currentVariantIndex = null; | |||
} | |||
} | // 添加卡牌 | ||
function addCard() { | |||
var data = getFormData(); | |||
if (!data.name) { | |||
mw.notify('卡牌名称不能为空!', {type: 'warn'}); | |||
return; | |||
} | |||
cardData.cards.push(data); | |||
cardData.currentCard = data; | |||
cardData.currentVariantIndex = 0; | |||
updateCardList(); | |||
} | updateVariantList(); | ||
updateCode(); | |||
clearForm(); | |||
mw.notify('卡牌添加成功!', {type: 'success'}); | |||
} | |||
// 添加变体 | |||
function addVariant() { | |||
if (!cardData.currentCard) { | |||
mw.notify('请先选择一个卡牌!', {type: 'warn'}); | |||
return; | |||
} | |||
var data = getFormData(); | |||
var variant = data.variants[0]; | |||
variant['卡组'] = '灵光一闪'; | |||
cardData.currentCard.variants.push(variant); | |||
cardData.currentVariantIndex = cardData.currentCard.variants.length - 1; | |||
updateVariantList(); | |||
updateCode(); | |||
mw.notify('变体添加成功!', {type: 'success'}); | |||
} | |||
// 保存数据 | |||
function saveData() { | |||
} | if (!cardData.currentCard || cardData.currentVariantIndex === null) { | ||
mw.notify('请先选择要保存的卡牌或变体!', {type: 'warn'}); | |||
return; | |||
} | |||
var data = getFormData(); | |||
var isVariant = cardData.currentVariantIndex > 0; | |||
if (isVariant) { | |||
var variant = data.variants[0]; | |||
variant['卡组'] = '灵光一闪'; | variant['卡组'] = '灵光一闪'; | ||
cardData.currentCard.variants[cardData.currentVariantIndex] = variant; | |||
} else { | |||
cardData.currentCard.name = data.name; | |||
cardData.currentCard.variants[0] = data.variants[0]; | |||
} | |||
updateCardList(); | |||
mw.notify( | updateVariantList(); | ||
}, | updateCode(); | ||
mw.notify('数据保存成功!', {type: 'success'}); | |||
} | |||
// 删除卡牌 | |||
function deleteCard() { | |||
if (!cardData.currentCard) { | |||
mw.notify('请先选择要删除的卡牌!', {type: 'warn'}); | |||
return; | |||
} | |||
if (!confirm('确定要删除卡牌 "' + cardData.currentCard.name + '" 吗?')) { | |||
return; | |||
} | |||
var index = cardData.cards.indexOf(cardData.currentCard); | |||
if (index > -1) { | |||
cardData.cards.splice(index, 1); | |||
} | |||
cardData.currentCard = null; | |||
cardData.currentVariantIndex = null; | |||
updateCardList(); | |||
updateVariantList(); | |||
updateCode(); | |||
clearForm(); | |||
mw.notify('卡牌删除成功!', {type: 'success'}); | |||
} | |||
// 删除变体 | |||
function deleteVariant() { | |||
if (!cardData.currentCard || cardData.currentVariantIndex === null) { | |||
mw.notify('请先选择要删除的变体!', {type: 'warn'}); | |||
return; | |||
} | |||
if (cardData.currentVariantIndex === 0) { | |||
mw.notify('不能删除主卡牌变体!', {type: 'warn'}); | |||
return; | |||
} | |||
if (!confirm('确定要删除这个变体吗?')) { | |||
return; | |||
} | |||
cardData.currentCard.variants.splice(cardData.currentVariantIndex, 1); | |||
cardData.currentVariantIndex = 0; | |||
updateVariantList(); | |||
updateCode(); | |||
setFormData(cardData.currentCard, 0); | |||
mw.notify('变体删除成功!', {type: 'success'}); | |||
} | |||
// 更新卡牌列表 | |||
function updateCardList() { | |||
var list = $('#card-list').empty(); | |||
cardData.cards.forEach(function(card, index) { | |||
var item = $('<div>') | |||
.addClass('card-list-item') | |||
.attr('data-index', index) | |||
.text(card.name) | |||
.click(function() { | |||
onCardSelected(index); | |||
}); | |||
if (cardData.currentCard === card) { | |||
item.addClass('card-list-item-selected'); | |||
} | } | ||
list.append(item); | |||
}); | |||
this. | } | ||
// 更新变体列表 | |||
} | function updateVariantList() { | ||
var list = $('#variant-list').empty(); | |||
if (cardData.currentCard) { | |||
cardData.currentCard.variants.forEach(function(variant, index) { | |||
var deck = variant['卡组'] || '未知'; | |||
var text = index === 0 ? '主卡牌 (' + deck + ')' : '变体 ' + index + ' (灵光一闪)'; | |||
var item = $('<div>') | |||
.addClass('card-list-item') | |||
.text(text) | |||
.click(function() { | |||
onVariantSelected(index); | |||
}); | |||
if (cardData.currentVariantIndex === index) { | |||
item.addClass('card-list-item-selected'); | |||
} | |||
list.append(item); | |||
}); | |||
} | |||
} | |||
// 卡牌选择事件 | |||
function onCardSelected(index) { | |||
cardData.currentCard = cardData.cards[index]; | |||
cardData.currentVariantIndex = 0; | |||
updateCardList(); | |||
updateVariantList(); | |||
setFormData(cardData.currentCard, 0); | |||
} | |||
// 变体选择事件 | |||
function onVariantSelected(index) { | |||
if (!cardData.currentCard) return; | |||
cardData.currentVariantIndex = index; | |||
updateVariantList(); | |||
setFormData(cardData.currentCard, index); | |||
} | |||
// 更新卡牌顺序 | |||
function updateCardOrder() { | |||
var newCards = []; | |||
$('#card-list .card-list-item').each(function() { | |||
var index = $(this).attr('data-index'); | |||
newCards.push(cardData.cards[index]); | |||
}); | |||
cardData.cards = newCards; | |||
updateCardList(); | |||
updateCode(); | |||
} | |||
// 转义Lua字符串 | |||
function escapeLuaString(str) { | |||
if (typeof str !== 'string') return str; | |||
return str.replace(/\\/g, '\\\\') | |||
.replace(/"/g, '\\"') | |||
.replace(/\n/g, '\\n'); | |||
} | |||
// 生成Lua代码 | |||
function generateLuaCode() { | |||
if (cardData.cards.length === 0) { | |||
return '-- 暂无数据'; | |||
} | |||
var lua = 'local p = {}\n\n'; | |||
// | // 生成 cardOrder(不包含衍生卡牌) | ||
lua += 'local cardOrder = {\n'; | |||
if ( | cardData.cards.forEach(function(card) { | ||
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') { | |||
lua += ' "' + escapeLuaString(card.name) + '",\n'; | |||
} | } | ||
}); | |||
lua += '}\n\n'; | |||
// | // 生成 card 数据 | ||
lua += 'local card = {\n'; | |||
cardData.cards.forEach(function(card) { | |||
lua += ' ["' + escapeLuaString(card.name) + '"] = {\n'; | |||
card.variants.forEach(function(variant) { | |||
lua += ' {\n'; | |||
if ( | // 按固定顺序输出字段 | ||
fieldDefinitions.cardOrder.forEach(function(field) { | |||
} | if (variant.hasOwnProperty(field) && variant[field] !== null && variant[field] !== '') { | ||
var value = variant[field]; | |||
if (typeof value === 'string') { | |||
lua += ' ["' + field + '"] = "' + escapeLuaString(value) + '",\n'; | |||
} else if (typeof value === 'number') { | |||
lua += ' ["' + field + '"] = ' + value + ',\n'; | |||
} | |||
} | |||
}); | |||
lua += ' },\n'; | |||
}); | }); | ||
} | |||
lua += ' },\n'; | |||
}); | |||
// | lua += '}\n\n'; | ||
lua += 'p.card = card\n'; | |||
lua += 'p.cardOrder = cardOrder\n\n'; | |||
if (! | lua += 'return p\n'; | ||
return lua; | |||
} | |||
// 更新代码显示 | |||
function updateCode() { | |||
var code = generateLuaCode(); | |||
$('#code-display').val(code); | |||
} | |||
// 复制代码 | |||
function copyCode() { | |||
var code = $('#code-display'); | |||
code.select(); | |||
document.execCommand('copy'); | |||
mw.notify('代码已复制到剪贴板!', {type: 'success'}); | |||
} | |||
// 从Wiki加载数据 | |||
function loadFromWiki() { | |||
mw.notify('正在加载数据...', {type: 'info'}); | |||
// 获取所有模块:卡牌/开头的页面 | |||
new mw.Api().get({ | |||
action: 'query', | |||
list: 'allpages', | |||
apprefix: '卡牌/', | |||
apnamespace: 828, // Module namespace | |||
aplimit: 500 | |||
}).done(function(data) { | |||
if (!data.query || !data.query.allpages || data.query.allpages.length === 0) { | |||
mw.notify('未找到卡牌模块!', {type: 'warn'}); | |||
return; | |||
} | |||
var pages = data.query.allpages; | |||
var loadedCards = []; | |||
var loadCount = 0; | |||
pages.forEach(function(page) { | |||
var pageName = page.title; | |||
// 加载每个模块的内容 | |||
new mw.Api().get({ | |||
action: 'query', | |||
prop: 'revisions', | |||
titles: pageName, | |||
rvprop: 'content', | |||
rvslots: 'main' | |||
}).done(function(moduleData) { | |||
loadCount++; | |||
var pageId = Object.keys(moduleData.query.pages)[0]; | |||
var content = moduleData.query.pages[pageId].revisions[0].slots.main['*']; | |||
if ( | // 解析Lua内容 | ||
var parsed = parseLuaContent(content); | |||
if (parsed && parsed.length > 0) { | |||
loadedCards = loadedCards.concat(parsed); | |||
} | } | ||
// 所有页面加载完成 | |||
if (loadCount === pages.length) { | |||
cardData.cards = loadedCards; | |||
cardData.currentCard = null; | |||
cardData.currentVariantIndex = null; | |||
updateCardList(); | |||
updateVariantList(); | |||
updateCode(); | |||
clearForm(); | |||
mw.notify('成功加载 ' + loadedCards.length + ' 张卡牌!', {type: 'success'}); | |||
} | |||
}); | }); | ||
} | }); | ||
}, | }).fail(function() { | ||
mw.notify('加载失败!', {type: 'error'}); | |||
}); | |||
} | |||
// 解析Lua内容 | |||
function parseLuaContent(content) { | |||
var cards = []; | |||
// | try { | ||
// 提取 cardOrder | |||
if ( | var cardOrderMatch = content.match(/local\s+cardOrder\s*=\s*\{([^}]+)\}/s); | ||
var cardOrder = []; | |||
if (cardOrderMatch) { | |||
var orderContent = cardOrderMatch[1]; | |||
var nameMatches = orderContent.match(/"([^"]+)"/g); | |||
if (nameMatches) { | |||
cardOrder = nameMatches.map(function(m) { return m.slice(1, -1); }); | |||
} | |||
} | } | ||
// 提取 card 表 | |||
var cardTableMatch = content.match(/local\s+card\s*=\s*\{(.+)\}\s*p\.card/s); | |||
if (!cardTableMatch) return cards; | |||
// | var cardContent = cardTableMatch[1]; | ||
// 提取所有卡牌名称 | |||
if ( | var allCardNames = []; | ||
var namePattern = /\["([^"]+)"\]\s*=\s*\{/g; | |||
var match; | |||
while ((match = namePattern.exec(cardContent)) !== null) { | |||
var name = match[1]; | |||
if (allCardNames.indexOf(name) === -1) { | |||
allCardNames.push(name); | |||
} | |||
} | |||
// 合并cardOrder和实际卡牌名称 | |||
var finalCardNames = []; | |||
cardOrder.forEach(function(name) { | |||
if (allCardNames.indexOf(name) !== -1) { | |||
finalCardNames.push(name); | |||
allCardNames.splice(allCardNames.indexOf(name), 1); | |||
} | } | ||
}); | }); | ||
finalCardNames = finalCardNames.concat(allCardNames); | |||
// | // 解析每个卡牌 | ||
finalCardNames.forEach(function(cardName) { | |||
var variants = extractCardVariants(cardContent, cardName); | |||
if (variants.length > 0) { | |||
cards.push({ | |||
name: cardName, | |||
variants: variants | |||
}); | }); | ||
} | |||
}); | }); | ||
} catch (e) { | |||
console.error('解析错误:', e); | |||
} | |||
return cards; | |||
} | |||
// 提取卡牌变体 | |||
function extractCardVariants(content, cardName) { | |||
var variants = []; | |||
var escapedName = cardName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |||
var pattern = new RegExp('\\["' + escapedName + '"\\]\\s*=\\s*\\{', 'g'); | |||
var match = pattern.exec(content); | |||
if (!match) return variants; | |||
var startPos = match.index + match[0].length; | |||
var cardBlock = extractBalancedBraces(content, startPos); | |||
if (!cardBlock) return variants; | |||
// | // 解析所有变体 | ||
var pos = 0; | |||
if ( | while (pos < cardBlock.length) { | ||
while (pos < cardBlock.length && /\s/.test(cardBlock[pos])) pos++; | |||
if (pos >= cardBlock.length) break; | |||
if (cardBlock[pos] === '{') { | |||
var variantContent = extractBalancedBraces(cardBlock, pos + 1); | |||
if (variantContent) { | |||
var variant = parseVariant(variantContent); | |||
if (Object.keys(variant).length > 0) { | |||
variants.push(variant); | |||
} | |||
pos += variantContent.length + 2; | |||
} else { | |||
pos++; | |||
} | |||
} else { | |||
pos++; | |||
} | } | ||
} | |||
return variants; | |||
} | |||
// 提取平衡的大括号内容 | |||
function extractBalancedBraces(text, startPos) { | |||
var braceCount = 1; | |||
var pos = startPos; | |||
var inString = false; | |||
var escapeNext = false; | |||
var result = []; | |||
while (pos < text.length && braceCount > 0) { | |||
var char = text[pos]; | |||
if (escapeNext) { | |||
result.push(char); | |||
escapeNext = false; | |||
pos++; | |||
continue; | |||
} | |||
if (char === '\\' && inString) { | |||
result.push(char); | |||
escapeNext = true; | |||
pos++; | |||
continue; | |||
} | |||
if (char === '"') { | |||
inString = !inString; | |||
result.push(char); | |||
pos++; | |||
continue; | |||
} | |||
if (!inString) { | |||
if (char === '{') { | |||
braceCount++; | |||
} else if (char === '}') { | |||
braceCount--; | |||
if (braceCount === 0) break; | |||
} | |||
} | |||
result.push(char); | |||
pos++; | |||
} | |||
return braceCount === 0 ? result.join('') : null; | |||
} | |||
// 解析变体 | |||
function parseVariant(content) { | |||
var variant = {}; | |||
var pos = 0; | |||
// | while (pos < content.length) { | ||
while (pos < content.length && /[\s,]/.test(content[pos])) pos++; | |||
if (pos >= content.length) break; | |||
if (content[pos] === '[') { | |||
var fieldMatch = content.substring(pos).match(/\["([^"]+)"\]\s*=\s*/); | |||
if (fieldMatch) { | |||
if ( | var fieldName = fieldMatch[1]; | ||
pos += fieldMatch[0].length; | |||
if ( | |||
var result = extractFieldValue(content, pos); | |||
if (result.value !== null) { | |||
variant[fieldName] = result.value; | |||
pos += result.length; | |||
} else { | |||
pos++; | |||
} | } | ||
} else { | |||
pos++; | |||
} | } | ||
} else { | |||
pos++; | |||
} | } | ||
}, | } | ||
return variant; | |||
} | |||
// 提取字段值 | |||
function extractFieldValue(text, startPos) { | |||
var pos = startPos; | |||
while (pos < text.length && /\s/.test(text[pos])) pos++; | |||
if (pos >= text.length) return {value: null, length: 0}; | |||
// 字符串值 | |||
if (text[pos] === '"') { | |||
pos++; | |||
var valueChars = []; | |||
var escapeNext = false; | |||
while (pos < text.length) { | |||
var char = text[pos]; | |||
while (pos < | |||
if ( | if (escapeNext) { | ||
if (char === 'n') valueChars.push('\n'); | |||
else if (char === 't') valueChars.push('\t'); | |||
else if (char === '\\') valueChars.push('\\'); | |||
else if (char === '"') valueChars.push('"'); | |||
else valueChars.push(char); | |||
escapeNext = false; | |||
pos++; | pos++; | ||
continue; | continue; | ||
} | } | ||
if (char === '\\' | if (char === '\\') { | ||
escapeNext = true; | |||
pos++; | pos++; | ||
continue; | continue; | ||
| 第748行: | 第846行: | ||
if (char === '"') { | if (char === '"') { | ||
pos++; | |||
break; | |||
} | } | ||
valueChars.push(char); | |||
pos++; | |||
} | |||
return { | |||
value: valueChars.join(''), | |||
length: pos - startPos | |||
}; | |||
} | |||
// 数字或其他值 | |||
else { | |||
var valueMatch = text.substring(pos).match(/([^,\n}\]]+)/); | |||
if (valueMatch) { | |||
var valueStr = valueMatch[1].trim(); | |||
} | var length = valueMatch[0].length; | ||
if (/^\d+$/.test(valueStr) || /^-\d+$/.test(valueStr)) { | |||
return {value: parseInt(valueStr), length: length}; | |||
} else { | |||
return {value: valueStr, length: length}; | |||
} | } | ||
} | |||
} | |||
return {value: null, length: 0}; | |||
} | |||
// 保存到Wiki | |||
function saveToWiki() { | |||
if (cardData.cards.length === 0) { | |||
mw.notify('没有数据可保存!', {type: 'warn'}); | |||
return; | |||
} | |||
if (!confirm('确定要保存数据到Wiki吗?这将覆盖现有的模块页面!')) { | |||
return; | |||
} | |||
mw.notify('正在保存数据...', {type: 'info'}); | |||
// 按角色名分组卡牌 | |||
var groupedCards = {}; | |||
cardData.cards.forEach(function(card) { | |||
// 这里需要用户手动指定角色名,简化处理,可以提示用户 | |||
// 暂时使用第一个卡牌的art文件名推测角色 | |||
var characterName = prompt('请为卡牌 "' + card.name + '" 指定角色名:', ''); | |||
if (!characterName) { | |||
characterName = '未分类'; | |||
} | } | ||
if (!groupedCards[characterName]) { | |||
} | groupedCards[characterName] = []; | ||
} | |||
groupedCards[characterName].push(card); | |||
}); | |||
// 保存每个角色的模块 | |||
var saveCount = 0; | |||
var totalModules = Object.keys(groupedCards).length; | |||
Object.keys(groupedCards).forEach(function(characterName) { | |||
var cards = groupedCards[characterName]; | |||
var luaCode = generateLuaCodeForCharacter(cards); | |||
var pageName = '模块:卡牌/' + characterName; | |||
new mw.Api().postWithToken('csrf', { | |||
action: 'edit', | |||
title: pageName, | |||
text: luaCode, | |||
summary: '通过卡牌编辑器更新', | |||
contentmodel: 'Scribunto' | |||
}).done(function() { | |||
saveCount++; | |||
if (saveCount === totalModules) { | |||
mw.notify('所有数据保存成功!', {type: 'success'}); | |||
} | |||
}).fail(function() { | |||
mw.notify('保存 ' + pageName + ' 失败!', {type: 'error'}); | |||
}); | |||
}); | |||
} | |||
// 为特定角色生成Lua代码 | |||
function generateLuaCodeForCharacter(cards) { | |||
var lua = 'local p = {}\n\n'; | |||
lua += 'local cardOrder = {\n'; | |||
cards.forEach(function(card) { | |||
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') { | |||
lua += ' "' + escapeLuaString(card.name) + '",\n'; | |||
} | |||
}); | |||
lua += '}\n\n'; | |||
lua += 'local card = {\n'; | |||
cards.forEach(function(card) { | |||
lua += ' ["' + escapeLuaString(card.name) + '"] = {\n'; | |||
card.variants.forEach(function(variant) { | |||
lua += ' {\n'; | |||
if ( | fieldDefinitions.cardOrder.forEach(function(field) { | ||
if (variant.hasOwnProperty(field) && variant[field] !== null && variant[field] !== '') { | |||
var value = variant[field]; | |||
if (typeof value === 'string') { | |||
lua += ' ["' + field + '"] = "' + escapeLuaString(value) + '",\n'; | |||
} else if (typeof value === 'number') { | |||
lua += ' ["' + field + '"] = ' + value + ',\n'; | |||
} | |||
} | } | ||
} | }); | ||
lua += ' },\n'; | |||
} | }); | ||
return | lua += ' },\n'; | ||
}); | |||
} | lua += '}\n\n'; | ||
lua += 'p.card = card\n'; | |||
lua += 'p.cardOrder = cardOrder\n\n'; | |||
lua += 'return p\n'; | |||
return lua; | |||
} | |||
// 初始化 | |||
$(function() { | |||
initUI(); | |||
}); | }); | ||
})(); | })(); | ||
2025年10月2日 (四) 10:32的版本
// MediaWiki:Card.js
// 卡牌管理系统
(function() {
'use strict';
// 检查是否在正确的页面
if (mw.config.get('wgPageName') !== 'MediaWiki:Card') {
return;
}
// 卡牌数据存储
var cardData = {
cards: [],
currentCard: null,
currentVariantIndex: null
};
// 字段定义
var fieldDefinitions = {
cardOrder: ['art', '卡组', '属性', '稀有度', 'AP', '机制', '类型', '描述', '衍生卡牌'],
deckTypes: ['', '起始卡牌', '独特卡牌', '灵光一闪', '衍生卡牌', '神光一闪'],
attributes: ['', '热情', '秩序', '正义', '本能', '虚无'],
rarities: ['', '白', '蓝', '橙', '彩'],
types: ['', '攻击', '技能', '强化', '状态异常']
};
// 创建下拉选择框
function createDropdown(options, value, placeholder) {
var container = $('<div>').addClass('card-dropdown-container');
var input = $('<input>')
.addClass('card-dropdown-input')
.attr('placeholder', placeholder || '点击选择或输入...')
.val(value || '');
var dropdown = $('<div>').addClass('card-dropdown-list').hide();
options.forEach(function(opt) {
$('<div>')
.addClass('card-dropdown-item')
.text(opt || '(空)')
.click(function() {
input.val(opt);
dropdown.hide();
})
.appendTo(dropdown);
});
input.on('focus click', function(e) {
e.stopPropagation();
$('.card-dropdown-list').hide();
dropdown.show();
});
$(document).click(function() {
dropdown.hide();
});
container.append(input, dropdown);
return container;
}
// 创建表单字段
function createFormField(label, inputElement) {
return $('<div>').addClass('card-form-field')
.append($('<div>').addClass('card-form-label').text(label))
.append($('<div>').addClass('card-form-input').append(inputElement));
}
// 初始化界面
function initUI() {
var container = $('<div>').attr('id', 'card-editor-container');
// 左侧:输入区
var leftPanel = $('<div>').addClass('card-panel card-input-panel');
var formGroup = $('<div>').addClass('card-group').append($('<div>').addClass('card-group-title').text('卡牌数据'));
// 创建表单字段
var nameInput = $('<input>').attr('id', 'card-name').attr('placeholder', '请输入卡牌名称...');
formGroup.append(createFormField('卡牌名称:', nameInput));
var deckDropdown = createDropdown(fieldDefinitions.deckTypes, '', '点击选择或输入...');
formGroup.append(createFormField('卡组类型:', deckDropdown));
var artContainer = $('<div>').addClass('card-art-container');
var artInput = $('<input>').attr('id', 'card-art').attr('placeholder', '输入图片文件名...');
artContainer.append(artInput);
formGroup.append(createFormField('图片文件:', artContainer));
var attrDropdown = createDropdown(fieldDefinitions.attributes, '', '点击选择...');
formGroup.append(createFormField('属性:', attrDropdown));
var rarityDropdown = createDropdown(fieldDefinitions.rarities, '', '点击选择...');
formGroup.append(createFormField('稀有度:', rarityDropdown));
var apInput = $('<input>').attr('id', 'card-ap').attr('placeholder', '输入AP数值...');
formGroup.append(createFormField('AP (行动点):', apInput));
var typeDropdown = createDropdown(fieldDefinitions.types, '', '点击选择...');
formGroup.append(createFormField('卡牌类型:', typeDropdown));
var mechanismInput = $('<input>').attr('id', 'card-mechanism').attr('placeholder', '请输入卡牌机制...');
formGroup.append(createFormField('卡牌机制:', mechanismInput));
// 描述区域
var descContainer = $('<div>').addClass('card-desc-container');
var descHeader = $('<div>').addClass('card-desc-header')
.append($('<span>').text('卡牌描述:'))
.append($('<div>').addClass('card-desc-buttons')
.append($('<div>').addClass('card-button card-button-small card-button-blue').text('蓝色文本').click(function() { insertTextFormat('蓝'); }))
.append($('<div>').addClass('card-button card-button-small card-button-green').text('绿色文本').click(function() { insertTextFormat('绿'); }))
.append($('<div>').addClass('card-button card-button-small card-button-stroke').text('绿色描边').click(insertStrokeFormat))
.append($('<div>').addClass('card-button card-button-small card-button-br').text('插入换行').click(insertBr))
);
var descTextarea = $('<textarea>').attr('id', 'card-desc').attr('placeholder', '请输入卡牌描述...');
descContainer.append(descHeader, descTextarea);
formGroup.append(descContainer);
var derivedInput = $('<input>').attr('id', 'card-derived').attr('placeholder', '请输入衍生卡牌...');
formGroup.append(createFormField('衍生卡牌:', derivedInput));
leftPanel.append(formGroup);
// 按钮区
var buttonContainer = $('<div>').addClass('card-button-container');
buttonContainer.append(
$('<div>').addClass('card-button card-button-add').text('添加卡牌').click(addCard),
$('<div>').addClass('card-button card-button-add').text('添加变体').click(addVariant),
$('<div>').addClass('card-button').text('保存数据').click(saveData),
$('<div>').addClass('card-button').text('清空表单').click(clearForm)
);
leftPanel.append(buttonContainer);
// 中间:列表区
var middlePanel = $('<div>').addClass('card-panel card-list-panel');
var cardListGroup = $('<div>').addClass('card-group')
.append($('<div>').addClass('card-group-title').text('卡牌列表'))
.append($('<div>').attr('id', 'card-list').addClass('card-list'));
middlePanel.append(cardListGroup);
var variantListGroup = $('<div>').addClass('card-group')
.append($('<div>').addClass('card-group-title').text('变体列表'))
.append($('<div>').attr('id', 'variant-list').addClass('card-list'));
middlePanel.append(variantListGroup);
var deleteButtons = $('<div>').addClass('card-button-container');
deleteButtons.append(
$('<div>').addClass('card-button card-button-delete').text('删除卡牌').click(deleteCard),
$('<div>').addClass('card-button card-button-delete').text('删除变体').click(deleteVariant)
);
middlePanel.append(deleteButtons);
// 右侧:代码显示区
var rightPanel = $('<div>').addClass('card-panel card-code-panel');
var codeGroup = $('<div>').addClass('card-group')
.append($('<div>').addClass('card-group-title').text('Lua代码预览'))
.append($('<textarea>').attr('id', 'code-display').addClass('card-code-display').attr('readonly', true));
rightPanel.append(codeGroup);
var codeButtons = $('<div>').addClass('card-button-container');
codeButtons.append(
$('<div>').addClass('card-button').text('复制代码').click(copyCode),
$('<div>').addClass('card-button').text('加载数据').click(loadFromWiki),
$('<div>').addClass('card-button').text('保存到Wiki').click(saveToWiki)
);
rightPanel.append(codeButtons);
container.append(leftPanel, middlePanel, rightPanel);
$('#mw-content-text').empty().append(container);
// 初始化拖拽排序
initSortable();
}
// 初始化拖拽排序
function initSortable() {
mw.loader.using('jquery.ui', function() {
$('#card-list').sortable({
update: function(event, ui) {
updateCardOrder();
}
});
});
}
// 文本格式化函数
function insertTextFormat(color) {
var textarea = $('#card-desc')[0];
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var text = textarea.value;
var selectedText = text.substring(start, end);
var newText = text.substring(0, start) +
'{{文本|' + color + '|' + selectedText + '}}' +
text.substring(end);
textarea.value = newText;
textarea.focus();
textarea.setSelectionRange(start + 6 + color.length, start + 6 + color.length + selectedText.length);
}
function insertStrokeFormat() {
var textarea = $('#card-desc')[0];
var start = textarea.selectionStart;
var end = textarea.selectionEnd;
var text = textarea.value;
var selectedText = text.substring(start, end);
var newText = text.substring(0, start) +
'{{描边|绿|' + selectedText + '}}' +
text.substring(end);
textarea.value = newText;
textarea.focus();
textarea.setSelectionRange(start + 7, start + 7 + selectedText.length);
}
function insertBr() {
var textarea = $('#card-desc')[0];
var pos = textarea.selectionStart;
var text = textarea.value;
var newText = text.substring(0, pos) + '<br>' + text.substring(pos);
textarea.value = newText;
textarea.focus();
textarea.setSelectionRange(pos + 4, pos + 4);
}
// 获取表单数据
function getFormData() {
var variant = {};
var art = $('#card-art').val().trim();
if (art) variant.art = art;
var deck = $('.card-dropdown-container').eq(0).find('input').val().trim();
if (deck) variant['卡组'] = deck;
var attr = $('.card-dropdown-container').eq(1).find('input').val().trim();
if (attr) variant['属性'] = attr;
var rarity = $('.card-dropdown-container').eq(2).find('input').val().trim();
if (rarity) variant['稀有度'] = rarity;
var ap = $('#card-ap').val().trim();
if (ap) {
variant.AP = isNaN(ap) ? ap : parseInt(ap);
}
var type = $('.card-dropdown-container').eq(3).find('input').val().trim();
if (type) variant['类型'] = type;
var mechanism = $('#card-mechanism').val().trim();
if (mechanism) variant['机制'] = mechanism;
var desc = $('#card-desc').val().trim();
if (desc) variant['描述'] = desc;
var derived = $('#card-derived').val().trim();
if (derived) variant['衍生卡牌'] = derived;
return {
name: $('#card-name').val().trim(),
variants: [variant]
};
}
// 设置表单数据
function setFormData(card, variantIndex) {
variantIndex = variantIndex || 0;
$('#card-name').val(card.name);
if (variantIndex < card.variants.length) {
var variant = card.variants[variantIndex];
var isVariant = variant['卡组'] === '灵光一闪';
// 设置字段是否可编辑
$('#card-name').prop('disabled', isVariant);
$('#card-art').prop('disabled', isVariant);
$('.card-dropdown-container').eq(1).find('input').prop('disabled', isVariant);
$('.card-dropdown-container').eq(2).find('input').prop('disabled', isVariant);
$('#card-derived').prop('disabled', isVariant);
// 设置值
$('#card-art').val(variant.art || '');
$('.card-dropdown-container').eq(0).find('input').val(variant['卡组'] || '');
$('.card-dropdown-container').eq(1).find('input').val(variant['属性'] || '');
$('.card-dropdown-container').eq(2).find('input').val(variant['稀有度'] || '');
$('#card-ap').val(variant.AP !== undefined ? variant.AP : '');
$('.card-dropdown-container').eq(3).find('input').val(variant['类型'] || '');
$('#card-mechanism').val(variant['机制'] || '');
$('#card-desc').val(variant['描述'] || '');
$('#card-derived').val(variant['衍生卡牌'] || '');
}
}
// 清空表单
function clearForm() {
$('#card-name').val('').prop('disabled', false);
$('#card-art').val('').prop('disabled', false);
$('#card-ap').val('');
$('#card-mechanism').val('');
$('#card-desc').val('');
$('#card-derived').val('').prop('disabled', false);
$('.card-dropdown-input').val('').prop('disabled', false);
cardData.currentCard = null;
cardData.currentVariantIndex = null;
}
// 添加卡牌
function addCard() {
var data = getFormData();
if (!data.name) {
mw.notify('卡牌名称不能为空!', {type: 'warn'});
return;
}
cardData.cards.push(data);
cardData.currentCard = data;
cardData.currentVariantIndex = 0;
updateCardList();
updateVariantList();
updateCode();
clearForm();
mw.notify('卡牌添加成功!', {type: 'success'});
}
// 添加变体
function addVariant() {
if (!cardData.currentCard) {
mw.notify('请先选择一个卡牌!', {type: 'warn'});
return;
}
var data = getFormData();
var variant = data.variants[0];
variant['卡组'] = '灵光一闪';
cardData.currentCard.variants.push(variant);
cardData.currentVariantIndex = cardData.currentCard.variants.length - 1;
updateVariantList();
updateCode();
mw.notify('变体添加成功!', {type: 'success'});
}
// 保存数据
function saveData() {
if (!cardData.currentCard || cardData.currentVariantIndex === null) {
mw.notify('请先选择要保存的卡牌或变体!', {type: 'warn'});
return;
}
var data = getFormData();
var isVariant = cardData.currentVariantIndex > 0;
if (isVariant) {
var variant = data.variants[0];
variant['卡组'] = '灵光一闪';
cardData.currentCard.variants[cardData.currentVariantIndex] = variant;
} else {
cardData.currentCard.name = data.name;
cardData.currentCard.variants[0] = data.variants[0];
}
updateCardList();
updateVariantList();
updateCode();
mw.notify('数据保存成功!', {type: 'success'});
}
// 删除卡牌
function deleteCard() {
if (!cardData.currentCard) {
mw.notify('请先选择要删除的卡牌!', {type: 'warn'});
return;
}
if (!confirm('确定要删除卡牌 "' + cardData.currentCard.name + '" 吗?')) {
return;
}
var index = cardData.cards.indexOf(cardData.currentCard);
if (index > -1) {
cardData.cards.splice(index, 1);
}
cardData.currentCard = null;
cardData.currentVariantIndex = null;
updateCardList();
updateVariantList();
updateCode();
clearForm();
mw.notify('卡牌删除成功!', {type: 'success'});
}
// 删除变体
function deleteVariant() {
if (!cardData.currentCard || cardData.currentVariantIndex === null) {
mw.notify('请先选择要删除的变体!', {type: 'warn'});
return;
}
if (cardData.currentVariantIndex === 0) {
mw.notify('不能删除主卡牌变体!', {type: 'warn'});
return;
}
if (!confirm('确定要删除这个变体吗?')) {
return;
}
cardData.currentCard.variants.splice(cardData.currentVariantIndex, 1);
cardData.currentVariantIndex = 0;
updateVariantList();
updateCode();
setFormData(cardData.currentCard, 0);
mw.notify('变体删除成功!', {type: 'success'});
}
// 更新卡牌列表
function updateCardList() {
var list = $('#card-list').empty();
cardData.cards.forEach(function(card, index) {
var item = $('<div>')
.addClass('card-list-item')
.attr('data-index', index)
.text(card.name)
.click(function() {
onCardSelected(index);
});
if (cardData.currentCard === card) {
item.addClass('card-list-item-selected');
}
list.append(item);
});
}
// 更新变体列表
function updateVariantList() {
var list = $('#variant-list').empty();
if (cardData.currentCard) {
cardData.currentCard.variants.forEach(function(variant, index) {
var deck = variant['卡组'] || '未知';
var text = index === 0 ? '主卡牌 (' + deck + ')' : '变体 ' + index + ' (灵光一闪)';
var item = $('<div>')
.addClass('card-list-item')
.text(text)
.click(function() {
onVariantSelected(index);
});
if (cardData.currentVariantIndex === index) {
item.addClass('card-list-item-selected');
}
list.append(item);
});
}
}
// 卡牌选择事件
function onCardSelected(index) {
cardData.currentCard = cardData.cards[index];
cardData.currentVariantIndex = 0;
updateCardList();
updateVariantList();
setFormData(cardData.currentCard, 0);
}
// 变体选择事件
function onVariantSelected(index) {
if (!cardData.currentCard) return;
cardData.currentVariantIndex = index;
updateVariantList();
setFormData(cardData.currentCard, index);
}
// 更新卡牌顺序
function updateCardOrder() {
var newCards = [];
$('#card-list .card-list-item').each(function() {
var index = $(this).attr('data-index');
newCards.push(cardData.cards[index]);
});
cardData.cards = newCards;
updateCardList();
updateCode();
}
// 转义Lua字符串
function escapeLuaString(str) {
if (typeof str !== 'string') return str;
return str.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"')
.replace(/\n/g, '\\n');
}
// 生成Lua代码
function generateLuaCode() {
if (cardData.cards.length === 0) {
return '-- 暂无数据';
}
var lua = 'local p = {}\n\n';
// 生成 cardOrder(不包含衍生卡牌)
lua += 'local cardOrder = {\n';
cardData.cards.forEach(function(card) {
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') {
lua += ' "' + escapeLuaString(card.name) + '",\n';
}
});
lua += '}\n\n';
// 生成 card 数据
lua += 'local card = {\n';
cardData.cards.forEach(function(card) {
lua += ' ["' + escapeLuaString(card.name) + '"] = {\n';
card.variants.forEach(function(variant) {
lua += ' {\n';
// 按固定顺序输出字段
fieldDefinitions.cardOrder.forEach(function(field) {
if (variant.hasOwnProperty(field) && variant[field] !== null && variant[field] !== '') {
var value = variant[field];
if (typeof value === 'string') {
lua += ' ["' + field + '"] = "' + escapeLuaString(value) + '",\n';
} else if (typeof value === 'number') {
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;
}
// 更新代码显示
function updateCode() {
var code = generateLuaCode();
$('#code-display').val(code);
}
// 复制代码
function copyCode() {
var code = $('#code-display');
code.select();
document.execCommand('copy');
mw.notify('代码已复制到剪贴板!', {type: 'success'});
}
// 从Wiki加载数据
function loadFromWiki() {
mw.notify('正在加载数据...', {type: 'info'});
// 获取所有模块:卡牌/开头的页面
new mw.Api().get({
action: 'query',
list: 'allpages',
apprefix: '卡牌/',
apnamespace: 828, // Module namespace
aplimit: 500
}).done(function(data) {
if (!data.query || !data.query.allpages || data.query.allpages.length === 0) {
mw.notify('未找到卡牌模块!', {type: 'warn'});
return;
}
var pages = data.query.allpages;
var loadedCards = [];
var loadCount = 0;
pages.forEach(function(page) {
var pageName = page.title;
// 加载每个模块的内容
new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: pageName,
rvprop: 'content',
rvslots: 'main'
}).done(function(moduleData) {
loadCount++;
var pageId = Object.keys(moduleData.query.pages)[0];
var content = moduleData.query.pages[pageId].revisions[0].slots.main['*'];
// 解析Lua内容
var parsed = parseLuaContent(content);
if (parsed && parsed.length > 0) {
loadedCards = loadedCards.concat(parsed);
}
// 所有页面加载完成
if (loadCount === pages.length) {
cardData.cards = loadedCards;
cardData.currentCard = null;
cardData.currentVariantIndex = null;
updateCardList();
updateVariantList();
updateCode();
clearForm();
mw.notify('成功加载 ' + loadedCards.length + ' 张卡牌!', {type: 'success'});
}
});
});
}).fail(function() {
mw.notify('加载失败!', {type: 'error'});
});
}
// 解析Lua内容
function parseLuaContent(content) {
var cards = [];
try {
// 提取 cardOrder
var cardOrderMatch = content.match(/local\s+cardOrder\s*=\s*\{([^}]+)\}/s);
var cardOrder = [];
if (cardOrderMatch) {
var orderContent = cardOrderMatch[1];
var nameMatches = orderContent.match(/"([^"]+)"/g);
if (nameMatches) {
cardOrder = nameMatches.map(function(m) { return m.slice(1, -1); });
}
}
// 提取 card 表
var cardTableMatch = content.match(/local\s+card\s*=\s*\{(.+)\}\s*p\.card/s);
if (!cardTableMatch) return cards;
var cardContent = cardTableMatch[1];
// 提取所有卡牌名称
var allCardNames = [];
var namePattern = /\["([^"]+)"\]\s*=\s*\{/g;
var match;
while ((match = namePattern.exec(cardContent)) !== null) {
var name = match[1];
if (allCardNames.indexOf(name) === -1) {
allCardNames.push(name);
}
}
// 合并cardOrder和实际卡牌名称
var finalCardNames = [];
cardOrder.forEach(function(name) {
if (allCardNames.indexOf(name) !== -1) {
finalCardNames.push(name);
allCardNames.splice(allCardNames.indexOf(name), 1);
}
});
finalCardNames = finalCardNames.concat(allCardNames);
// 解析每个卡牌
finalCardNames.forEach(function(cardName) {
var variants = extractCardVariants(cardContent, cardName);
if (variants.length > 0) {
cards.push({
name: cardName,
variants: variants
});
}
});
} catch (e) {
console.error('解析错误:', e);
}
return cards;
}
// 提取卡牌变体
function extractCardVariants(content, cardName) {
var variants = [];
var escapedName = cardName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
var pattern = new RegExp('\\["' + escapedName + '"\\]\\s*=\\s*\\{', 'g');
var match = pattern.exec(content);
if (!match) return variants;
var startPos = match.index + match[0].length;
var cardBlock = extractBalancedBraces(content, startPos);
if (!cardBlock) return variants;
// 解析所有变体
var pos = 0;
while (pos < cardBlock.length) {
while (pos < cardBlock.length && /\s/.test(cardBlock[pos])) pos++;
if (pos >= cardBlock.length) break;
if (cardBlock[pos] === '{') {
var variantContent = extractBalancedBraces(cardBlock, pos + 1);
if (variantContent) {
var variant = parseVariant(variantContent);
if (Object.keys(variant).length > 0) {
variants.push(variant);
}
pos += variantContent.length + 2;
} else {
pos++;
}
} else {
pos++;
}
}
return variants;
}
// 提取平衡的大括号内容
function extractBalancedBraces(text, startPos) {
var braceCount = 1;
var pos = startPos;
var inString = false;
var escapeNext = false;
var result = [];
while (pos < text.length && braceCount > 0) {
var char = text[pos];
if (escapeNext) {
result.push(char);
escapeNext = false;
pos++;
continue;
}
if (char === '\\' && inString) {
result.push(char);
escapeNext = true;
pos++;
continue;
}
if (char === '"') {
inString = !inString;
result.push(char);
pos++;
continue;
}
if (!inString) {
if (char === '{') {
braceCount++;
} else if (char === '}') {
braceCount--;
if (braceCount === 0) break;
}
}
result.push(char);
pos++;
}
return braceCount === 0 ? result.join('') : null;
}
// 解析变体
function parseVariant(content) {
var variant = {};
var pos = 0;
while (pos < content.length) {
while (pos < content.length && /[\s,]/.test(content[pos])) pos++;
if (pos >= content.length) break;
if (content[pos] === '[') {
var fieldMatch = content.substring(pos).match(/\["([^"]+)"\]\s*=\s*/);
if (fieldMatch) {
var fieldName = fieldMatch[1];
pos += fieldMatch[0].length;
var result = extractFieldValue(content, pos);
if (result.value !== null) {
variant[fieldName] = result.value;
pos += result.length;
} else {
pos++;
}
} else {
pos++;
}
} else {
pos++;
}
}
return variant;
}
// 提取字段值
function extractFieldValue(text, startPos) {
var pos = startPos;
while (pos < text.length && /\s/.test(text[pos])) pos++;
if (pos >= text.length) return {value: null, length: 0};
// 字符串值
if (text[pos] === '"') {
pos++;
var valueChars = [];
var escapeNext = false;
while (pos < text.length) {
var char = text[pos];
if (escapeNext) {
if (char === 'n') valueChars.push('\n');
else if (char === 't') valueChars.push('\t');
else if (char === '\\') valueChars.push('\\');
else if (char === '"') valueChars.push('"');
else valueChars.push(char);
escapeNext = false;
pos++;
continue;
}
if (char === '\\') {
escapeNext = true;
pos++;
continue;
}
if (char === '"') {
pos++;
break;
}
valueChars.push(char);
pos++;
}
return {
value: valueChars.join(''),
length: pos - startPos
};
}
// 数字或其他值
else {
var valueMatch = text.substring(pos).match(/([^,\n}\]]+)/);
if (valueMatch) {
var valueStr = valueMatch[1].trim();
var length = valueMatch[0].length;
if (/^\d+$/.test(valueStr) || /^-\d+$/.test(valueStr)) {
return {value: parseInt(valueStr), length: length};
} else {
return {value: valueStr, length: length};
}
}
}
return {value: null, length: 0};
}
// 保存到Wiki
function saveToWiki() {
if (cardData.cards.length === 0) {
mw.notify('没有数据可保存!', {type: 'warn'});
return;
}
if (!confirm('确定要保存数据到Wiki吗?这将覆盖现有的模块页面!')) {
return;
}
mw.notify('正在保存数据...', {type: 'info'});
// 按角色名分组卡牌
var groupedCards = {};
cardData.cards.forEach(function(card) {
// 这里需要用户手动指定角色名,简化处理,可以提示用户
// 暂时使用第一个卡牌的art文件名推测角色
var characterName = prompt('请为卡牌 "' + card.name + '" 指定角色名:', '');
if (!characterName) {
characterName = '未分类';
}
if (!groupedCards[characterName]) {
groupedCards[characterName] = [];
}
groupedCards[characterName].push(card);
});
// 保存每个角色的模块
var saveCount = 0;
var totalModules = Object.keys(groupedCards).length;
Object.keys(groupedCards).forEach(function(characterName) {
var cards = groupedCards[characterName];
var luaCode = generateLuaCodeForCharacter(cards);
var pageName = '模块:卡牌/' + characterName;
new mw.Api().postWithToken('csrf', {
action: 'edit',
title: pageName,
text: luaCode,
summary: '通过卡牌编辑器更新',
contentmodel: 'Scribunto'
}).done(function() {
saveCount++;
if (saveCount === totalModules) {
mw.notify('所有数据保存成功!', {type: 'success'});
}
}).fail(function() {
mw.notify('保存 ' + pageName + ' 失败!', {type: 'error'});
});
});
}
// 为特定角色生成Lua代码
function generateLuaCodeForCharacter(cards) {
var lua = 'local p = {}\n\n';
lua += 'local cardOrder = {\n';
cards.forEach(function(card) {
if (card.variants[0] && card.variants[0]['卡组'] !== '衍生卡牌') {
lua += ' "' + escapeLuaString(card.name) + '",\n';
}
});
lua += '}\n\n';
lua += 'local card = {\n';
cards.forEach(function(card) {
lua += ' ["' + escapeLuaString(card.name) + '"] = {\n';
card.variants.forEach(function(variant) {
lua += ' {\n';
fieldDefinitions.cardOrder.forEach(function(field) {
if (variant.hasOwnProperty(field) && variant[field] !== null && variant[field] !== '') {
var value = variant[field];
if (typeof value === 'string') {
lua += ' ["' + field + '"] = "' + escapeLuaString(value) + '",\n';
} else if (typeof value === 'number') {
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;
}
// 初始化
$(function() {
initUI();
});
})();