Card.js:修订间差异
来自卡厄思梦境WIKI
无编辑摘要 |
无编辑摘要 |
||
| 第4行: | 第4行: | ||
// 加载CSS | // 加载CSS | ||
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css'); | mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css'); | ||
// 调试日志 | |||
console.log('Card Manager: 脚本已加载'); | |||
// 卡牌管理器类 | // 卡牌管理器类 | ||
class CardManager { | class CardManager { | ||
constructor() { | constructor() { | ||
console.log('Card Manager: 初始化中...'); | |||
this.currentFighter = ''; | this.currentFighter = ''; | ||
this.cards = {}; | this.cards = {}; | ||
this.currentCard = null; | this.currentCard = null; | ||
this.fighters = []; | this.fighters = ['示例战斗员1', '示例战斗员2']; // 默认战斗员列表 | ||
this.defaultData = { | this.defaultData = { | ||
order: [], | order: [], | ||
| 第19行: | 第22行: | ||
}; | }; | ||
this.cardData = { | this.cardData = { | ||
name: '', | name: '', | ||
| 第45行: | 第47行: | ||
} | } | ||
async init() { | async init() { | ||
console.log('Card Manager: 开始初始化'); | |||
await this.loadFighters(); | await this.loadFighters(); | ||
this.render(); | this.render(); | ||
console.log('Card Manager: 初始化完成'); | |||
} | } | ||
async loadFighters() { | async loadFighters() { | ||
try { | try { | ||
| 第63行: | 第64行: | ||
}); | }); | ||
this.fighters = response.query.categorymembers.map(page => page.title); | if (response.query && response.query.categorymembers) { | ||
this.fighters = response.query.categorymembers.map(page => page.title); | |||
console.log('Card Manager: 加载了', this.fighters.length, '个战斗员'); | |||
} | |||
} catch (error) { | } catch (error) { | ||
console.error('加载战斗员列表失败:', error) | console.error('Card Manager: 加载战斗员列表失败:', error); | ||
} | } | ||
} | } | ||
render() { | render() { | ||
const container = | console.log('Card Manager: 开始渲染'); | ||
// 获取内容容器 | |||
const contentDiv = document.getElementById('mw-content-text'); | |||
if (!contentDiv) { | |||
console.error('Card Manager: 找不到内容容器'); | |||
return; | |||
} | |||
// 清空现有内容 | |||
contentDiv.innerHTML = ''; | |||
// 创建主容器 | |||
const container = document.createElement('div'); | |||
container.className = 'card-manager'; | |||
// 左侧面板 | // 左侧面板 | ||
const leftPanel = | const leftPanel = document.createElement('div'); | ||
leftPanel.className = 'card-input-panel'; | |||
leftPanel.appendChild(this.renderFighterSelect()); | leftPanel.appendChild(this.renderFighterSelect()); | ||
leftPanel.appendChild(this.renderDefaultInfo()); | leftPanel.appendChild(this.renderDefaultInfo()); | ||
| 第81行: | 第98行: | ||
// 中间面板 | // 中间面板 | ||
const middlePanel = | const middlePanel = document.createElement('div'); | ||
middlePanel.className = 'card-list-panel'; | |||
middlePanel.appendChild(this.renderCardLists()); | middlePanel.appendChild(this.renderCardLists()); | ||
// 右侧面板 | // 右侧面板 | ||
const rightPanel = | const rightPanel = document.createElement('div'); | ||
rightPanel.className = 'card-preview-panel'; | |||
rightPanel.appendChild(this.renderPreview()); | rightPanel.appendChild(this.renderPreview()); | ||
| 第92行: | 第111行: | ||
container.appendChild(rightPanel); | container.appendChild(rightPanel); | ||
contentDiv.appendChild(container); | |||
console.log('Card Manager: 渲染完成'); | |||
} | } | ||
renderFighterSelect() { | renderFighterSelect() { | ||
const container = | const container = document.createElement('div'); | ||
container.className = 'form-group'; | |||
const label = | const label = document.createElement('div'); | ||
label.className = 'form-label'; | |||
label.textContent = '当前战斗员'; | |||
const selectContainer = document.createElement('div'); | |||
selectContainer.className = 'custom-select'; | |||
const display = document.createElement('div'); | |||
display.className = 'select-display'; | |||
display.textContent = this.currentFighter || '请选择战斗员...'; | |||
const arrow = document.createElement('span'); | |||
arrow.className = 'select-arrow'; | |||
arrow.textContent = '▼'; | |||
display.appendChild(arrow); | |||
const dropdown = document.createElement('div'); | |||
dropdown.className = 'select-dropdown'; | |||
const | this.fighters.forEach(fighter => { | ||
const option = document.createElement('div'); | |||
this.currentFighter | option.className = 'select-option'; | ||
( | option.textContent = fighter; | ||
this.currentFighter = | if (fighter === this.currentFighter) { | ||
option.classList.add('selected'); | |||
} | |||
option.onclick = () => { | |||
this.currentFighter = fighter; | |||
display.firstChild.textContent = fighter; | |||
dropdown.querySelectorAll('.select-option').forEach(o => o.classList.remove('selected')); | |||
option.classList.add('selected'); | |||
dropdown.classList.remove('active'); | |||
this.loadFighterData(); | this.loadFighterData(); | ||
} | }; | ||
); | dropdown.appendChild(option); | ||
}); | |||
display.onclick = (e) => { | |||
e.stopPropagation(); | |||
dropdown.classList.toggle('active'); | |||
}; | |||
document.addEventListener('click', () => { | |||
dropdown.classList.remove('active'); | |||
}); | |||
selectContainer.appendChild(display); | |||
selectContainer.appendChild(dropdown); | |||
container.appendChild(label); | |||
container.appendChild(selectContainer); | |||
return container; | return container; | ||
} | } | ||
renderDefaultInfo() { | renderDefaultInfo() { | ||
const container = | const container = document.createElement('div'); | ||
const title = | container.className = 'default-info-section'; | ||
const title = document.createElement('div'); | |||
title.className = 'section-title'; | |||
title.textContent = '默认信息'; | |||
container.appendChild(title); | container.appendChild(title); | ||
// 卡牌顺序 | // 卡牌顺序 | ||
const orderGroup = | const orderGroup = document.createElement('div'); | ||
const orderLabel = | orderGroup.className = 'form-group'; | ||
const orderInput = | |||
this.defaultData.order = | const orderLabel = document.createElement('div'); | ||
orderLabel.className = 'form-label'; | |||
orderLabel.textContent = '卡牌顺序(card.order)'; | |||
const orderInput = document.createElement('div'); | |||
orderInput.className = 'custom-input'; | |||
orderInput.contentEditable = true; | |||
orderInput.textContent = this.defaultData.order.join(','); | |||
orderInput.addEventListener('input', () => { | |||
this.defaultData.order = orderInput.textContent.split(',').map(s => s.trim()).filter(s => s); | |||
this.updatePreview(); | this.updatePreview(); | ||
}); | }); | ||
orderGroup.appendChild(orderLabel); | orderGroup.appendChild(orderLabel); | ||
orderGroup.appendChild(orderInput); | orderGroup.appendChild(orderInput); | ||
| 第146行: | 第206行: | ||
// 属性 | // 属性 | ||
const egoGroup = | const egoGroup = document.createElement('div'); | ||
const egoLabel = | egoGroup.className = 'form-group'; | ||
const egoInput = | |||
this.defaultData.ego = | const egoLabel = document.createElement('div'); | ||
egoLabel.className = 'form-label'; | |||
egoLabel.textContent = '属性(ego)'; | |||
const egoInput = document.createElement('div'); | |||
egoInput.className = 'custom-input'; | |||
egoInput.contentEditable = true; | |||
egoInput.textContent = this.defaultData.ego; | |||
egoInput.addEventListener('input', () => { | |||
this.defaultData.ego = egoInput.textContent; | |||
this.updatePreview(); | this.updatePreview(); | ||
}); | }); | ||
egoGroup.appendChild(egoLabel); | egoGroup.appendChild(egoLabel); | ||
egoGroup.appendChild(egoInput); | egoGroup.appendChild(egoInput); | ||
| 第159行: | 第229行: | ||
} | } | ||
renderCardInput() { | renderCardInput() { | ||
const container = | const container = document.createElement('div'); | ||
const title = | |||
const title = document.createElement('div'); | |||
title.className = 'section-title'; | |||
title.textContent = '卡牌数据'; | |||
container.appendChild(title); | container.appendChild(title); | ||
// 卡牌名称 | // 卡牌名称 | ||
container.appendChild(this. | container.appendChild(this.createInputField('卡牌名称', 'name')); | ||
// 显示名称 | // 显示名称 | ||
container.appendChild(this. | container.appendChild(this.createInputField('显示名称(displayname)', 'displayname')); | ||
// 图片 | // 图片 | ||
container.appendChild(this. | container.appendChild(this.createInputField('图片(art)', 'art')); | ||
// | // 卡组选择 | ||
container.appendChild( | container.appendChild(this.createGroupSelect()); | ||
// 稀有度 | // 稀有度 | ||
container.appendChild( | container.appendChild(this.createRaritySelect()); | ||
// 神明 | // 神明 | ||
container.appendChild( | container.appendChild(this.createGodSelect()); | ||
// AP | // AP | ||
container.appendChild(this. | container.appendChild(this.createInputField('AP', 'ap')); | ||
// 卡牌类型 | // 卡牌类型 | ||
container.appendChild( | container.appendChild(this.createTypeSelect()); | ||
// 机制 | // 机制 | ||
container.appendChild(this. | container.appendChild(this.createInputField('机制(dict)', 'dict')); | ||
// 描述 | // 描述 | ||
const descGroup = | const descGroup = document.createElement('div'); | ||
const descLabel = | descGroup.className = 'form-group'; | ||
const descLabel = document.createElement('div'); | |||
descLabel.className = 'form-label'; | |||
descLabel.textContent = '描述(desc_global)'; | |||
const formatButtons = this.createFormatButtons(); | |||
const descInput = document.createElement('div'); | |||
descInput.className = 'custom-textarea'; | |||
descInput.contentEditable = true; | |||
descInput.textContent = this.cardData.desc_global; | |||
descInput.id = 'desc-input'; | |||
descInput.addEventListener('input', () => { | |||
this.cardData.desc_global = descInput.textContent; | |||
this.updatePreview(); | |||
}); | |||
descGroup.appendChild(descLabel); | descGroup.appendChild(descLabel); | ||
descGroup.appendChild( | descGroup.appendChild(formatButtons); | ||
descGroup.appendChild( | descGroup.appendChild(descInput); | ||
container.appendChild(descGroup); | container.appendChild(descGroup); | ||
// 衍生卡牌 | // 衍生卡牌 | ||
container.appendChild(this. | container.appendChild(this.createInputField('衍生卡牌(sub)', 'sub')); | ||
// 是否存在灵光一闪 | // 是否存在灵光一闪 | ||
container.appendChild(this. | container.appendChild(this.createCheckboxField('是否存在灵光一闪', 'isinspiration')); | ||
// 是否存在神光一闪 | // 是否存在神光一闪 | ||
container.appendChild(this. | container.appendChild(this.createCheckboxField('是否存在神光一闪', 'isgod_inspiration')); | ||
// 按钮组 | // 按钮组 | ||
const btnGroup = | const btnGroup = document.createElement('div'); | ||
const saveBtn = | btnGroup.className = 'btn-group'; | ||
const saveBtn = document.createElement('div'); | |||
saveBtn.className = 'btn btn-primary'; | |||
saveBtn.textContent = '保存卡牌'; | |||
saveBtn.onclick = () => this.saveCard(); | saveBtn.onclick = () => this.saveCard(); | ||
const newBtn = | |||
const newBtn = document.createElement('div'); | |||
newBtn.className = 'btn btn-success'; | |||
newBtn.textContent = '新建卡牌'; | |||
newBtn.onclick = () => this.newCard(); | newBtn.onclick = () => this.newCard(); | ||
btnGroup.appendChild(saveBtn); | btnGroup.appendChild(saveBtn); | ||
btnGroup.appendChild(newBtn); | btnGroup.appendChild(newBtn); | ||
| 第273行: | 第319行: | ||
} | } | ||
createInputField(label, field) { | |||
const group = document.createElement('div'); | |||
const group = | group.className = 'form-group'; | ||
const labelDiv = | |||
const labelDiv = document.createElement('div'); | |||
labelDiv.className = 'form-label'; | |||
labelDiv.textContent = label; | |||
const input = document.createElement('div'); | |||
input.className = 'custom-input'; | |||
input.contentEditable = true; | |||
input.textContent = this.cardData[field] || ''; | |||
input.addEventListener('input', () => { | |||
this.cardData[field] = input.textContent; | |||
this.updatePreview(); | |||
}); | |||
group.appendChild(labelDiv); | group.appendChild(labelDiv); | ||
group.appendChild(input); | group.appendChild(input); | ||
return group; | return group; | ||
} | } | ||
createCheckboxField(label, field) { | |||
const group = document.createElement('div'); | |||
const | group.className = 'form-group'; | ||
const | const labelDiv = document.createElement('div'); | ||
const | labelDiv.className = 'form-label'; | ||
labelDiv.textContent = label; | |||
const checkboxContainer = document.createElement('div'); | |||
checkboxContainer.className = 'checkbox-group'; | |||
const | const checkbox = document.createElement('div'); | ||
checkbox.className = 'custom-checkbox'; | |||
if (this.cardData[field]) { | |||
checkbox.classList.add('checked'); | |||
} | |||
const statusLabel = document.createElement('div'); | |||
statusLabel.textContent = this.cardData[field] ? '是' : '否'; | |||
checkbox.onclick = () => { | |||
checkbox.classList.toggle('checked'); | |||
this.cardData[field] = checkbox.classList.contains('checked'); | |||
statusLabel.textContent = this.cardData[field] ? '是' : '否'; | |||
this.updatePreview(); | |||
}; | }; | ||
checkboxContainer.appendChild(checkbox); | |||
checkboxContainer.appendChild(statusLabel); | |||
group.appendChild(labelDiv); | |||
group.appendChild(checkboxContainer); | |||
return | return group; | ||
} | } | ||
createFormatButtons() { | createFormatButtons() { | ||
const container = | const container = document.createElement('div'); | ||
container.className = 'format-buttons'; | |||
const buttons = [ | const buttons = [ | ||
| 第355行: | 第391行: | ||
buttons.forEach(btn => { | buttons.forEach(btn => { | ||
const button = | const button = document.createElement('div'); | ||
button.className = 'format-btn ' + btn.class; | |||
button.textContent = btn.label; | |||
button.onclick = () => { | button.onclick = () => { | ||
const textarea = | const textarea = document.getElementById('desc-input'); | ||
this.insertTemplate(textarea, btn.template); | if (textarea) { | ||
this.insertTemplate(textarea, btn.template); | |||
} | |||
}; | }; | ||
container.appendChild(button); | container.appendChild(button); | ||
| 第366行: | 第406行: | ||
} | } | ||
insertTemplate(textarea, template) { | insertTemplate(textarea, template) { | ||
const selection = window.getSelection(); | const selection = window.getSelection(); | ||
| 第372行: | 第411行: | ||
if (template === '<br>') { | if (template === '<br>') { | ||
document.execCommand('insertHTML', false, '<br>'); | |||
} else { | } else { | ||
const result = template.replace('%s', selectedText || '选择文字'); | const result = template.replace('%s', selectedText || '选择文字'); | ||
document.execCommand('insertText', false, result); | |||
} | } | ||
textarea. | this.cardData.desc_global = textarea.textContent; | ||
this.updatePreview(); | |||
} | } | ||
createGroupSelect() { | |||
const group = document.createElement('div'); | |||
const | group.className = 'form-group'; | ||
const label = | const label = document.createElement('div'); | ||
label.className = 'form-label'; | |||
label.textContent = '卡组(group)'; | |||
const container = document.createElement('div'); | |||
container.className = 'group-options'; | |||
const groups = ['自我意识技能', '起始卡牌', '独特卡牌', '灵光一闪', '神光一闪']; | const groups = ['自我意识技能', '起始卡牌', '独特卡牌', '灵光一闪', '神光一闪']; | ||
groups.forEach( | groups.forEach(groupName => { | ||
const option = | const option = document.createElement('div'); | ||
if (this.cardData.group === | option.className = 'group-option'; | ||
option.textContent = groupName; | |||
if (this.cardData.group === groupName) { | |||
option.classList.add('selected'); | option.classList.add('selected'); | ||
} | } | ||
| 第435行: | 第444行: | ||
container.querySelectorAll('.group-option').forEach(o => o.classList.remove('selected')); | container.querySelectorAll('.group-option').forEach(o => o.classList.remove('selected')); | ||
option.classList.add('selected'); | option.classList.add('selected'); | ||
this.cardData.group = | this.cardData.group = groupName; | ||
this.updatePreview(); | this.updatePreview(); | ||
}; | }; | ||
| 第441行: | 第450行: | ||
}); | }); | ||
return | group.appendChild(label); | ||
group.appendChild(container); | |||
return group; | |||
} | } | ||
createRaritySelect() { | createRaritySelect() { | ||
const container = | const group = document.createElement('div'); | ||
group.className = 'form-group'; | |||
const label = document.createElement('div'); | |||
label.className = 'form-label'; | |||
label.textContent = '稀有度(rarity)'; | |||
const container = document.createElement('div'); | |||
container.className = 'rarity-options'; | |||
const rarities = ['白', '蓝', '橙', '彩']; | const rarities = ['白', '蓝', '橙', '彩']; | ||
rarities.forEach(rarity => { | rarities.forEach(rarity => { | ||
const option = | const option = document.createElement('div'); | ||
option.className = 'rarity-option'; | |||
option.textContent = rarity; | |||
if (this.cardData.rarity === rarity) { | if (this.cardData.rarity === rarity) { | ||
option.classList.add('selected'); | option.classList.add('selected'); | ||
| 第463行: | 第485行: | ||
}); | }); | ||
return | group.appendChild(label); | ||
group.appendChild(container); | |||
return group; | |||
} | } | ||
createGodSelect() { | createGodSelect() { | ||
const container = | const group = document.createElement('div'); | ||
group.className = 'form-group'; | |||
const label = document.createElement('div'); | |||
label.className = 'form-label'; | |||
label.textContent = '神明'; | |||
const container = document.createElement('div'); | |||
container.className = 'god-options'; | |||
const gods = ['circen', 'diallos', 'nihilum', 'secred', 'vitor']; | const gods = ['circen', 'diallos', 'nihilum', 'secred', 'vitor']; | ||
gods.forEach(god => { | gods.forEach(god => { | ||
const option = | const option = document.createElement('div'); | ||
option.className = 'god-option'; | |||
option.textContent = god; | |||
if (this.cardData.god === god) { | if (this.cardData.god === god) { | ||
option.classList.add('selected'); | option.classList.add('selected'); | ||
| 第485行: | 第520行: | ||
}); | }); | ||
return | group.appendChild(label); | ||
group.appendChild(container); | |||
return group; | |||
} | } | ||
createTypeSelect() { | createTypeSelect() { | ||
const container = | const group = document.createElement('div'); | ||
group.className = 'form-group'; | |||
const label = document.createElement('div'); | |||
label.className = 'form-label'; | |||
label.textContent = '卡牌类型(type)'; | |||
const container = document.createElement('div'); | |||
container.className = 'type-options'; | |||
const types = ['攻击', '技能', '强化']; | const types = ['攻击', '技能', '强化']; | ||
types.forEach(type => { | types.forEach(type => { | ||
const option = | const option = document.createElement('div'); | ||
option.className = 'type-option'; | |||
option.textContent = type; | |||
if (this.cardData.type === type) { | if (this.cardData.type === type) { | ||
option.classList.add('selected'); | option.classList.add('selected'); | ||
| 第507行: | 第555行: | ||
}); | }); | ||
return | group.appendChild(label); | ||
group.appendChild(container); | |||
return group; | |||
} | } | ||
renderCardLists() { | renderCardLists() { | ||
const container = | const container = document.createElement('div'); | ||
const title = document.createElement('div'); | |||
const | title.className = 'panel-title'; | ||
container.appendChild( | title.textContent = '卡牌列表'; | ||
container.appendChild(title); | |||
const | const listContainer = document.createElement('div'); | ||
listContainer.className = 'list-container'; | |||
this.updateCardList( | listContainer.id = 'card-list'; | ||
container.appendChild( | |||
this.updateCardList(listContainer); | |||
container.appendChild(listContainer); | |||
return container; | return container; | ||
} | } | ||
updateCardList(container) { | updateCardList(container) { | ||
container.innerHTML = ''; | container.innerHTML = ''; | ||
const cardNames = Object.keys(this.cards); | |||
if (cardNames.length === 0) { | |||
const empty = document.createElement('div'); | |||
empty.className = 'list-empty'; | |||
empty.textContent = '暂无卡牌'; | |||
container.appendChild(empty); | |||
return; | return; | ||
} | } | ||
cardNames.forEach(name => { | |||
const card = this.cards[name]; | const card = this.cards[name]; | ||
const item = | const item = document.createElement('div'); | ||
item.className = 'card-list-item'; | |||
const info = document.createElement('div'); | |||
const nameDiv = document.createElement('div'); | |||
nameDiv.className = 'card-list-item-name'; | |||
nameDiv.textContent = name; | |||
const detailDiv = document.createElement('div'); | |||
detailDiv.className = 'card-list-item-info'; | |||
detailDiv.textContent = `${card.type || '攻击'} | AP:${card.ap || 0} | ${card.rarity || ''}`; | |||
info.appendChild(nameDiv); | info.appendChild(nameDiv); | ||
info.appendChild(detailDiv); | info.appendChild(detailDiv); | ||
const deleteBtn = | const deleteBtn = document.createElement('div'); | ||
deleteBtn.className = 'delete-btn'; | |||
deleteBtn.textContent = '删除'; | |||
deleteBtn.onclick = (e) => { | deleteBtn.onclick = (e) => { | ||
e.stopPropagation(); | e.stopPropagation(); | ||
| 第554行: | 第620行: | ||
this.updateCardList(container); | this.updateCardList(container); | ||
this.updatePreview(); | this.updatePreview(); | ||
if (this.currentCard === name) { | if (this.currentCard === name) { | ||
this.newCard(); | this.newCard(); | ||
| 第575行: | 第639行: | ||
} | } | ||
renderPreview() { | renderPreview() { | ||
const container = | const container = document.createElement('div'); | ||
const title = | |||
const title = document.createElement('div'); | |||
title.className = 'panel-title'; | |||
title.textContent = 'Lua代码预览'; | |||
container.appendChild(title); | container.appendChild(title); | ||
const preview = | const preview = document.createElement('div'); | ||
preview.className = 'preview-code'; | |||
preview.id = 'code-preview'; | preview.id = 'code-preview'; | ||
container.appendChild(preview); | container.appendChild(preview); | ||
const copyBtn = document.createElement('div'); | |||
copyBtn.className = 'btn btn-primary'; | |||
copyBtn.textContent = '复制代码'; | |||
copyBtn.style.marginTop = '10px'; | copyBtn.style.marginTop = '10px'; | ||
copyBtn.onclick = () => this.copyCode(); | copyBtn.onclick = () => this.copyCode(); | ||
container.appendChild(copyBtn); | container.appendChild(copyBtn); | ||
this.updatePreview(); | |||
return container; | return container; | ||
} | } | ||
updatePreview() { | updatePreview() { | ||
const preview = document.getElementById('code-preview'); | const preview = document.getElementById('code-preview'); | ||
| 第604行: | 第671行: | ||
} | } | ||
generateLuaCode() { | generateLuaCode() { | ||
let code = 'local card = {}\n\n'; | let code = 'local card = {}\n\n'; | ||
if (this.defaultData.order.length > 0) { | if (this.defaultData.order.length > 0) { | ||
code += `card.order = { "${this.defaultData.order.join(',')}" }\n\n`; | code += `card.order = { "${this.defaultData.order.join(',')}" }\n\n`; | ||
} | } | ||
code += 'card.info = {\n'; | code += 'card.info = {\n'; | ||
if (this.defaultData.ego) { | if (this.defaultData.ego) { | ||
| 第620行: | 第684行: | ||
code += '}\n\n'; | code += '}\n\n'; | ||
Object.keys(this.cards).forEach(name => { | Object.keys(this.cards).forEach(name => { | ||
const card = this.cards[name]; | const card = this.cards[name]; | ||
| 第631行: | 第694行: | ||
} | } | ||
generateCardCode(name, card) { | generateCardCode(name, card) { | ||
let code = `card["${name}"] = {\n`; | let code = `card["${name}"] = {\n`; | ||
code += ' base = {\n'; | code += ' base = {\n'; | ||
if (card.displayname) code += ` displayname = "${card.displayname}",\n`; | |||
if (card.displayname) | if (card.art) code += ` art = "${card.art}",\n`; | ||
if (card.group) code += ` group = "${card.group}",\n`; | |||
if (card.rarity) code += ` rarity = "${card.rarity}",\n`; | |||
if (card.art) | if (card.ap) code += ` ap = ${isNaN(card.ap) ? `"${card.ap}"` : card.ap},\n`; | ||
if (card.type) code += ` type = "${card.type}",\n`; | |||
if (card.dict) code += ` dict = "${card.dict}",\n`; | |||
if (card.group) | |||
if (card.rarity) | |||
if (card.ap) | |||
if (card.type) | |||
if (card.dict) | |||
if (card.desc_global) { | if (card.desc_global) { | ||
const desc = card.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"'); | const desc = card.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"'); | ||
code += ` desc_global = "${desc}",\n`; | code += ` desc_global = "${desc}",\n`; | ||
} | } | ||
if (card.sub) | if (card.sub) code += ` sub = "${card.sub}",\n`; | ||
if (card.isinspiration) code += ` isinspiration = 1,\n`; | |||
if (card.isgod_inspiration) code += ` isgod_inspiration = 1,\n`; | |||
if (card.isinspiration) | |||
if (card.isgod_inspiration) | |||
code += ' },\n'; | code += ' },\n'; | ||
code += '}\n\n'; | code += '}\n\n'; | ||
| 第733行: | 第719行: | ||
} | } | ||
saveCard() { | saveCard() { | ||
if (!this.cardData.name) { | if (!this.cardData.name) { | ||
| 第740行: | 第725行: | ||
} | } | ||
this.cards[this.cardData.name] = JSON.parse(JSON.stringify(this.cardData)); | |||
this.currentCard = this.cardData.name; | |||
if (!this.defaultData.order.includes(this.cardData.name)) { | |||
this.defaultData.order.push(this.cardData.name); | |||
if (!this.defaultData.order.includes( | |||
this.defaultData.order.push( | |||
} | } | ||
const listContainer = document.getElementById('card-list'); | const listContainer = document.getElementById('card-list'); | ||
if (listContainer) { | if (listContainer) { | ||
| 第762行: | 第741行: | ||
} | } | ||
newCard() { | newCard() { | ||
this.currentCard = null; | this.currentCard = null; | ||
| 第789行: | 第767行: | ||
}; | }; | ||
this.render(); | |||
} | } | ||
loadCard(name) { | loadCard(name) { | ||
if (!this.cards[name]) return; | if (!this.cards[name]) return; | ||
| 第811行: | 第775行: | ||
this.currentCard = name; | this.currentCard = name; | ||
this.cardData = JSON.parse(JSON.stringify(this.cards[name])); | this.cardData = JSON.parse(JSON.stringify(this.cards[name])); | ||
this.render(); | |||
} | } | ||
async loadFighterData() { | async loadFighterData() { | ||
if (!this.currentFighter) return; | if (!this.currentFighter) return; | ||
| 第843行: | 第798行: | ||
if (page.revisions) { | if (page.revisions) { | ||
const content = page.revisions[0].slots.main['*']; | const content = page.revisions[0].slots.main['*']; | ||
console.log('Card Manager: 加载了战斗员数据'); | |||
} | } | ||
} catch (error) { | } catch (error) { | ||
console.error('加载战斗员数据失败:', error); | console.error('Card Manager: 加载战斗员数据失败:', error); | ||
} | } | ||
} | } | ||
copyCode() { | copyCode() { | ||
const code = this.generateLuaCode(); | const code = this.generateLuaCode(); | ||
const textarea = document.createElement('textarea'); | const textarea = document.createElement('textarea'); | ||
textarea.value = code; | textarea.value = code; | ||
| 第911行: | 第812行: | ||
textarea.style.opacity = '0'; | textarea.style.opacity = '0'; | ||
document.body.appendChild(textarea); | document.body.appendChild(textarea); | ||
textarea.select(); | textarea.select(); | ||
document.execCommand('copy'); | document.execCommand('copy'); | ||
document.body.removeChild(textarea); | document.body.removeChild(textarea); | ||
alert('代码已复制到剪贴板!'); | alert('代码已复制到剪贴板!'); | ||
} | } | ||
} | |||
// 初始化函数 | |||
function initCardManager() { | |||
console.log('Card Manager: 准备启动'); | |||
const manager = new CardManager(); | |||
manager.init(); | |||
} | } | ||
// | // 在页面加载完成后启动 | ||
if ( | if (document.readyState === 'loading') { | ||
document.addEventListener('DOMContentLoaded', () => { | |||
mw.loader.using(['mediawiki.api']).then(initCardManager); | |||
}); | }); | ||
} else { | |||
mw.loader.using(['mediawiki.api']).then(initCardManager); | |||
} | } | ||
})(); | })(); | ||
2025年10月23日 (四) 16:09的版本
(function() {
'use strict';
// 加载CSS
mw.loader.load('/index.php?title=Mediawiki:Card.css&action=raw&ctype=text/css', 'text/css');
// 调试日志
console.log('Card Manager: 脚本已加载');
// 卡牌管理器类
class CardManager {
constructor() {
console.log('Card Manager: 初始化中...');
this.currentFighter = '';
this.cards = {};
this.currentCard = null;
this.fighters = ['示例战斗员1', '示例战斗员2']; // 默认战斗员列表
this.defaultData = {
order: [],
ego: ''
};
this.cardData = {
name: '',
displayname: '',
art: '',
group: '',
rarity: '',
god: '',
ap: '',
type: '攻击',
dict: '',
desc_global: '',
sub: '',
isinspiration: false,
isgod_inspiration: false,
inspirations: [],
god_inspirations: {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
}
};
}
async init() {
console.log('Card Manager: 开始初始化');
await this.loadFighters();
this.render();
console.log('Card Manager: 初始化完成');
}
async loadFighters() {
try {
const api = new mw.Api();
const response = await api.get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:战斗员',
cmlimit: 500
});
if (response.query && response.query.categorymembers) {
this.fighters = response.query.categorymembers.map(page => page.title);
console.log('Card Manager: 加载了', this.fighters.length, '个战斗员');
}
} catch (error) {
console.error('Card Manager: 加载战斗员列表失败:', error);
}
}
render() {
console.log('Card Manager: 开始渲染');
// 获取内容容器
const contentDiv = document.getElementById('mw-content-text');
if (!contentDiv) {
console.error('Card Manager: 找不到内容容器');
return;
}
// 清空现有内容
contentDiv.innerHTML = '';
// 创建主容器
const container = document.createElement('div');
container.className = 'card-manager';
// 左侧面板
const leftPanel = document.createElement('div');
leftPanel.className = 'card-input-panel';
leftPanel.appendChild(this.renderFighterSelect());
leftPanel.appendChild(this.renderDefaultInfo());
leftPanel.appendChild(this.renderCardInput());
// 中间面板
const middlePanel = document.createElement('div');
middlePanel.className = 'card-list-panel';
middlePanel.appendChild(this.renderCardLists());
// 右侧面板
const rightPanel = document.createElement('div');
rightPanel.className = 'card-preview-panel';
rightPanel.appendChild(this.renderPreview());
container.appendChild(leftPanel);
container.appendChild(middlePanel);
container.appendChild(rightPanel);
contentDiv.appendChild(container);
console.log('Card Manager: 渲染完成');
}
renderFighterSelect() {
const container = document.createElement('div');
container.className = 'form-group';
const label = document.createElement('div');
label.className = 'form-label';
label.textContent = '当前战斗员';
const selectContainer = document.createElement('div');
selectContainer.className = 'custom-select';
const display = document.createElement('div');
display.className = 'select-display';
display.textContent = this.currentFighter || '请选择战斗员...';
const arrow = document.createElement('span');
arrow.className = 'select-arrow';
arrow.textContent = '▼';
display.appendChild(arrow);
const dropdown = document.createElement('div');
dropdown.className = 'select-dropdown';
this.fighters.forEach(fighter => {
const option = document.createElement('div');
option.className = 'select-option';
option.textContent = fighter;
if (fighter === this.currentFighter) {
option.classList.add('selected');
}
option.onclick = () => {
this.currentFighter = fighter;
display.firstChild.textContent = fighter;
dropdown.querySelectorAll('.select-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
dropdown.classList.remove('active');
this.loadFighterData();
};
dropdown.appendChild(option);
});
display.onclick = (e) => {
e.stopPropagation();
dropdown.classList.toggle('active');
};
document.addEventListener('click', () => {
dropdown.classList.remove('active');
});
selectContainer.appendChild(display);
selectContainer.appendChild(dropdown);
container.appendChild(label);
container.appendChild(selectContainer);
return container;
}
renderDefaultInfo() {
const container = document.createElement('div');
container.className = 'default-info-section';
const title = document.createElement('div');
title.className = 'section-title';
title.textContent = '默认信息';
container.appendChild(title);
// 卡牌顺序
const orderGroup = document.createElement('div');
orderGroup.className = 'form-group';
const orderLabel = document.createElement('div');
orderLabel.className = 'form-label';
orderLabel.textContent = '卡牌顺序(card.order)';
const orderInput = document.createElement('div');
orderInput.className = 'custom-input';
orderInput.contentEditable = true;
orderInput.textContent = this.defaultData.order.join(',');
orderInput.addEventListener('input', () => {
this.defaultData.order = orderInput.textContent.split(',').map(s => s.trim()).filter(s => s);
this.updatePreview();
});
orderGroup.appendChild(orderLabel);
orderGroup.appendChild(orderInput);
container.appendChild(orderGroup);
// 属性
const egoGroup = document.createElement('div');
egoGroup.className = 'form-group';
const egoLabel = document.createElement('div');
egoLabel.className = 'form-label';
egoLabel.textContent = '属性(ego)';
const egoInput = document.createElement('div');
egoInput.className = 'custom-input';
egoInput.contentEditable = true;
egoInput.textContent = this.defaultData.ego;
egoInput.addEventListener('input', () => {
this.defaultData.ego = egoInput.textContent;
this.updatePreview();
});
egoGroup.appendChild(egoLabel);
egoGroup.appendChild(egoInput);
container.appendChild(egoGroup);
return container;
}
renderCardInput() {
const container = document.createElement('div');
const title = document.createElement('div');
title.className = 'section-title';
title.textContent = '卡牌数据';
container.appendChild(title);
// 卡牌名称
container.appendChild(this.createInputField('卡牌名称', 'name'));
// 显示名称
container.appendChild(this.createInputField('显示名称(displayname)', 'displayname'));
// 图片
container.appendChild(this.createInputField('图片(art)', 'art'));
// 卡组选择
container.appendChild(this.createGroupSelect());
// 稀有度
container.appendChild(this.createRaritySelect());
// 神明
container.appendChild(this.createGodSelect());
// AP
container.appendChild(this.createInputField('AP', 'ap'));
// 卡牌类型
container.appendChild(this.createTypeSelect());
// 机制
container.appendChild(this.createInputField('机制(dict)', 'dict'));
// 描述
const descGroup = document.createElement('div');
descGroup.className = 'form-group';
const descLabel = document.createElement('div');
descLabel.className = 'form-label';
descLabel.textContent = '描述(desc_global)';
const formatButtons = this.createFormatButtons();
const descInput = document.createElement('div');
descInput.className = 'custom-textarea';
descInput.contentEditable = true;
descInput.textContent = this.cardData.desc_global;
descInput.id = 'desc-input';
descInput.addEventListener('input', () => {
this.cardData.desc_global = descInput.textContent;
this.updatePreview();
});
descGroup.appendChild(descLabel);
descGroup.appendChild(formatButtons);
descGroup.appendChild(descInput);
container.appendChild(descGroup);
// 衍生卡牌
container.appendChild(this.createInputField('衍生卡牌(sub)', 'sub'));
// 是否存在灵光一闪
container.appendChild(this.createCheckboxField('是否存在灵光一闪', 'isinspiration'));
// 是否存在神光一闪
container.appendChild(this.createCheckboxField('是否存在神光一闪', 'isgod_inspiration'));
// 按钮组
const btnGroup = document.createElement('div');
btnGroup.className = 'btn-group';
const saveBtn = document.createElement('div');
saveBtn.className = 'btn btn-primary';
saveBtn.textContent = '保存卡牌';
saveBtn.onclick = () => this.saveCard();
const newBtn = document.createElement('div');
newBtn.className = 'btn btn-success';
newBtn.textContent = '新建卡牌';
newBtn.onclick = () => this.newCard();
btnGroup.appendChild(saveBtn);
btnGroup.appendChild(newBtn);
container.appendChild(btnGroup);
return container;
}
createInputField(label, field) {
const group = document.createElement('div');
group.className = 'form-group';
const labelDiv = document.createElement('div');
labelDiv.className = 'form-label';
labelDiv.textContent = label;
const input = document.createElement('div');
input.className = 'custom-input';
input.contentEditable = true;
input.textContent = this.cardData[field] || '';
input.addEventListener('input', () => {
this.cardData[field] = input.textContent;
this.updatePreview();
});
group.appendChild(labelDiv);
group.appendChild(input);
return group;
}
createCheckboxField(label, field) {
const group = document.createElement('div');
group.className = 'form-group';
const labelDiv = document.createElement('div');
labelDiv.className = 'form-label';
labelDiv.textContent = label;
const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'checkbox-group';
const checkbox = document.createElement('div');
checkbox.className = 'custom-checkbox';
if (this.cardData[field]) {
checkbox.classList.add('checked');
}
const statusLabel = document.createElement('div');
statusLabel.textContent = this.cardData[field] ? '是' : '否';
checkbox.onclick = () => {
checkbox.classList.toggle('checked');
this.cardData[field] = checkbox.classList.contains('checked');
statusLabel.textContent = this.cardData[field] ? '是' : '否';
this.updatePreview();
};
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(statusLabel);
group.appendChild(labelDiv);
group.appendChild(checkboxContainer);
return group;
}
createFormatButtons() {
const container = document.createElement('div');
container.className = 'format-buttons';
const buttons = [
{ label: '蓝色文本', class: 'blue', template: '{{文本|蓝|%s}}' },
{ label: '绿色文本', class: 'green', template: '{{文本|绿|%s}}' },
{ label: '绿色描边', class: 'green', template: '{{描边|绿|%s}}' },
{ label: '词典', class: '', template: '{{词典|%s}}' },
{ label: '换行', class: '', template: '<br>' }
];
buttons.forEach(btn => {
const button = document.createElement('div');
button.className = 'format-btn ' + btn.class;
button.textContent = btn.label;
button.onclick = () => {
const textarea = document.getElementById('desc-input');
if (textarea) {
this.insertTemplate(textarea, btn.template);
}
};
container.appendChild(button);
});
return container;
}
insertTemplate(textarea, template) {
const selection = window.getSelection();
const selectedText = selection.toString();
if (template === '<br>') {
document.execCommand('insertHTML', false, '<br>');
} else {
const result = template.replace('%s', selectedText || '选择文字');
document.execCommand('insertText', false, result);
}
this.cardData.desc_global = textarea.textContent;
this.updatePreview();
}
createGroupSelect() {
const group = document.createElement('div');
group.className = 'form-group';
const label = document.createElement('div');
label.className = 'form-label';
label.textContent = '卡组(group)';
const container = document.createElement('div');
container.className = 'group-options';
const groups = ['自我意识技能', '起始卡牌', '独特卡牌', '灵光一闪', '神光一闪'];
groups.forEach(groupName => {
const option = document.createElement('div');
option.className = 'group-option';
option.textContent = groupName;
if (this.cardData.group === groupName) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.group-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.group = groupName;
this.updatePreview();
};
container.appendChild(option);
});
group.appendChild(label);
group.appendChild(container);
return group;
}
createRaritySelect() {
const group = document.createElement('div');
group.className = 'form-group';
const label = document.createElement('div');
label.className = 'form-label';
label.textContent = '稀有度(rarity)';
const container = document.createElement('div');
container.className = 'rarity-options';
const rarities = ['白', '蓝', '橙', '彩'];
rarities.forEach(rarity => {
const option = document.createElement('div');
option.className = 'rarity-option';
option.textContent = rarity;
if (this.cardData.rarity === rarity) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.rarity-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.rarity = rarity;
this.updatePreview();
};
container.appendChild(option);
});
group.appendChild(label);
group.appendChild(container);
return group;
}
createGodSelect() {
const group = document.createElement('div');
group.className = 'form-group';
const label = document.createElement('div');
label.className = 'form-label';
label.textContent = '神明';
const container = document.createElement('div');
container.className = 'god-options';
const gods = ['circen', 'diallos', 'nihilum', 'secred', 'vitor'];
gods.forEach(god => {
const option = document.createElement('div');
option.className = 'god-option';
option.textContent = god;
if (this.cardData.god === god) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.god-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.god = god;
this.updatePreview();
};
container.appendChild(option);
});
group.appendChild(label);
group.appendChild(container);
return group;
}
createTypeSelect() {
const group = document.createElement('div');
group.className = 'form-group';
const label = document.createElement('div');
label.className = 'form-label';
label.textContent = '卡牌类型(type)';
const container = document.createElement('div');
container.className = 'type-options';
const types = ['攻击', '技能', '强化'];
types.forEach(type => {
const option = document.createElement('div');
option.className = 'type-option';
option.textContent = type;
if (this.cardData.type === type) {
option.classList.add('selected');
}
option.onclick = () => {
container.querySelectorAll('.type-option').forEach(o => o.classList.remove('selected'));
option.classList.add('selected');
this.cardData.type = type;
this.updatePreview();
};
container.appendChild(option);
});
group.appendChild(label);
group.appendChild(container);
return group;
}
renderCardLists() {
const container = document.createElement('div');
const title = document.createElement('div');
title.className = 'panel-title';
title.textContent = '卡牌列表';
container.appendChild(title);
const listContainer = document.createElement('div');
listContainer.className = 'list-container';
listContainer.id = 'card-list';
this.updateCardList(listContainer);
container.appendChild(listContainer);
return container;
}
updateCardList(container) {
container.innerHTML = '';
const cardNames = Object.keys(this.cards);
if (cardNames.length === 0) {
const empty = document.createElement('div');
empty.className = 'list-empty';
empty.textContent = '暂无卡牌';
container.appendChild(empty);
return;
}
cardNames.forEach(name => {
const card = this.cards[name];
const item = document.createElement('div');
item.className = 'card-list-item';
const info = document.createElement('div');
const nameDiv = document.createElement('div');
nameDiv.className = 'card-list-item-name';
nameDiv.textContent = name;
const detailDiv = document.createElement('div');
detailDiv.className = 'card-list-item-info';
detailDiv.textContent = `${card.type || '攻击'} | AP:${card.ap || 0} | ${card.rarity || ''}`;
info.appendChild(nameDiv);
info.appendChild(detailDiv);
const deleteBtn = document.createElement('div');
deleteBtn.className = 'delete-btn';
deleteBtn.textContent = '删除';
deleteBtn.onclick = (e) => {
e.stopPropagation();
if (confirm(`确定要删除卡牌"${name}"吗?`)) {
delete this.cards[name];
this.updateCardList(container);
this.updatePreview();
if (this.currentCard === name) {
this.newCard();
}
}
};
item.appendChild(info);
item.appendChild(deleteBtn);
item.onclick = () => {
this.loadCard(name);
container.querySelectorAll('.card-list-item').forEach(i => i.classList.remove('active'));
item.classList.add('active');
};
container.appendChild(item);
});
}
renderPreview() {
const container = document.createElement('div');
const title = document.createElement('div');
title.className = 'panel-title';
title.textContent = 'Lua代码预览';
container.appendChild(title);
const preview = document.createElement('div');
preview.className = 'preview-code';
preview.id = 'code-preview';
container.appendChild(preview);
const copyBtn = document.createElement('div');
copyBtn.className = 'btn btn-primary';
copyBtn.textContent = '复制代码';
copyBtn.style.marginTop = '10px';
copyBtn.onclick = () => this.copyCode();
container.appendChild(copyBtn);
this.updatePreview();
return container;
}
updatePreview() {
const preview = document.getElementById('code-preview');
if (!preview) return;
preview.textContent = this.generateLuaCode();
}
generateLuaCode() {
let code = 'local card = {}\n\n';
if (this.defaultData.order.length > 0) {
code += `card.order = { "${this.defaultData.order.join(',')}" }\n\n`;
}
code += 'card.info = {\n';
if (this.defaultData.ego) {
code += ` ego = "${this.defaultData.ego}",\n`;
}
code += '}\n\n';
Object.keys(this.cards).forEach(name => {
const card = this.cards[name];
code += this.generateCardCode(name, card);
});
code += 'return card';
return code;
}
generateCardCode(name, card) {
let code = `card["${name}"] = {\n`;
code += ' base = {\n';
if (card.displayname) code += ` displayname = "${card.displayname}",\n`;
if (card.art) code += ` art = "${card.art}",\n`;
if (card.group) code += ` group = "${card.group}",\n`;
if (card.rarity) code += ` rarity = "${card.rarity}",\n`;
if (card.ap) code += ` ap = ${isNaN(card.ap) ? `"${card.ap}"` : card.ap},\n`;
if (card.type) code += ` type = "${card.type}",\n`;
if (card.dict) code += ` dict = "${card.dict}",\n`;
if (card.desc_global) {
const desc = card.desc_global.replace(/\n/g, '<br>').replace(/"/g, '\\"');
code += ` desc_global = "${desc}",\n`;
}
if (card.sub) code += ` sub = "${card.sub}",\n`;
if (card.isinspiration) code += ` isinspiration = 1,\n`;
if (card.isgod_inspiration) code += ` isgod_inspiration = 1,\n`;
code += ' },\n';
code += '}\n\n';
return code;
}
saveCard() {
if (!this.cardData.name) {
alert('请输入卡牌名称!');
return;
}
this.cards[this.cardData.name] = JSON.parse(JSON.stringify(this.cardData));
this.currentCard = this.cardData.name;
if (!this.defaultData.order.includes(this.cardData.name)) {
this.defaultData.order.push(this.cardData.name);
}
const listContainer = document.getElementById('card-list');
if (listContainer) {
this.updateCardList(listContainer);
}
this.updatePreview();
alert('卡牌保存成功!');
}
newCard() {
this.currentCard = null;
this.cardData = {
name: '',
displayname: '',
art: '',
group: '',
rarity: '',
god: '',
ap: '',
type: '攻击',
dict: '',
desc_global: '',
sub: '',
isinspiration: false,
isgod_inspiration: false,
inspirations: [],
god_inspirations: {
circen: [],
diallos: [],
nihilum: [],
secred: [],
vitor: []
}
};
this.render();
}
loadCard(name) {
if (!this.cards[name]) return;
this.currentCard = name;
this.cardData = JSON.parse(JSON.stringify(this.cards[name]));
this.render();
}
async loadFighterData() {
if (!this.currentFighter) return;
try {
const api = new mw.Api();
const moduleName = `模块:卡牌/${this.currentFighter}`;
const response = await api.get({
action: 'query',
prop: 'revisions',
titles: moduleName,
rvprop: 'content',
rvslots: 'main'
});
const pages = response.query.pages;
const page = Object.values(pages)[0];
if (page.revisions) {
const content = page.revisions[0].slots.main['*'];
console.log('Card Manager: 加载了战斗员数据');
}
} catch (error) {
console.error('Card Manager: 加载战斗员数据失败:', error);
}
}
copyCode() {
const code = this.generateLuaCode();
const textarea = document.createElement('textarea');
textarea.value = code;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
alert('代码已复制到剪贴板!');
}
}
// 初始化函数
function initCardManager() {
console.log('Card Manager: 准备启动');
const manager = new CardManager();
manager.init();
}
// 在页面加载完成后启动
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
mw.loader.using(['mediawiki.api']).then(initCardManager);
});
} else {
mw.loader.using(['mediawiki.api']).then(initCardManager);
}
})();