コンテンツにスキップ

利用者:Dragoniez/scripts/PrimaryTopicColors.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/*******************************
 * Name: PrimaryTopicColors    *
 * Author: Dragoniez           *
 * Version: 1.0                *
 *******************************/
//<nowiki>
/*global mediaWiki, jQuery */

(function(mw, $) { // Create a function scope

// ********************** VARIABLES **********************

const articleLinks = {}; // {'pageTitle': [<link1>, <link2>, ...], 'pageTitle2': [<link3>, <link4>, ...], ...}
const DEVTALK = '利用者‐会話:Dragoniez/scripts/PrimaryTopicColors';
const RFD = 'Wikipedia:リダイレクトの削除依頼/受付';
const devLink = `<a href="${mw.util.getUrl(DEVTALK + '#リンク修正依頼')}" target="_blank">開発者</a>`;
const TEST = '利用者:Dragoniez/test2';
const TESTTALK = '利用者‐会話:Dragoniez/test2';
const scriptAd = ' ([[User:Dragoniez/scripts/PrimaryTopicColors|PTC]])';
const debuggingMode = {
    'pageToEdit': false
};

// ********************** DOM READY FUNCTION **********************
$(async function() {

    // Don't run the script on certain pages
    if (
        mw.config.get('wgAction') === 'edit' ||
        mw.config.get('wgCanonicalSpecialPageName') === 'Recentchanges' ||
        mw.config.get('wgCanonicalSpecialPageName') === 'Watchlist'
    ) {
        return;
    }

    // Append <style>
    $('head').append(
        '<style id="primary-topic-colors">' +
        '   .ptc-primarytopic:link,' +
        '   .ptc-primarytopic-toresolve:link {' +
        '       color: #23AF3A;' +
        '   }' +
        '   .ptc-primarytopic:visited,' +
        '   .ptc-primarytopic-toresolve:visited {' +
        '       color: #1B842C;' +
        '   }' +
        '   .ptc-primarytopic-toresolve::after {' +
        '       content: "RD";' +
        '       color: red;' +
        '       font-size: smaller;' +
        '       vertical-align: super;' +
        '   }' +
        '</style>'
    );

    // Mark links
    const articleTitles = collectLinks();
    if (!articleTitles) return;
    const markLinksAsyc = []; // Stores looped async function
    for (const article of articleTitles) {
        markLinksAsyc.push(markLinks(article));
    }
    const result = await Promise.all(markLinksAsyc); // Wait until all the async procedures are finished

    // Append a portletlink
    if ($.inArray('autoconfirmed', mw.config.get('wgUserGroups')) === -1) return;
    const ptsToResolve = []; // Stores 'PAGETITLE (DISAMB)', which is subject to deletion
    for (const el of result) { // 'result' is an array that stores the result of each async procedure
        if (el) ptsToResolve.push(el); // If not undefined, the relevant element of the array has a pagename
    }
    if (ptsToResolve.length === 0) return;
    mw.loader.using('jquery.ui', function() {
        appendPortletlink(ptsToResolve);
    });

});

// ********************** MAIN FUNCTIONS **********************

function collectLinks() {

    // RegExp for links
    const pageRegExp = new RegExp(mw.config.get('wgArticlePath').replace('$1', '') + '([^#]+)'); // Matches '/wiki/PAGENAME' in URLs
    const scriptRegExp =  new RegExp('^' + mw.config.get('wgScript') + '\\?title=([^#&]+)'); // Matches '/w/index.php?title=PAGENAME' in URLs

    // Find all article links that match the RegExps and save them in articleLinks
    const articleTitles = [];
    var $container, url, matchedArr, pageTitle;

    switch(mw.config.get('wgAction')) {
        case 'submit':
            $container = $('.mw-content-ltr'); // The parsed preview
            break;
        case 'history':
            $container = $('.comment'); // Edit summaries
            break;
        default:
            $container = $('.mw-body-content');
    }

    $container.find('a').each(function(i, lnk) { // Loop through all the links in the page's content

        // No need to look at certain links
        if (
            $(lnk).hasClass('mw-changeslist-date') || // Revision link
            $(lnk).hasClass('mw-changeslist-diff') || // Diff link
            $(lnk).hasClass('mw-changeslist-history') || // History link
            $(lnk).hasClass('mw-thanks-thank-link') || // Thanks link
            $(lnk).parent('span').hasClass('mw-usertoollinks') || // Links to user namespace on some pages
            $(lnk).parent('span').hasClass('mw-history-undo') || // Undo link
            $(lnk).parent('span').hasClass('mw-rollback-link') || // Rollback link
            $(lnk).parent('span').hasClass('autocomment') || // Section link in edit summary
            $(lnk).parent('span').hasClass('mw-editsection') || // 'Edit section' link
            $(lnk).hasClass('extiw') || // Interwiki link
            $(lnk).hasClass('external') || // External link
            $(lnk).hasClass('new') || // Red link
            $(lnk).hasClass('mw-redirect') || // Redirect link
            $(lnk).hasClass('mw-disambig') // Disambiguation link
        ) {
            return;
        }
        var id;
        if (id = $(lnk).closest('div').attr('id')) {
            if (id.match(/^mw-diff-[on]title[1245]$/)) return; // Diff page: -title3 contains edit summaries
        }

        // Get the href of the link
        url = $(lnk).attr('href');
        if (!url) return;
        if (url.indexOf('www.wikidata.org') !== -1) return;

        // Extract a page title from the href
        if (matchedArr = pageRegExp.exec(url)) {
            pageTitle = matchedArr[1];
        } else if (matchedArr = scriptRegExp.exec(url)) {
            pageTitle = matchedArr[1];
        } else {
            return;
        }
        pageTitle = decodeURIComponent(pageTitle).replace(/_/g, ' ');

        // Exclude page titles inclduing "(", ")", ":", "#", "/" and push the jQuery object into the array
        if (pageTitle.match(/[#\:\(\)\/]/)) return;
        if (!articleLinks[pageTitle]) articleLinks[pageTitle] = [];
        articleLinks[pageTitle].push(lnk);
        if ($.inArray(pageTitle, articleTitles) === -1) articleTitles.push(pageTitle);

    });

    if (articleTitles.length === 0) return;
    //console.log(articleTitles);
    return articleTitles;

}

function markLinks(pageTitle) {
    return new Promise(function(resolve) {
        new mw.Api().get({
            'action': 'query',
            'generator': 'prefixsearch',
            'gpssearch': pageTitle + ' (',
            'gpslimit': 3,
            'gpsprofile': 'strict',
            'redirects': true,
            'formatversion': 2
        }).done(function(res){
            var resPgs, resRedirs, links;
            if (!res || !res.query || !(resPgs = res.query.pages)) return resolve();
            if (resPgs.length === 0) return resolve();
            if (resRedirs = res.query.redirects) {
                if (resRedirs.length !== 0) {
                    for (const redir of resRedirs) {
                        for (const pg of resPgs) {
                            if (pg.index == redir.index) {
                                pg.title = redir.from; // Title key has the redirect target: Replace it with the redirect source
                                pg.redirectTo = redir.to; // Create a new key in the object and store the redirect target
                            }
                        }
                    }
                }
            }
            for (let i = 0; i < resPgs.length; i++) {
                if (resPgs[i].title.indexOf('(曖昧さ回避)') !== -1) { // If the title contains '(曖昧さ回避)'
                    resPgs.splice(i, 1); // Remove the object element from the array
                    break;
                }
            }
            if (resPgs.length === 0) return resolve(); // If resPgs has elements left in it, the page without parentheses is a primary topic
            links = articleLinks[pageTitle];
            if (resPgs.length === 1 && resPgs[0].redirectTo === pageTitle) { // If resPgs has only one page left and if it's a redirect to the PT
                for (let i = 0; links && i < links.length; i++) {
                    $(links[i]).addClass('ptc-primarytopic-toresolve'); // The page is potentially unnecessarily disambiguated
                }
                resolve(resPgs[0].title);
            } else { // Or else, just mark up the relevant link as a primary topic
                for (let i = 0; links && i < links.length; i++) {
                    $(links[i]).addClass('ptc-primarytopic');
                }
                resolve();
            }
        });
    });
}

function appendPortletlink(ptsToResolve) {

    // Define the position of the portletlink (skin-dependent)
    var lkPosition;
    if (mw.config.get('skin') === 'minerva') {
        lkPosition = 'p-personal';
    } else {
        lkPosition = 'p-cactions';
    }

    // Add a portletlink
    $(mw.util.addPortletLink(lkPosition, '#', 'リンク修正依頼', 'ca-ptc', '不要な曖昧さ回避リンクの修正依頼またはリダイレクトの削除依頼', null, '#ca-move')).click({'arg1': ptsToResolve}, openDialog);

}

var firsttime = true, pagelist = [];
function openDialog(e) {
    e.preventDefault();

    var ptsToResolve = e.data.arg1;
    if (firsttime) { // When the dialog is opened for the first time
        firsttime = false;
        pagelist = ptsToResolve; // The list of the potentially unnecessarily db-ed pages need to be updated when the dialog is re-opened
    }

    const ptList = [];
    for (const pt of pagelist) {
        ptList.push(
            '<li>' +
                '<input class="ptc-request-checkbox" type="checkbox" style="margin-right: 0.5em;">' +
                `<a class="ptc-request-target" href="${mw.util.getUrl(pt, {'redirect': 'no'})}" target="_blank">${pt}</a>` +
                ' <span style="font-size: smaller;">(' +
                    `<a href="${mw.util.getUrl('特別:pagehistory/' + pt)}" target="_blank">履歴</a>` +
                    ' / ' +
                    `<a href="${mw.util.getUrl('特別:リンク元/' + pt)}" target="_blank">リンク元</a>` +
                    ' / ' +
                    `<a href="${mw.util.getUrl('特別:前方一致ページ一覧', {'prefix': pt.replace(/ {1}\(.+\)$/, ' ('), 'namespace': 0})}" target="_blank">前方一致ページ検索</a>` +
                ')</span>' +
            '</li>'
        );
    }
    const dialogHtml =
    '<div id="ptc-dialog" title="Primary Topic Colors" style="max-height: 80vh;">' +
    '   <div id="ptc-dialog-header">' +
    '       <h2>リンク修正依頼</h2>' +
    '   </div>' +
    '   <div id="ptc-dialog-body">' +
    '       <form>' +
    '           <div id="ptc-dialog-pagelist" style="border: 1px solid #a2a9b1; padding: 0.5em; margin-bottom: 0.5em; box-sizing: border-box;">' +
    '               <ul style="list-style: none; font-size: 110%; margin-left: 0;">' +
                        ptList.join('') +
    '               </ul>' +
    '               <input id="ptc-checkall-btn" type="button" value="全てチェック" style="margin-top: 0.2em;">' +
    '           </div>' +
    '           <p>' +
                    `修正依頼: ${devLink}にスクリプトによる一括リンク修正を依頼<br>` +
                    '削除依頼: リダイレクトの削除依頼を提出 (リンク修正が不要な場合)' +
    '           </p>' +
    '       </form>' +
    '       <div style="magin: 0.5em 0;">' +
    '           <p id="ptc-editmessage" style="display: none;"></p>' +
    '       </div>' +
    '   </div>' +
    '</div>';

    // Add the frame div to the page
    $('body').append(dialogHtml);

    // Show dialog
    $('#ptc-dialog').dialog({
        'resizable': false,
        'height': 'auto',
        'width': 'auto',
        'modal': true,
        'buttons': [{
            'text': '修正依頼',
            'click': submitRequest
        }, {
            'text': '削除依頼',
            'click': submitRequest
        }, {
            'text': '閉じる',
            'click': function() {
                $(this).dialog('close');
            }
        }]
    });

}

async function submitRequest(e) {

    if (!/autoconfirmed/.test(mw.config.get('wgUserGroups'))) { // Just in case
        $('#ptc-dialog, #ca-ptc').remove();
        return;
    }

    const reqType = e.target.innerText; // '修正依頼' or '削除依頼'
    var ep = editPrep(reqType);
    if (!ep) return;

    ep = await checkDuplicates(ep); // If requests are already submitted, remove the related pages from the request list
    if (ep.reportText.indexOf('RFD') === -1) return editDone(ep, 'dr'); // If reportText has no RFD template in it, all the redirects have already been reported 

    const ts = await getTimestamps(ep);
    if (!ts) return editDone(ep, 'ts');

    const sectNum = await getSectionNumber(ep);
    if (!sectNum) return editDone(ep, 'sect');

    const result = await edit(ep, sectNum, ts.baseTS, ts.curTS);
    switch(result) {
        case true: // Edit succeeded
            editDone(ep, true);
            return;
        case false: // Edit failed with an unknown error
            editDone(ep, false);
            return;
        default: // Edit failed with a known error ('result' stores error info)
            editDone(ep, result);
    }

}

function editPrep(reqType) {

    // Variables
    const pageToEdit = reqType === '修正依頼' ? DEVTALK: RFD;
    const checkedCnt = $('.ptc-request-checkbox:checked').length;

    // Show error and return if no checkbox is checked
    if (checkedCnt === 0) return alert('チェックされた項目がありません');

    // Collect redirect names
    const  redirPagenames = [];
    $('.ptc-request-checkbox:checked').siblings('.ptc-request-target').each(function() {
        redirPagenames.push($(this).text());
    });

    // Generate request text
    const tlRFD = '{{RFD|PAGE1|PAGE2}}', vote = redirPagenames.length > 1 ? '{{AFD|全削除}}' : '{{AFD|削除}}';
    var reportText = '';
    for (const pg of redirPagenames) {
        reportText += '* ' + tlRFD.replace('PAGE1', pg).replace('PAGE2', pg.replace(/ {1}\(.+\)$/, '')) + '\n';
    }    
    if (reqType === '削除依頼') {
        reportText += '** ' + vote + ' リダイレクト元以外に括弧付きのページが存在しないため、不要な曖昧さ回避として削除を依頼します。--~~~~';
    } else {
        reportText += ': {{コ|依頼}} (PTCによる自動依頼) --~~~~';
    }

    // Get the name of the section to edit
    const sectionRFD = getSectionRFD();
    const sectionToEdit = pageToEdit === RFD ? sectionRFD : 'リンク修正依頼';

    // Generate edit summary
    const summary = generateSummary(sectionToEdit, redirPagenames);

    // Hide parts on dialog
    $('#ptc-editmessage')
        .prop('innerHTML', '依頼中<img src="https://upload.wikimedia.org/wikipedia/commons/4/42/Loading.gif" style="vertical-align: middle; max-height: 100%; border: 0;">')
        .css('display', 'inline-block');
    if (checkedCnt === pagelist.length) {
        $('#ptc-dialog-pagelist').remove();
    } else {
        $('.ptc-request-checkbox').each(function() {
            if ($(this).is(':checked')) $(this).parent('li').remove();
        });
    }
    $('#ptc-dialog').dialog({'buttons': [] });

    // Update pagelist (remove requested pages)
    for (let i = pagelist.length - 1; i >= 0; i--) {
        if ($.inArray(pagelist[i], redirPagenames) !== -1) pagelist.splice(i, 1);
    }

    // Return values
    return {
        'pageToEdit': debuggingMode.pageToEdit ? (pageToEdit === DEVTALK ? TESTTALK : TEST) : pageToEdit,
        'sectionToEdit': sectionToEdit,
        'redirPagenames': redirPagenames,
        'reportText': reportText,
        'summary': summary
    };

}

function getSectionRFD() {
    const d = new Date();
    const lastDay = function(y, m){
        return new Date(y, m + 1, 0).getDate();
    }
    var sectionName;
    switch(true) {
        case (1 <= d.getDate() && d.getDate() <= 5):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月1日 - 5日新規依頼`;
            break;
        case (6 <= d.getDate() && d.getDate() <= 10):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月6日 - 10日新規依頼`;
            break;
        case (11 <= d.getDate() && d.getDate() <= 15):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月11日 - 15日新規依頼`;
            break;
        case (16 <= d.getDate() && d.getDate() <= 20):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月16日 - 20日新規依頼`;
            break;
        case (21 <= d.getDate() && d.getDate() <= 25):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月21日 - 25日新規依頼`;
            break;
        case (26 <= d.getDate() && d.getDate() <= lastDay(d.getFullYear(), d.getMonth())):
            sectionName = `${d.getFullYear()}${d.getMonth() + 1}月26日 - ${lastDay(d.getFullYear(), d.getMonth())}日新規依頼`;
            break;
        default:
    }
    return sectionName;
}

/**
 * @param {string} section
 * @param {Array} pagesArr
 * @returns {string}
 */
function generateSummary(section, pagesArr) {
    return `/* ${section} */ +[[` + pagesArr.slice(0, 5).join(']], [[') + ']]' + (pagesArr.length > 5 ? ' ほか' : '') + scriptAd;
}

// Function to check duplicate requests already present on the target page
async function checkDuplicates(ep) {

    // Get the content of the page to which the request will be submitted
    const wikitext = await parsePage(ep);
    if (!wikitext) return ep;

    // Extract RFD templates
    const templates = findTemplates(wikitext, 'rfd');
    if (templates.length === 0) return ep;

    // Get duplicate RFDs
    const duplicateRequests = [];
    const pagesEscaped = ep.redirPagenames.join('|').replace(/_/g, ' ').replaceAllPTC('(', '\\(', ')', '\\)', '.', '\\.', '^', '\\^', '$', '\\$', '*', '\\*', '+', '\\+', '?', '\\?');
    const pageRegExp = new RegExp(`(?:${pagesEscaped})`);
    for (const tl of templates) { // Loop through all the extracted RFD templates and check if the 1st parameter contains the page(s) to be RFD-ed
        let mtch;
        if (mtch = tl.split('|')[1].match(pageRegExp)) {
            mtch = mtch[0].replace(/_/g, ' ');
            if ($.inArray(mtch, duplicateRequests) === -1) duplicateRequests.push(mtch);
        }
    }

    // Update ep
    const drEscaped = duplicateRequests.join('|').replaceAllPTC('(', '\\(', ')', '\\)', '.', '\\.', '^', '\\^', '$', '\\$', '*', '\\*', '+', '\\+', '?', '\\?');
    const rqTxtRegExp = new RegExp('\\* \\{{2}RFD\\|(?:' + drEscaped + ')\\|.+\\}{2}\\n', 'g');
    if (ep.reportText.match(rqTxtRegExp)) {
        ep.reportText = ep.reportText.replace(rqTxtRegExp, ''); // Remove all duplicates from the report text
        for (let i = ep.redirPagenames.length; i >= 0; i--) { // Remove all duplicates from the page list
            if ($.inArray(ep.redirPagenames[i], duplicateRequests) !== -1) ep.redirPagenames.splice(i, 1);
        }
        ep.summary = generateSummary(ep.sectionToEdit, ep.redirPagenames); // Re-generate edit summary
        ep.updated = true; // Create a new property in ep which signals that the report text has been updated
    }
    return ep; // Return the updated ep

}

function parsePage(ep) {
    return new Promise(function(resolve) {
        new mw.Api().get({
            'action': 'parse',
            'page': ep.pageToEdit,
            'prop': 'wikitext',
            'formatversion': 2
        }).then(function(res) {
            if (res && res.parse) return resolve(res.parse.wikitext);
            resolve();
        }).catch(function() {
            resolve();
        });
    });
}

/** 
 * Function to extract templates from wikitext
 * @param {string} text The text in which to search for templates
 * @param {string} templateName [Optional] Specify the template name (case-insensitive)
 * @returns {Array} An array of the extracted templates
 */
function findTemplates(text, templateName) {

    // Split the text with '{{', the head delimiter of templates
    const tempInnerContent = text.split('{{'); // Note: tempInnerContent[0] is always an empty string or a string that has nothing to do with templates
    const templates = []; // The array of extracted templates to return

    // Extract templates from the text
    if (tempInnerContent.length === 0) { // If the text has no tempalte in it

        return templates; // Return an empty array

    } else { // If the text has some templates in it

        const nest = []; // Stores the element number of tempInnerContent if the element involves nested templates
        for (let i = 1; i < tempInnerContent.length; i++) { // Loop through all elements in tempInnerContent (except tempInnerContent[0])

            let tempTailCnt = (tempInnerContent[i].match(/\}\}/g) || []).length; // The number of '}}' in the split segment
            let temp = ''; // Temporary escape hatch for templates

            if (tempTailCnt === 0) { // The split segment not having any '}}' means that it nests another template

                nest.push(i); // Push the element number into the array

            } else if (tempTailCnt === 1) { // The split segment itself is the whole inner content of one template

                temp = '{{' + tempInnerContent[i].split('}}')[0] + '}}';
                if ($.inArray(temp, templates) === -1) templates.push(temp);

            } else if (tempTailCnt > 1) { // The split segment is part of more than one template (e.g. TL2|...}}...}} )

                for (let j = 0; j < tempTailCnt; j++) { // Loop through all the nests

                    if (j === 0) { // The innermost template

                        temp = '{{' + tempInnerContent[i].split('}}')[j] + '}}'; // Same as when tempTailCnt === 1
                        if ($.inArray(temp, templates) === -1) templates.push(temp);

                    } else { // Nesting templates

                        const elNum = nest[nest.length -1]; // The start of the nesting template
                        nest.pop();
                        const nestedTempInnerContent = tempInnerContent[i].split('}}');

                        temp = '{{' + tempInnerContent.slice(elNum, i).join('{{') + '{{' + nestedTempInnerContent.slice(0, j + 1).join('}}') + '}}';
                        if ($.inArray(temp, templates) === -1) templates.push(temp);

                    }

                }

            }

        }

        // Check if the optional parameter is specified
        if (templateName && templates.length !== 0) {
            const templateRegExp = new RegExp(templateName, 'i');
            for (let i = templates.length -1; i >= 0; i--) {
                // Remove the template from the array if it's not an instance of the specified template
                if (templates[i].split('|')[0].search(templateRegExp) === -1) templates.splice(i, 1);
            }
        }

        return templates;
    }
}

// Function to get the timestamps of the latest revision and the current time
function getTimestamps(ep) {
    return new Promise(function(resolve) {
        new mw.Api().get({
            'action': 'query',
            'titles': ep.pageToEdit,
            'prop': 'revisions',
            'curtimestamp': true,
            'formatversion': 2
        }).then(function(res){
            var resPages;
            if (res && res.query && (resPages = res.query.pages)) {
                return resolve({
                    'baseTS': resPages[0].revisions[0].timestamp,
                    'curTS': res.curtimestamp
                });
            }
            resolve();
        }).catch(function() {
            resolve();
        });
    });
}

// Function to get the section number from the section title 
function getSectionNumber(ep) {
    return new Promise(function(resolve) {
        new mw.Api().get({
            'action': 'parse',
            'page': ep.pageToEdit,
            'prop': 'sections',
            'formatversion': 2
        }).then(function(res) {
            var resSect;
            if (res && res.parse && (resSect = res.parse.sections)) {
                for (let i = 0; i < resSect.length; i++) {
                    if (resSect[i].line === ep.sectionToEdit) {
                        return resolve(resSect[i].index);
                    }
                }
            }
            resolve();
        }).catch(function() {
            resolve();
        });
    });
}

/**
 * @returns true when edit succeeded, false when unexpected error occurred, errcode when known error occurred 
 */
function edit(ep, sectionNum, baseTS, curTS) {
    return new Promise(function(resolve) {
        new mw.Api().post({
            'action': 'edit',
            'title': ep.pageToEdit,
            'section': sectionNum,
            'appendtext': '\n\n' + ep.reportText,
            'summary': ep.summary,
            'basetimestamp': baseTS,
            'starttimestamp': curTS,
            'token': mw.user.tokens.get('csrfToken'),
            'format': 'json'
        }).then(function(res) {
            if (res && res.edit) {
                if (res.edit.result === 'Success') return resolve(true);
            }
            resolve(false);
        }).catch(function(code, err) {
            resolve(err.error.info);
        });
    });
}

/**
 * @param {*} ep
 * @param {*} type 'dr' when request is cancelled because of duplicates, 'ts' when timestamps failed to be fetched, 'sect' when section number
 * failed to be fetcehd, true when edit succeeded, false when unexpected error occurred on edit, errcode when edit failed 
 */
function editDone(ep, type) {

    const drText = ep.updated ? ' (重複依頼分は除去されました)' : '';
    const $msg = $('#ptc-editmessage');

    switch(type) {
        case 'dr':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">中止: 選択分は既に全て依頼されています</span>');
            break;
        case 'ts':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先の最新版が取得できませんでした</span>');
            break;
        case 'sect':
            $msg.prop('innerHTML', '<span style="color: MediumVioletRed;">失敗: 報告先のセクション番号が取得できませんでした</span>');
            break;
        case true:
            $msg.prop('innerHTML', `<span style="color: MediumSeaGreen;">成功: 依頼が完了しました${drText}</span>`);
            break;
        case false:
            $msg.prop('innerHTML', '<span style="color: MediumSeaGreen;">失敗: ページの編集段階で不明なエラーが発生しました</span>');
            break;
        default:
            $msg.prop('innerHTML', `<span style="color: MediumVioletRed;">失敗: ${type}</span>`);
            break;
    }

    if (pagelist.length === 0) {
        $('#ptc-dialog').dialog({
            'buttons': [{
                'text': '閉じる',
                'click': function() {
                    $(this).dialog('close');
                }
            }]
        });
    } else {
        $('#ptc-dialog').dialog({
            'buttons': [{
                'text': '修正依頼',
                'click': submitRequest
            }, {
                'text': '削除依頼',
                'click': submitRequest
            }, {
                'text': '閉じる',
                'click': function() {
                    $(this).dialog('close');
                }
            }]
        });
    }

}

// ********************** EVENT HANDLERS **********************

$(document).off('dialogclose', '#ptc-dialog').on('dialogclose', '#ptc-dialog', function() {
    $(this).remove();
    if (pagelist.length === 0) $('#ca-ptc').remove();
});

$(document).off('click', '#ptc-checkall-btn').on('click', '#ptc-checkall-btn', function() {
    $('.ptc-request-checkbox').prop('checked', true);
});

// ********************** AUXILIARY FUNCTIONS **********************

/**
 * String method (alternative) to replace all occurences of a string with another
 * (takes a replacer and a replacee as arguments)
 * @returns {string}
 */
String.prototype.replaceAllPTC = function() {
    var replaced = '';
    if (arguments.length %2 !== 0) {
        return new Error('SyntaxError: replaceAllPTC takes an even number of arguments.');
    } else {
        for (let i = 0; i < arguments.length; i = i + 2) {
            if (i === 0) {
                replaced = this.split(arguments[i]).join(arguments[i + 1]);
            } else {
                replaced = replaced.split(arguments[i]).join(arguments[i + 1]);
            }
        }
        return replaced;
    }
}

})(mediaWiki, jQuery);
//</nowiki>