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。
/**
* MediaWiki 卡牌管理器
* 用于管理模块:卡牌/角色名的数据
*/
(function() {
'use strict';
const CardManager = {
cards: [],
currentCard: null,
currentVariantIndex: null,
characterName: '',
// 初始化
init: function() {
this.characterName = this.getCharacterName();
this.createUI();
this.bindEvents();
this.loadCardModule();
},
// 创建UI
createUI: function() {
const container = document.getElementById('card-manager-root');
if (!container) return;
container.innerHTML = `
<h2>卡牌数据管理器</h2>
<div class="cm-container">
<div class="cm-section cm-input-section">
<div class="cm-section-title">卡牌数据</div>
<div class="cm-form-group">
<div class="cm-label">卡牌名称:</div>
<input type="text" id="card-name" class="cm-input" placeholder="请输入卡牌名称...">
</div>
<div class="cm-form-group">
<div class="cm-label">卡组类型:</div>
<input type="text" id="deck-type" class="cm-input" list="deck-list" placeholder="请选择或输入...">
<datalist id="deck-list">
<option value="起始卡牌">
<option value="独特卡牌">
<option value="灵光一闪">
<option value="衍生卡牌">
</datalist>
</div>
<div class="cm-form-group">
<div class="cm-label">图片文件:</div>
<input type="text" id="card-art" class="cm-input" placeholder="如: unique_1003_01.png">
</div>
<div class="cm-form-group">
<div class="cm-label">属性:</div>
<input type="text" id="card-attr" class="cm-input" list="attr-list" placeholder="请选择或输入...">
<datalist id="attr-list">
<option value="热情">
<option value="秩序">
<option value="正义">
<option value="本能">
<option value="虚无">
</datalist>
</div>
<div class="cm-form-group">
<div class="cm-label">稀有度:</div>
<input type="text" id="card-rarity" class="cm-input" list="rarity-list" placeholder="请选择或输入...">
<datalist id="rarity-list">
<option value="白">
<option value="蓝">
<option value="橙">
<option value="彩">
</datalist>
</div>
<div class="cm-form-group">
<div class="cm-label">AP (行动点):</div>
<input type="text" id="card-ap" class="cm-input" placeholder="输入数字或X">
</div>
<div class="cm-form-group">
<div class="cm-label">卡牌类型:</div>
<input type="text" id="card-type" class="cm-input" list="type-list" placeholder="请选择或输入...">
<datalist id="type-list">
<option value="攻击">
<option value="技能">
<option value="强化">
<option value="状态异常">
</datalist>
</div>
<div class="cm-form-group">
<div class="cm-label">卡牌机制:</div>
<input type="text" id="card-mechanism" class="cm-input" placeholder="如:消灭">
</div>
<div class="cm-form-group">
<div class="cm-label">卡牌描述:</div>
<div class="cm-format-buttons">
<span class="cm-btn cm-btn-small cm-btn-blue" data-action="blue-text">蓝色文本</span>
<span class="cm-btn cm-btn-small cm-btn-green" data-action="green-text">绿色文本</span>
<span class="cm-btn cm-btn-small cm-btn-green" data-action="green-stroke">绿色描边</span>
<span class="cm-btn cm-btn-small cm-btn-orange" data-action="insert-br">插入<br></span>
</div>
<textarea id="card-desc" class="cm-textarea" placeholder="请输入卡牌描述..."></textarea>
</div>
<div class="cm-form-group">
<div class="cm-label">衍生卡牌:</div>
<input type="text" id="card-derived" class="cm-input" placeholder="请输入衍生卡牌名称">
</div>
<div class="cm-button-group">
<span class="cm-btn cm-btn-success" data-action="add-card">添加卡牌</span>
<span class="cm-btn cm-btn-success" data-action="add-variant">添加变体</span>
</div>
<div class="cm-button-group">
<span class="cm-btn" data-action="save-card">保存数据</span>
<span class="cm-btn" data-action="clear-form">清空表单</span>
</div>
</div>
<div class="cm-section cm-list-section">
<div class="cm-section-title">卡牌列表</div>
<div id="card-list" class="cm-list"></div>
<div class="cm-section-title" style="margin-top: 20px;">变体列表</div>
<div id="variant-list" class="cm-list"></div>
<div class="cm-button-group">
<span class="cm-btn cm-btn-danger" data-action="delete-card">删除卡牌</span>
<span class="cm-btn cm-btn-danger" data-action="delete-variant">删除变体</span>
</div>
</div>
<div class="cm-section cm-preview-section">
<div class="cm-section-title">Lua代码预览</div>
<pre id="code-preview" class="cm-code-preview">-- 暂无数据</pre>
<div class="cm-button-group">
<span class="cm-btn" data-action="export">导出到模块</span>
<span class="cm-btn" data-action="import">从模块导入</span>
</div>
</div>
</div>
`;
},
// 从页面标题获取角色名
getCharacterName: function() {
const match = mw.config.get('wgPageName').match(/模块:卡牌\/(.+)/);
return match ? match[1] : '';
},
// 绑定事件
bindEvents: function() {
const container = document.getElementById('card-manager-root');
if (!container) return;
// 按钮点击事件
container.addEventListener('click', (e) => {
const target = e.target;
const action = target.getAttribute('data-action');
if (!action) return;
switch(action) {
case 'add-card':
this.addCard();
break;
case 'add-variant':
this.addVariant();
break;
case 'save-card':
this.saveCard();
break;
case 'clear-form':
this.clearForm();
break;
case 'delete-card':
this.deleteCard();
break;
case 'delete-variant':
this.deleteVariant();
break;
case 'export':
this.exportToModule();
break;
case 'import':
this.importFromModule();
break;
case 'blue-text':
this.insertFormat('文本', '蓝');
break;
case 'green-text':
this.insertFormat('文本', '绿');
break;
case 'green-stroke':
this.insertFormat('描边', '绿');
break;
case 'insert-br':
this.insertBr();
break;
}
// 处理卡牌和变体选择
if (target.classList.contains('cm-card-item')) {
const index = parseInt(target.getAttribute('data-index'));
this.selectCard(index);
} else if (target.classList.contains('cm-variant-item')) {
const index = parseInt(target.getAttribute('data-index'));
this.selectVariant(index);
}
});
// 卡组类型变化
const deckType = document.getElementById('deck-type');
if (deckType) {
deckType.addEventListener('change', (e) => {
const isVariant = e.target.value === '灵光一闪';
this.toggleVariantFields(isVariant);
});
}
},
// 切换变体字段状态
toggleVariantFields: function(isVariant) {
const fields = ['card-name', 'card-art', 'card-attr', 'card-rarity', 'card-derived'];
fields.forEach(id => {
const el = document.getElementById(id);
if (el) el.disabled = isVariant;
});
},
// 插入格式
insertFormat: function(type, color) {
const textarea = document.getElementById('card-desc');
if (!textarea) return;
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');
if (!textarea) return;
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 = this.getInputValue('card-art');
if (art) variant.art = art;
const deck = this.getInputValue('deck-type');
if (deck) variant['卡组'] = deck;
const attr = this.getInputValue('card-attr');
if (attr) variant['属性'] = attr;
const rarity = this.getInputValue('card-rarity');
if (rarity) variant['稀有度'] = rarity;
const ap = this.getInputValue('card-ap');
if (ap) {
variant['AP'] = ap.toUpperCase() === 'X' ? 'X' : parseInt(ap);
}
const mechanism = this.getInputValue('card-mechanism');
if (mechanism) variant['机制'] = mechanism;
const cardType = this.getInputValue('card-type');
if (cardType) variant['类型'] = cardType;
const desc = this.getInputValue('card-desc');
if (desc) variant['描述'] = desc;
const derived = this.getInputValue('card-derived');
if (derived) variant['衍生卡牌'] = derived;
return {
name: this.getInputValue('card-name'),
variants: [variant]
};
},
// 获取输入值
getInputValue: function(id) {
const el = document.getElementById(id);
return el ? el.value.trim() : '';
},
// 设置输入值
setInputValue: function(id, value) {
const el = document.getElementById(id);
if (el) el.value = value || '';
},
// 设置表单数据
setFormData: function(card, variantIndex = 0) {
this.setInputValue('card-name', card.name);
if (variantIndex < card.variants.length) {
const variant = card.variants[variantIndex];
const isVariant = variant['卡组'] === '灵光一闪';
this.toggleVariantFields(isVariant);
this.setInputValue('card-art', variant.art);
this.setInputValue('deck-type', variant['卡组']);
this.setInputValue('card-attr', variant['属性']);
this.setInputValue('card-rarity', variant['稀有度']);
this.setInputValue('card-ap', variant['AP'] !== undefined ? variant['AP'] : '');
this.setInputValue('card-mechanism', variant['机制']);
this.setInputValue('card-type', variant['类型']);
this.setInputValue('card-desc', variant['描述']);
this.setInputValue('card-derived', variant['衍生卡牌']);
}
},
// 清空表单
clearForm: function() {
const fields = ['card-name', 'card-art', 'deck-type', 'card-attr', 'card-rarity',
'card-ap', 'card-mechanism', 'card-type', 'card-desc', 'card-derived'];
fields.forEach(id => this.setInputValue(id, ''));
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 = document.getElementById('card-list');
if (!list) return;
list.innerHTML = '';
this.cards.forEach((card, index) => {
const item = document.createElement('div');
item.className = 'cm-card-item';
item.setAttribute('data-index', index);
item.textContent = card.name;
if (this.currentCard === card) {
item.classList.add('cm-active');
}
list.appendChild(item);
});
},
// 更新变体列表
updateVariantList: function() {
const list = document.getElementById('variant-list');
if (!list) return;
list.innerHTML = '';
if (this.currentCard) {
this.currentCard.variants.forEach((variant, index) => {
const deck = variant['卡组'] || '未知';
const label = index === 0 ? `主卡牌 (${deck})` : `变体 ${index} (灵光一闪)`;
const item = document.createElement('div');
item.className = 'cm-variant-item';
item.setAttribute('data-index', index);
item.textContent = label;
if (this.currentVariantIndex === index) {
item.classList.add('cm-active');
}
list.appendChild(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();
const preview = document.getElementById('code-preview');
if (preview) preview.textContent = code;
},
// 导出到模块
exportToModule: function() {
if (this.cards.length === 0) {
mw.notify('没有数据可导出!', {type: 'error'});
return;
}
const code = this.generateLuaCode();
const moduleName = `模块:卡牌/${this.characterName}`;
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 = [];
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, ''));
});
}
}
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);
}
}
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;
}
};
mw.loader.using(['mediawiki.api', 'mediawiki.notify']).then(() => {
if (mw.config.get('wgPageName') === 'MediaWiki:Card') {
CardManager.init();
}
});
window.CardManager = CardManager;
})();