LLWiki正在建设中,欢迎加入我们

MediaWiki:Gadget-uploader-rewrite.js

来自LLWiki
跳转到导航 跳转到搜索

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

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera:前往菜单 → 设置(Mac为Opera → Preferences),然后隐私和安全 → 清除浏览数据 → 缓存的图片和文件
//<nowiki>
// 引自[[moegirl:user:東東君/js/uploader.js|moegirl:user:-{東東君}-/js/uploader.js]]
"use strict";

var api = new mw.Api();

/**
 * @param {object} options
 * @param {File} options.body
 * @param {string} options.fileName
 * @param {string} options.comment
 * @param {string} options.pageContent  
 */
function upload({ body, fileName, comment, pageContent }) {
    return new Promise(function (resolve, reject) {
        var data = {
            filename: fileName,
            comment: comment,
            text: pageContent,
            format: 'json',
            ignorewarnings: 1
        };

        if (typeof body == 'string') {
            data.url = body;
            data.action = 'upload';
            console.log('Upload by url: ' + body);
            api.postWithToken('csrf', data).then(resolve, reject);
        } else {
            api.upload(body, data).then(resolve, reject);
        }
    });
}

/**
 * @param {string[]} fileNames 
 */
function checkFileNames(fileNames) {
    return api.get({
        action: 'query',
        titles: fileNames.map(item => 'File:' + item).join('|'),
        converttitles: 1,
        formatversion: 2
    }).then(function (data) {
        var result = {};
        data.query.pages.forEach( function(item) {
            result[item.title.substring(5)] = !('missing' in item);
            return result;
        } );
        return result;
    });
}

