MediaWiki

MediaWiki:Card.js

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献2025年10月2日 (四) 10:19的版本

注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-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">插入&lt;br&gt;</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;
})();