$.when( $.ready, mw.loader.getScript( 'https://unpkg.com/[email protected]/dist/vue.min.js' ) ).then(function() {
    $(document.body).append('<div id="widget-fileUploader" style="display:none">');
  
    // 向“更多”菜单注入按钮
    $('#p-cactions ul').append('<li id="btn-fileUploader"><a title="上传文件">' + wgULS("批量上传文件", "批次上傳檔案") + '</a></li>');
    $('#btn-fileUploader').click(() => {
        $('#widget-fileUploader').fadeIn(200);
        $('#content').css('position', 'static');
    });

    const template = `
    <div id="widget-fileUploader" class="uploader-container">
        <input ref="fileInput" style="display:none" type="file" multiple="multiple" :accept="allowedFileTypes.map(item => '.' + item).join(',')" @change="addFileByFileSelector" />
        <div class="uploader-closeBtn" @click="hideWidget">×</div>
        <div class="uploader-body">
            <div class="uploader-fileList" @dragenter.prevent="() => {}" @dragover.prevent="() => {}" @drop.prevent="addFileByDropping">
                <div v-if="files.length === 0" key="hintMask" class="hintMask" @click="$refs.fileInput.click()">
                    <div class="hintText">` + wgULS("点此添加文件,或将文件拖拽至此", "點此添加檔案,或將檔案拖拽至此") + `</div>
                </div>
                <div v-for="(item, index) in files" :key="item.body.lastModified" class="item" :data-name="item.fileName" :data-selected="index === focusedFileIndex" title="单击选中文件,双击复制文件名" @click="focusFile(index)">
                    <img v-if="isImageFile(item.body)" :src="item.objectUrl" />
                    <div v-else class="unablePreviewHint">
                        <div>` + wgULS("不支持预览的文件类型", "不支援預覽的檔案類型") + `</div>
                        <div v-if="typeof item.body !== 'string'" class="type">Mimetype: {{ item.body.type }}</div>
                    </div>
                    <div class="removeBtn" @click.stop="files.splice(index, 1)">×</div>
                </div>
                <div v-if="files.length !== 0" class="item addFileBox" @click="$refs.fileInput.click()" />
            </div>
            <div class="uploader-panel">
                <div class="block">
                    <div class="input-container" title="上传后使用文件时的名字,要求不能和现有文件重复">
                        <span>` + wgULS("文件", "檔案") + `名:</span>
                        <input v-model.trim="form.fileName" />
                    </div>
                    <div class="input-container categoryInput" title="所有文件共享分类">
                        <span>` + wgULS("分 类", "分 類") + `:</span>
                        <input ref="categoryInput" v-model.trim="form.categoryInput" @input="loadCategoryHint" @keydown.enter="addCategory(form.categoryInput)" @keydown.up.prevent="handlerFor_categoryInput_wasKeyDowned" />
                        <div class="inputHint">` + wgULS("按下回车键添加分类", "按下確認鍵添加分類") + `</div>
                        <div ref="categoryHints" v-if="categoryHints.length !== 0" class="categoryHints" @keydown.enter="addCategory(categoryHints[categoryHintFocusedIndex])" @keydown.prevent="handlerFor_categoryHints_wasKeyDowned">
                            <div v-for="(item, index) in categoryHints" class="item" :data-selected="index === categoryHintFocusedIndex" @click="addCategory(item)">{{ item }}</div>
                        </div>
                    </div>
                    <div class="categories">
                        <div v-for="(item, index) in form.categories" class="item" title="点击删除分类" @click="form.categories.splice(index, 1)" >{{ item }}</div>
                    </div>
                </div>
                <div class="block">
                    <div class="input-container">
                        <span>角色名:</span>
                        <input v-model.trim="form.charaName" />
                    </div>
                    <div class="input-container">
                        <span>` + wgULS("源地址", "原位址") + `:</span>
                        <input v-model.trim="form.source" />
                    </div>
                    <div class="input-container" style="visibility:hidden">
                        <span>作 者:</span>
                        <input v-model.trim="form.author" />
                    </div>
                </div>
                <div class="block" style="flex-direction:column; justify-content:space-around; align-items:flex-start;">
                    <div class="input-container" title="所有文件共享前缀">
                        <span>` + wgULS("添加前缀", "添加前綴") + `:</span>
                        <input v-model.trim="form.prefix" style="width:calc(100% - 6em)" />
                    </div>
                    <div class="input-container" style="justify-content:flex-start;">
                        <select v-model.trim="form.license">
                            <option disabled="disabled" value="">` + wgULS("选择授权协议(将鼠标放在选项上显示详情)", "選擇授權協定(將滑鼠放在選項上顯示詳情)") + `</option>
                            <optgroup label="CC` + wgULS("协议", "協定") + `">
                            <option value="CC Zero" title="作者授权以无著作权方式使用">CC-0</option>
                            <option value="CC BY" title="作者授权以署名方式使用,该授权需兼容4.0协议">CC BY 4.0</option>
                            <option value="CC BY-SA" title="作者授权以署名-相同方式方式使用,该授权需兼容4.0协议">CC BY-SA 4.0</option>
                            <option value="CC BY-NC-SA" title="作者授权以署名-非商业使用-相同协议方式使用,该授权需兼容4.0协议">CC BY-NC-SA 4.0</option>
                            </optgroup>
                            <optgroup label="` + wgULS("公有领域", "公有領域") + `">'
                            <option value="PD-Old">` + wgULS("作者离世一定年限后流入公有领域", "作者離世一定年限後流入公有領域") + `</option>
                            <option value="PD-Other">` + wgULS("其他原因流入公有领域", "其他原因流入公有領域") + `</option>
                            </optgroup>
                            <optgroup label="其他">
                            <option value="Copyright" title="原作者没有明确的授权声明">` + wgULS("原作者保留权利", "原作者保留權利") + `</option>
                            <option value="none:gotoCommons">` + wgULS("原作者授权LLWiki使用", "原作者授權LLWiki使用") + `</option>
                            <option value="可自由使用" title="作者放弃版权或声明可自由使用">可自由使用</option>
                            <option value="LLWiki版权所有">LLWiki` + wgULS("版权所有", "版權所有") + `</option>
                            </optgroup>
                        </select>
                    </div>
                    <div class="buttons">
                        <button @click="addSourceUrlFile">` + wgULS("添加源地址上传", "添加原始地址上傳") + `</button>
                        <button :disabled="status === 2" title="执行上传文件" @click="submit(false)">` + wgULS("上传", "上傳") + `</button>
                        <button :disabled="status === 2" title="在发生文件名已存在的情况时,自动滤掉已存在的文件。通常用于在上一次批量上传中一部分失败后,再次尝试将之前没传上去的文件重新上传" @click="submit(true)">` + wgULS("差分上传", "差分上傳") + `</button>
                        <button title="将当前文件除文件名的信息同步到全部文件" @click="asyncCurrentFileInfo">同步` + wgULS("文件", "檔案") + `信息</button>
                        <button @click="showManual">` + wgULS("使用说明", "使用說明") + `</button>
                    </div>
                </div>
            </div>
        </div>
    </div>
    `;

    new Vue({
        el: '#widget-fileUploader',
        template,

        data() {
            return {
                allowedFileTypes: ['ogg', 'mp3', 'png', 'gif', 'jpg', 'jpeg', 'webp'],
                files: [], // 待上传的文件
                focusedFileIndex: 0,
                categoryHints: [],
                categoryInputDebounceTimeoutKey: 0,
                categoryHintFocusedIndex: -1,
                status: 1, // 0:失败,1:初始化,2:提交中,3:成功
                form: {
                    fileName: '',
                    categoryInput: '', // 分类输入栏
                    categories: [], // 实际要提交的分类
                    charaName: '',
                    author: '',
                    source: '',
                    prefix: '',
                    license: ''
                },
                doubleClickTimeoutKey: 0  // 用于双击复制文件名 
            };
        },

        mounted() {
            $('#widget-fileUploader').hide();
        },

        watch: {
            files() {
                this.focusedFileIndex === 0 && this.focusFile(0);
            },

            form: {
                deep: true,
                handler() {
                    if (!this.files[this.focusedFileIndex]) { return; }

                    this.files[this.focusedFileIndex] = {
                        ...this.files[this.focusedFileIndex],
                        fileName: this.form.fileName,
                        author: this.form.author,
                        charaName: this.form.charaName,
                        source: this.form.source,
                        license: this.form.license
                    };
                }
            },

            license(val) {
                if (val === 'none:gotoCommons') {
                    alert(wgULS('该协议需要手动填写授权证明,请到特殊页面上传', '該協定需要手動填寫授權證明,請到特殊頁面上傳'));
                    window.open('https://llwiki.org/zh/Special:上传文件', '_blank');
                }
            }
        },

        computed: {
            license() {
                return this.form.license;
            },
        },

        methods: {
            createFileItem(fileBody) {
                return {
                    body: fileBody,
                    objectUrl: typeof fileBody === 'string' ? fileBody : URL.createObjectURL(fileBody),
                    fileName: typeof fileBody === 'string' ? fileBody.replace(/.+\/(.+?)$/, '$1') : fileBody.name,
                    author: '',
                    charaName: '',
                    source: '',
                    license: 'Copyright'
                };
            },
      
            isImageFile(fileBody) {
                const imageType = ['jpg', 'png', 'jpeg', 'gif', 'webp'];
                return imageType.includes( (typeof fileBody === 'string' ? fileBody : fileBody.name).replace(/.+\.(.+?)$/, '$1').toLowerCase() );
            },

            hideWidget() {
                $('#widget-fileUploader').fadeOut(200);
                $('#content').css('position', 'relative');
            },

            loadCategoryHint() {
                clearTimeout(this.categoryInputDebounceTimeoutKey);
                this.categoryInputDebounceTimeoutKey = setTimeout(() => {
                    if (this.form.categoryInput === '') { return; }
                    api.get({action: "query", list: "search", srwhat: 'title', srsearch: this.form.categoryInput, srnamespace: "14", srlimit: "20", srprop: "", formatversion: 2})
                        .then(data => {
                        const hints = data.query.search.map(item => item.title.substring(9));
                        this.categoryHints = hints;
                    });
                }, 500);
            },

            resetCategory() {
                this.form.categoryInput = '';
                this.categoryHints = [];
                this.categoryHintFocusedIndex = -1;
            },

            addCategory(categoryName) {
                this.form.categories.push(categoryName);
                this.resetCategory();
            },

            // 实现上下键切换分类提示
            handlerFor_categoryHints_wasKeyDowned(e) {
                if (e.code === 'ArrowUp') {
                    this.categoryHintFocusedIndex++;
                    if (this.categoryHintFocusedIndex > this.categoryHints.length - 1) {
                        this.categoryHintFocusedIndex = 0;
                    }
                }
        
                if (e.code === 'ArrowDown') {
                    this.categoryHintFocusedIndex--;
                    if (this.categoryHintFocusedIndex < 0) {
                        this.$refs.categoryInput.focus();
                    }
                }

                this.categoryHintFocusedIndex >= 0 && this.$refs.categoryHints.querySelectorAll('div')[this.categoryHintFocusedIndex].scrollIntoView();
            },

            handlerFor_categoryInput_wasKeyDowned() {
                if (this.categoryHints.length === 0 || !this.$refs.categoryHints) { return; }
                this.$refs.categoryHints.focus();
                this.categoryHintFocusedIndex = 0;
            },
      
            addFileByFileSelector(e) {
                const originalFileList = e.target.files;
                [].forEach.call(originalFileList, file => {
                    if (this.files.length === 50) { return; }
          
                    if (file.size / 1024 / 1024 > 20) return alert(wgULS("文件", "檔案") + `【${file.name}】` + wgULS("大小超过20MB,无法上传", "大小超過20MB,無法上傳") + `!`);
                    this.files.push(this.createFileItem(file));
                });

                e.target.value = '';
                if (this.files.length === 50) mw.notify(wgULS('一次最多上传50个文件', '一次最多上傳50個檔案'), { type: 'wran' });
            },

            addFileByDropping(e) {
                const originalFileList = e.dataTransfer.files;
                [].forEach.call(originalFileList, file => {
                    if (this.files.length === 50) { return; }
          
                    if (!this.allowedFileTypes.includes( file.name.replace(/.+\.(.+?)$/, '$1').toLowerCase() )) return alert(`【${file.name}】` + wgULS("不支持上传这种格式的文件", "不支持上傳這種格式的檔案") + `!`);
                    if (file.size / 1024 / 1024 > 20) return alert(`【${file.name}】的` + wgULS("大小超过20MB,无法上传", "大小超過20MB,無法上傳") + `!`);
                    this.files.push(this.createFileItem(file));
                });

                if (this.files.length === 50) mw.notify(wgULS('一次最多上传50个文件', '一次最多上傳50個檔案'), { type: 'wran' });
            },

            focusFile(index) {
                this.focusedFileIndex = index;
                const file = this.files[index];
                this.form = {
                    ...this.form,
                    fileName: file.fileName,
                    author: file.author,
                    charaName: file.charaName,
                    source: file.source,
                    license: file.license
                };

                // 实现双击复制文件名
                if (this.doubleClickTimeoutKey === 0) {
                    this.doubleClickTimeoutKey = setTimeout(() => {
                        this.doubleClickTimeoutKey = 0;
                    }, 300);
                } else {
                    mw.notify('已复制' + wgULS("文件", "檔案") + '名');
                    this.copyFileName(this.form.prefix + file.fileName);
                    clearTimeout(this.doubleClickTimeoutKey);
                    this.doubleClickTimeoutKey = 0;
                }
            },

            addSourceUrlFile() {
                var url = (prompt(wgULS('请输入文件地址', '請輸入檔案位址') + ':') || '').trim();
                if (!url) { return; }
                this.files.push(this.createFileItem(url));
            },

            copyFileName(fileName) {
                const inputTag = document.createElement('input');
                inputTag.value = fileName;
                inputTag.style.cssText = `
                    position: fixed;
                    left: -9999px;
                `;
                document.body.appendChild(inputTag);
                inputTag.focus();
                document.execCommand('selectAll');
                document.execCommand('copy');
                setTimeout(() => document.body.removeChild(inputTag), 1000);
            },

            asyncCurrentFileInfo() {
                if (!confirm(wgULS('确定要将当前选中的文件信息(不含文件名)同步到所有文件中?', '確定要將當前選中的檔案資訊(不含檔案名)同步到所有檔案中?'))) { return; }
                const currentFile = this.files[this.focusedFileIndex];
                if (!currentFile) return mw.notify(wgULS('当前未选中文件', '當前未選中檔案'));

                this.files.forEach(item => {
                    item.author = currentFile.author;
                    item.charaName = currentFile.charaName;
                    item.source = currentFile.source;
                    item.license = currentFile.license;
                });

                mw.notify('已同步');
            },

            showManual() {
                alert(wgULS([
                    '使用说明',
                    '1. 该插件支持拖拽上传、批量上传。',
                    '2. 若文件上传时发生异常,请以监视列表为准。',
                    '3. 每个文件拥有独立的信息,但“分类”和“添加前缀”是共享的。在需要同步每个文件的角色名等信息时可以使用“同步文件信息”的功能。',
                    '4. 什么是“差分上传”:在发生文件名已存在的情况时,自动滤掉已存在的文件。通常用于在上一次批量上传中一部分失败后,再次尝试将之前没传上去的文件重新上传。',
                    '5. 双击文件可以自动复制“前缀 + 文件名”。'
                ].join('\n'), [
                    '使用說明',
                    '1. 該外掛程式支援拖拽上傳、批次上傳。',
                    '2. 若檔案上傳時發生異常,請以監視清單為準。',
                    '3. 每個檔案擁有獨立的資訊,但「分類」和「添加前綴」是共享的。在需要同步每個檔案的角色名等資訊時可以使用「同步檔案資訊」的功能。',
                    '4. 什麼是「差分上傳」:在發生檔案名已存在的情況時,自動濾掉已存在的檔案。通常用於在上一次批次上傳中一部分失敗後,再次嘗試將之前沒傳上去的檔案重新上傳。',
                    '5. 雙擊檔案可以自動複製「前綴 + 檔案名」。'
                ].join('\n')));
            },

            async submit(diffMode) {
                if (this.files.length === 0) return mw.notify(wgULS('您还没有上传任何文件', '您還沒有上傳任何檔案'), { type: 'warn' });
                if (this.files.some(item => item.fileName === '')) return mw.notify(wgULS('存在文件名为空的文件', '存在檔案名為空的檔案'), { type: 'warn' });

                const duplicateFiles = this.files.reduce((result, item) => {
                    const isDuplicate = this.files.filter(item2 => item2.fileName === item.fileName).length > 1;
                    isDuplicate && result.push(item);
                    return result;
                }, []);
                if (duplicateFiles.length > 0) return alert([
                    wgULS('这些文件名发生了重复,请不要给要上传的文件设置相同的名称:', '這些檔案名發生了重複,請不要給要上傳的檔案設定相同的名稱:'),
                    ...duplicateFiles.map(item => item.fileName)
                ].join('\n'));
        
                const authorizedFiles = this.files.filter(item => item.license === 'none:gotoCommons');
                if (authorizedFiles.length > 0) return alert([
                    wgULS('这些文件的授权协议不允许使用上传工具,请在本次上传中删除,并前往特殊页面填写授权信息后上传:', '這些檔案的授權協定不允許使用上傳工具,請在本次上傳中刪除,並前往特殊頁面填寫授權資訊後上傳:'),
                    ...authorizedFiles.map(item => item.fileName),
                ].join('\n'));

                if (!confirm(wgULS('确定要开始上传吗?', '確定要開始上傳嗎?'))) { return; }

                let postData = this.files.map(item => {
                    const metaCategories = (item.charaName ? `[[Category:${item.charaName}${wgULS('图片', '圖片')}]]` : '');
                    const source = item.source ? `源地址:${item.source}` : '';

                    const comment = metaCategories + source;
                    const pageContent = [
                        '== 摘要 ==',
                        metaCategories + this.form.categories.map(item => `[[Category:${item}]]`).join(''),
                        source,
                        '== 许可协议 ==',
                        `{{${item.license}}}`
                    ].join('\n');

                    return {
                        body: item.body,
                        fileName: this.form.prefix + item.fileName,
                        comment,
                        pageContent
                    };
                });

                mw.notify(wgULS("开始", "開始") + `${diffMode ? '差分' : ''}` + wgULS("上传", "上傳") + `,共${postData.length}` + wgULS("个文件", "個檔案") + `...`);
                console.log(`---- FileUploader 开始${diffMode ? '差分' : ''}上传,共${postData.length}个文件 ----`);
                this.status = 2;

                const printLogFn = (type = 'info') => msg => { mw.notify(msg, { type }); console.log(msg) };
                const printLog = printLogFn();
                printLog.warn = printLogFn('warn');
                printLog.error = printLogFn('error');

                try {
                    const checkedResult = await checkFileNames(postData.map(item => item.fileName));
                    const existedFiles = postData.filter(item => checkedResult[item.fileName.replace(/^./, s => s.toUpperCase())]); // 首字母转大写,因为checkedResult返回的文件名首字母是大写 
                    if (existedFiles.length > 0 && !diffMode) {
                        alert([
                            wgULS('这些文件名已被使用,请为对应的文件更换其他名称:', '這些檔案名已被使用,請為對應的檔案更換其他名稱'),
                            ...existedFiles.map(item => item.fileName)
                        ].join('\n'));
                        this.status = 1;
                        return;
                    }

                    if (diffMode) postData = postData.filter(item => !checkedResult[item.fileName.replace(/^./, s => s.toUpperCase())]);
                    if (diffMode && postData.length === 0) {
                        alert(wgULS('差分模式下没有可以上传的文件', '差分模式下沒有可以上傳的檔案'));
                        this.status = 1;
                        return;
                    }

                    printLog.warn("差分" + wgULS("上传共需要上传", "上傳共需要上傳") + `${postData.length}` + wgULS("个文件", "個檔案"));

                    let uploadResults = [];
                    if (postData.length <= 3) {
                        uploadResults = await Promise.all(postData.map(item =>
                            new Promise(resolve => {
                                upload(item)
                                .then(() => {
                                    printLog(`【${item.fileName}】` + wgULS("上传成功", "上傳成功"));
                                    resolve({ fileName: item.fileName, result: true });
                                })
                                .catch(() => {
                                    printLog.error(`【${item.fileName}】` + wgULS("上传失败", "上傳失敗"));
                                    resolve({ fileName: item.fileName, result: false });
                                });
                            }))
                        );
                    } else {
                        alert(wgULS('上传的文件超过三个,执行分段上传,请耐心等待。进入控制台可查看全部日志(按F12后选择Console)。', '上傳的檔案超過三個,執行分段上傳,請耐心等待。進入控制臺可檢視全部日誌(按F12後選擇Console)。'));
                        printLog.warn(wgULS('上传文件超过3个,执行分段上传', '上傳檔案超過3個,執行分段上傳'));
            
                        // 分段上传
                        const segmentedPostData = postData.reduce((result, item) => {
                            if (result.length === 0) result.push([]);
                            if (result[result.length - 1].length === 3) result.push([]);
                            result[result.length - 1].push(item);
                            return result;
                        }, []);
            
                        console.log(segmentedPostData);
            
                        for (let i=0, len=segmentedPostData.length; i < len; i++) {
                            printLog(`共${len}个分段,现在开始第${i + 1}个`);
              
                            const segment = segmentedPostData[i];
                            const segmentedUploadResult = await Promise.all(segment.map(item =>
                                new Promise(resolve => {
                                    upload(item)
                                    .then(() => {
                                        printLog(`【${item.fileName}】上传成功`);
                                        resolve({ fileName: item.fileName, result: true });
                                    })
                                    .catch(() => {
                                        printLog.error(`【${item.fileName}】上传失败`);
                                        resolve({ fileName: item.fileName, result: false });
                                    });
                                }))
                            );

                            uploadResults.push(...segmentedUploadResult);
                            printLog(`第${i + 1}个分段完成,其中${segmentedUploadResult.filter(item => item.result).length}个成功,${segmentedUploadResult.filter(item => !item.result).length}个失败`);
                        }
                    }
          
                    const report = wgULS([
                        `全部上传结果:共计${uploadResults.length}个文件,其中${uploadResults.filter(item => item.result).length}个成功,${uploadResults.filter(item => !item.result).length}个失败`,
                        ...uploadResults.map((item, index) => `${index + 1}. 【${item.fileName}】${item.result ? '成功' : '失败'}`)
                    ].join('\n'), [
                        `全部上傳結果:共計${uploadResults.length}個檔案,其中${uploadResults.filter(item => item.result).length}個成功,${uploadResults.filter(item => !item.result).length}個失敗`,
                        ...uploadResults.map((item, index) => `${index + 1}. 【${item.fileName}】${item.result ? '成功' : '失敗'}`)
                    ].join('\n'));

                    console.log(report);
                    alert(report);
          
                    this.status = 3;
                } catch (e) {
                    console.log('上传流程出现错误', e);
                    mw.notify(wgULS('网络错误,请重试', '網路錯誤,請重試'), { type: 'error' });
                    this.status = 0;
                };
            }
        }
    });
});
//</nowiki>