MediaWiki:MassCategorization/code.js

/* * Mass Categorization * @description (De)Categorize listed multiple pages. * @author Ozuzanna * Added option for (Dessamator) * Updated: (Dorumin) *    - Some optimization and code-cleaning. *    - Re-added the replace function *    - Now can add, remove, and replace multiple categories at once (but not all three modes at the same time). *    - Now prompts if you try to exit the modal while running. *    - The broad option now doesn't replace stuff like "Test 123", but I kept the option in anyways. */ mw.loader.using('mediawiki.api').then(function {   if ($('#t-mc').length)        return;	var i18n = {		en: {			title: "Mass Categorization",			mode: "Mode",			add: "Add",			remove: "Remove",			replace: "Replace",			category: "Category",			categoryPlural: "Categories",			replaceWith: "Replace with",			matching: "Matching",			generalMatching: "General (does not account for piped categories)",			broadMatching: "Broad (takes care of piped links)",			noInclude: "Do not include in transclusion (for templates)",			caseSensitive: "Case sensitive (removal and replace only)",			instructions: "Put the name of each page you want to categorize on a separate line",			outputInitial: "Any errors encountered will appear below",			cancel: "Cancel",			addCategoryContents: "Add category contents",			initiate: "Initiate",			categoryPrompt: "Please enter the category name (no category prefix)",			doesNotExist: "$1 does not exist", failedToGetContents: "Failed to get contents of $1", categoryAlert: "Please enter at least one category", warning: "Warning", closeModalWarning: "Are you sure you want to close the modal without finishing?", close: "Close", finished: "Finished", nothingLeftToDo: "Nothing left to do, or next line is blank", noCategoryToReplace: "No $1 to replace with entered", pageNotExist: "Page $1 does not exist", addSummary: "Adding $1 (automatic)", addFail: "Failed to add $1 to $2", categoryAlready: "$1 already has the category $2 or an error was encountered; it has been skipped", categoryCheckFail: "Category check failed for $1; it has been skipped", removeNotFound: "$1 was not found on $2", removeFail: "Failed to remove $1 from $2", removeSuccess: "$1 successfully removed from $2", removeSummary: "Removing $1 (automatic)", replaceFail: "Failed to replace $1 on $2", replaceSuccess: "Category successfully replaced on $1", replaceSummary: "Replacing $1 with $2 (automatic)", noCategoryReplace: "No category to replace", automatic: "automatic" //keep lowercase },		es: { title: 'Categorización en Masa', mode: 'Modo', add: 'Añadir', remove: 'Quitar', replace: 'Reemplazar', category: 'Categoría', categoryPlural: 'Categorias', replaceWith: 'Reemplazar con', matching: 'Coincidencias', generalMatching: "General (no toma en cuenta las categorias con barra)", broadMatching: "Extensa (toma en cuenta las categorias con barra)", noInclude: "No incluir en transclusión (para plantillas)", caseSensitive: "Toma en cuenta mayúsculas y minúsculas (solo en las modos de quitar y reemplazar)", instructions: "Introduzca el nombre de cada página que quieras categorizar en una linea separada", outputInitial: "Cualquier error encontrado aparecerá abajo", cancel: "Cancelar", addCategoryContents: "Añadír contenido de categoría", initiate: "Iniciar", categoryPrompt: "Por favor introduzca el nombre de la categoría (sin el prefijo de Categoría)", doesNotExist: "$1 no existe", failedToGetContents: "Error para cargar contenidos de $1", categoryAlert: "Introduzca al menos una categoría", warning: "Advertencia", closeModalWarning: "Está usted seguro de que quiere cerrar el módulo sin terminar?", close: "Cerrar", finished: "Finalizado", nothingLeftToDo: "Nada más que hacer, o la siguiente linea esta en blanco", noCategoryToReplace: "Ninguna $1 para reemplazar fue introducida", pageNotExist: "La página $1 no existe", addSummary: "Añadiendo $1 (automático)", addFail: "Error para añadir $1 a $2", categoryAlready: "$1 ya contiene la categoría $2 o algún error fue encontrado; ha sido salteada", categoryCheckFail: "La verificación de categorias falló para la página $1; esta ha sido salteada", removeNotFound: "$1 no fue encontrado en la página $2", removeSummary: "Removiendo $1 (automático)", removeFail: "Error removiendo $1 de la página $2", replaceSummary: "Reemplazando $1 con $2 (automático)", removeSuccess: "$1 fue removido de la página $2 con exito", replaceFail: "Error al reemplazar $1 en la página $2", replaceSuccess: "Categoría reemplazada con exito en la página $1", noCategoryReplace: "Ninguna categoría que reemplazar", automatic: "automático" //keep lowercase },		ko: { title: "다중 분류 작업", mode: "작업", add: "추가", remove: "제거", replace: "변경", category: "분류", categoryPlural: "분류들", replaceWith: "바꿀 분류", matching: "일치 조건", generalMatching: "일반 (파이프 분류를 고려하지 않음)", broadMatching: "광역 (파이프 링크 처리)", noInclude: "틀에 매개되어 있는 분류 제외", caseSensitive: "대소문자 구분 (제거 및 변경만 가능)", instructions: "각각의 줄에 분류 작업을 할 문서 이름을 입력하십시오", outputInitial: "아래에 작업 중에 발생한 오류가 나타납니다", cancel: "취소", addCategoryContents: "분류 내용을 추가", initiate: "실행", categoryPrompt: "분류 이름을 입력하십시오 (분류 접두사 없이)", doesNotExist: "$1 문서가 이 위키에 없음", failedToGetContents: "$1 문서에서 내용을 가져오지 못함", categoryAlert: "최소 한 개의 분류를 입력하시오", warning: "경고", closeModalWarning: "정말로 이 작업을 닫으시겠습니까? 아직 작업이 끝나지 않았습니다.", close: "닫기", finished: "끝났음", nothingLeftToDo: "더 이상 할 것이 없거나 다음 줄이 비었습니다", noCategoryToReplace: "입력된 분류를 대체할 $1 문서가 없음", pageNotExist: "문서 $1 이(가) 없음", addSummary: "$1 추가 (자동)", addFail: "$2에 $1을(를) 추가하는데 실패함", categoryAlready: "$1에 이미 $2 분류가 있거나 오류가 발생함; 이 문서는 스킵됨", categoryCheckFail: "$1에서 분류를 확인하지 못함; 이 문서는 스킵됨", removeNotFound: "$2에서 $1을(를) 찾지 못함", removeFail: "$2에서 $1을(를) 제거하지 못함", removeSuccess: "$2에서 $1이(가) 성공적으로 제거됨", removeSummary: "$1 제거 (자동)", replaceFail: "$2에서 $1을(를) 바꾸지 못함", replaceSuccess: "$1(으)로 분류를 성공적으로 바꿈", replaceSummary: "$1을(를) $2(으)로 변경 (자동)", noCategoryReplace: "바꿀 분류가 없음", automatic: "자동" }	};	i18n = i18n[mw.config.get("wgContentLanguage")] || i18n.en; //set i18n according to wiki language (must be done like this or script won't work) var FormMC = '\  \ \           Mode: \  \ ' + i18n.add + ' \ ' + i18n.remove + ' \ ' + i18n.replace + ' \ \           \            \                + \ \                - \            \            \                ' + i18n.category + ': \  \ ' + i18n.replaceWith+ ': \  \ \               ' + i18n.matching + ': \ \               ' + i18n.generalMatching + ' \ \               ' + i18n.broadMatching + ' \ \           \                ' + i18n.noInclude + ' \ \               ' + i18n.caseSensitive + ' \ \           \            ' + i18n.instructions + ' \  \ \       ' + i18n.outputInitial + ' \   ',    delay = window.massCategorizationDelay || 1000, Api = new mw.Api; // Add the necessary CSS for the modal mw.util.addCSS('                                         \        #mc-remove-category[disabled] {                       \            display: none;                                    \        }                                                     \        #mc-add-category, #mc-remove-category {               \            transition: all 0.2s ease-in-out;                 \            cursor: pointer;                                  \            font-size: 16px;                                  \            margin-top: 4px;                                  \        }                                                     \        #mc-add-category:hover, #mc-remove-category:hover {   \            -webkit-text-shadow:0 0 2px blue;                 \            -moz-text-shadow: 0 0 2px blue;                   \            text-shadow:0 0 2px grey;                         \        }                                                     \ #text-error-output {                                 \ height: 10em;                                    \ width: 80%;                                      \ margin: 5px auto 0px auto;                       \ color: #000;                                     \ background-color: #ffbfbf;                       \ height: 150px;                                   \ border: 1px solid black;                         \ font-weight: bold;                               \ overflow: scroll;                                \ }                                                    \    ');    //Support for Monobook    if (mw.config.get('skin') === 'monobook') {        mw.util.addPortletLink('p-tb', '#', i18n.title, 't-mc');    } else {        $('#my-tools-menu').prepend(' ' + i18n.title + '</li>');    }    document.getElementById('t-mc').addEventListener('click', function { $.showCustomModal(i18n.title, FormMC, {           id: 'form-categorization',            callback: function {                document.getElementById('mc-add-category').addEventListener('click', function(e) { e.preventDefault; document.getElementById('mc-remove-category').removeAttribute('disabled'); var container = document.getElementById('mc-categories-container'); $(container).append(' \                       ' + i18n.category + ': \                            <input type="text" class="category-name" value="" /> \                        <p class="replace-para" style="' + (document.getElementById('select-mc').value == 3 ? '' : 'display: none;') + 'padding-top: 3px;">' + i18n.replaceWith + ': \                            <input type="text" class="replace-category-name" value="" />\                    '); $(container.lastElementChild).fadeIn; });               document.getElementById('mc-remove-category').addEventListener('click', function(e) { e.preventDefault; var $toremove = $('#mc-categories-container > div:not(.removed)').last; $toremove.addClass('removed').fadeOut(400, function {                       $(this).remove;                    }); if ($toremove.parent.children(':not(.removed):last').prop('tagName') != 'DIV') this.setAttribute('disabled', 'disabled'); });               document.getElementById('select-mc').addEventListener('change', function { if (this.value == 3) { $('.replace-para').fadeIn; } else { $('.replace-para').fadeOut; }               });            },            width: 500,            buttons: [{                message: i18n.cancel,                handler: function {                    $('#form-categorization').closeModal;                }            }, {                message: i18n.addCategoryContents,                defaultButton: true,                handler: function {                    addCategoryContents;                }            }, {                id: 'start-button',                message: i18n.initiate,                defaultButton: true,                handler: function {                    init;                }            }]        }); });   function logError(msg) {        console.log(msg);        var errBox = document.getElementById('text-error-output');        var text = document.createTextNode(msg);        errBox.appendChild(text);        var brTag = document.createElement('br');        errBox.appendChild(brTag);    }    function addCategoryContents {        var category = prompt(categoryPrompt + ' :').replace('_', ' ');        Api.get({ action: 'query', list: 'categorymembers', cmtitle: "Category:" + category, cmlimit: 5000, cb: new Date.getTime })           .done(function(d) { if (!d.error) { var data = d.query; var pList = document.getElementById('text-categorization'); if (data.categorymembers) { for (var i in data.categorymembers) { pList.value += data.categorymembers[i].title + '\n'; }                   } else { logError(i18n.doesNotExist.replace('$1',category)); }               } else { logError(i18n.failedToGetContents.replace('$1',category) + ' : ' + d.error.code); }           })            .fail(function { logError(i18n.failedToGetContents.replace('$1',category)); });   }    function init {        var catSlots = Array.from(document.getElementsByClassName('category-name')),            txt = document.getElementById('text-categorization'),            pages = txt.value.split('\n'),            page = pages[0],            catNames = [];        catSlots.forEach(function(name) { name = name.value.trim; catNames.push(name.charAt(0).toUpperCase + name.slice(1)); });       if (!catNames.filter(Boolean).length) {            alert(i18n.categoryAlert);            return;        }        document.getElementById('start-button').setAttribute('disabled', 'disabled');        $('.blackout, #form-categorization .close').unbind;        $('.blackout, #form-categorization .close').bind('click', function { $.showCustomModal(i18n.warning, i18n.closeModalWarning, {               id: 'close-warning',                width: 400,                buttons: [{                    message: i18n.close,                    defaultButton: true,                    handler: function {                        // Nope, you can't close both with one call.                        $('#close-warning').closeModal;                        $('#form-categorization').closeModal;                    }                }, {                    message: i18n.cancel,                    handler: function {                        $('#close-warning').closeModal;                    }                }]            }); });       if (!page && !document.getElementById('form-complete')) {            document.getElementById('start-button').removeAttribute("disabled");            $.showCustomModal(i18n.finished, i18n.nothingLeftToDo, { id: 'form-complete', width: 200, callback: function { var $blackout = $('.blackout').last; $blackout.unbind; $blackout.click(function {                       $('#form-complete .wikia-button').click;                    }); },               buttons: [{ message: i18n.close, id: 'form-complete-button', defaultButton: true, handler: function { $('#form-complete').closeModal; var $elems = $('.blackout, #form-categorization .close'); $elems.unbind; $elems.click(function {                           $('#form-categorization').closeModal;                        }); }               }]            });        } else {            categorize(page, catNames);        }        pages = pages.slice(1, pages.length);        txt.value = pages.join('\n');    }    function categorize(pageToCat, cats) {        var actionVal = document.getElementById('select-mc').value;        if (actionVal == 3) {            var newCatEl = Array.from(document.getElementsByClassName('replace-category-name'));            var newCats = [];            newCatEl.forEach(function(name) { name = name.value.trim; newCats.push(name.charAt(0).toUpperCase + name.slice(1)); });           if (!newCats.filter(Boolean).length) {                alert(i18n.noCategoryToReplace.replace('$1', (newCatEl.length == 1 ? i18n.category : i18n.categoryPlural) ));               document.getElementById('start-button').removeAttribute("disabled");                return;            }        }        Api.get({ action: 'query', titles: pageToCat, prop: 'revisions|categories', rvprop: 'content', cb: new Date.getTime }).done(function(d) { var page = d.query.pages[Object.keys(d.query.pages)[0]]; var content = page.missing === '' ? '' : page.revisions[0]['*']; var newContent = content; var config; var summary; if (actionVal == 1 && !d.error) { /*                * Add category. */               if (page.missing === '') { logError(i18n.pageNotExist.replace('$1', pageToCat)); return; }               var knownCats = []; if (page.categories) { page.categories.forEach(function(categ) {                       knownCats.push(categ.title);                    }); }               var toAdd = []; cats.forEach(function(cat) {                   if (!cat) return;                    cat = i18n.category + ':' + cat;                    if (knownCats.indexOf(cat) === -1) toAdd.push(cat);                }); if (toAdd.length) { var sPrefix = ""; var sSuffix = ""; if ($('input[name=mass-categorization-noinclude]').prop('checked')) { sPrefix = " "; sSuffix = "<\/noinclude>"; }                   summary = i18n.addSummary.replace('$1', (toAdd.length == 1 ? i18n.category : i18n.categoryPlural + ':') + ' ' + toAdd.join(', ') + '').replace(/\[\[' + i18n.category + ':(.*?)\]\]/g, '$1'); config = { format: 'json', action: 'edit', title: pageToCat, summary: summary, nocreate: '', appendtext: sPrefix + '\n' + toAdd.join('\n') + '' + sSuffix, bot: true, token: mw.user.tokens.get('editToken') };                   $.ajax({                        url: mw.util.wikiScript('api'),                        data: config,                        dataType: 'json',                        type: 'POST',                        success: function(d) {                            if (!d.error) {                                console.log((toAdd.length == 1 ? i18n.category : i18n.categoryPlural) + ' successfully added to ' + pageToCat + '!');                            } else {                                logError(i18n.addFail.replace('$1',(toAdd.length == 1 ? i18n.category : i18n.categoryPlural )).replace('$2',pageToCat) + ': ' + d.error.code);                           }                        },                        error: function {                            logError(i18n.addFail.replace('$1',(toAdd.length == 1 ? i18n.category : i18n.categoryPlural )).replace('$2',pageToCat));                       }                    }); } else { window.test = toAdd; logError(cats.length == 1 ? pageToCat + ' already has the category ' + cats[0].substring(9) + ' or an error was encountered; it has been skipped.' : pageToCat + ' has each of the categories specified; it has been skipped.'); }           } else if (actionVal == 2 && !d.error) { /*                * Remove category. */               if (page.missing === '') { logError(i18n.pageNotExist.replace('$1',pageToCat)); return; }               cats.forEach(function(cat, i) {                    if (!cat) {                        cats[i] = false;                        return;                    }                    // Remove it                    var cSens = document.getElementById('mc-case-sensitive').checked;                    var broad = document.getElementById('mc-broad-removal').checked;                    var flags = 'g' + (cSens ? '' : 'i');                   var nRegEx = '\\[\\[' + i18n.category + ':' + cat + '\\]\\]';                    var sRegEx = '(\\[\\[' + i18n.category + ':' + cat + '\\]\\]|\\[\\[' + i18n.category + ':' + cat + '\\|.*?\\]\\])';                    var regex = new RegExp(broad ? sRegEx : nRegEx, flags);                   if ($('input[name=mass-categorization-removal]:checked').val == 2) {                        regex = new RegExp(sRegEx, "gi");                    }                    if (document.getElementById('mc-noinclude').checked) {                        regex = new RegExp('\\<noinclude\\>\\s*' + (broad ? sRegEx : nRegEx) + '\\s*\\<\/noinclude\\>', flags);                    }                    if (regex.test(newContent))                         newContent = newContent.replace(regex, );                    else {                        console.log(i18n.removeNotFound.replace('$1', cat).replace('$2', pageToCat));                        cats[i] = false;                    }                    newContent = newContent.replace(regex, );                }); //don't submit if new and old contents are equal (no category found) if (newContent == content) { logError(i18n.removeNotFound.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2',pageToCat )); return; }               //submit new page cats = cats.filter(Boolean); summary = (i18n.removeSummary.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) + ' ' + i18n.category + ' :' + cats.join(', ') + '') ).replace(/\[\[' + i18n.category + ':(.*?)\]\]/g, '$1'); config = { format: 'json', action: 'edit', watchlist: 'nochange', title: pageToCat, summary: summary, nocreate: '', text: newContent, bot: true, token: mw.user.tokens.get('editToken') };               $.ajax({                    url: mw.util.wikiScript('api'),                    data: config,                    dataType: 'json',                    type: 'POST',                    success: function(d) {                        if (!d.error) {                            console.log(i18.removeSuccess.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat) );                       } else {                            logError(i18n.removeFail.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat) + ': ' + d.error.code);                       }                    },                    error: function {                        logError(i18n.removeFail.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat));                   }                }); } else if (actionVal == 3 && !d.error) { /*                 * Replace category. */               if (page.missing === '') { logError(i18n.pageNotExist.replace('$1', pageToCat) ); return; }               // Replace it                cats.forEach(function(cat, i) {                    if (!cat) {                        console.log(i18n.noCategoryToReplace);                        cats.splice(i, 1);                        newCats.splice(i, 1);                        return;                    } else if (!newCats[i]) {                        console.log(cat + ': ' + i18n.noCategoryToReplace);                        cats.splice(i, 1);                        newCats.splice(i, 1);                        return;                    }                    var cSens = document.getElementById('mc-case-sensitive').checked;                    var broad = document.getElementById('mc-broad-removal').checked;                    var flags = 'g' + (cSens ? '' : 'i');                   var nRegEx = '\\[\\[' + i18n.category + ':' + cat + '\\]\\]';                    var sRegEx = '(\\[\\[' + i18n.category + ':' + cat + '\\]\\]|\\[\\[' + i18n.category + ':' + cat + '\\|.*?\\]\\])';                    var regex = new RegExp(broad ? sRegEx : nRegEx, flags);                   var newCat = i18n.category + ':' + newCats[i].charAt(0).toUpperCase + newCats[i].substring(1);                    if (regex.test(newContent))                         newContent = newContent.replace(regex,  + newCat + );                    else {                        cats[i] = false;                        newCats[i] = false;                        return;                    }                }); // Don't submit if new and old contents are equal (no category found) if (newContent == content) { logError(i18n.removeNotFound.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat)); return; }               // Create the summary var replacements = ''; cats = cats.filter(Boolean); newCats = newCats.filter(Boolean); cats.forEach(function(cat, i) {                   var temp = replacements + '' + cat + ' → ' + newCats[i] + ', ';                     if (temp.length > 150) {                        if (replacements.indexOf('(+)') == -1)                            replacements = replacements.replace(' (automatic)', '(+) (automatic)');                        return;                    }                    replacements = temp;                }); summary = i18.replaceSummary.replace('$1', cats.length == 1 ?  + cats[0] + ' with .*/, ) + '|' + newCats[0].replace(/\|.*/, ) +  : replacements.replace(/, (?!.*, )/, '') ); //submit new page config = { format: 'json', action: 'edit', watchlist: 'nochange', title: pageToCat, summary: summary, nocreate: '', text: newContent, bot: true, token: mw.user.tokens.get('editToken') };               $.ajax({                    url: mw.util.wikiScript('api'),                    data: config,                    dataType: 'json',                    type: 'POST',                    success: function(d) {                        if (!d.error) {                            console.log(i18n.replaceSuccess.replace('$1', pageToCat));                        } else {                            logError(i18n.replaceFail.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat) + ': ' + d.error.code);                       }                    },                    error: function {                        logError(i18n.replaceFail.replace('$1', (cats.length == 1 ? i18n.category : i18n.categoryPlural) ).replace('$2', pageToCat));                   }                }); } else { if (actionVal == 1) logError(i18.categoryCheckFail.replace('$1', pageToCat) + d.error.code); else logError(i18n.failedToGetContents.replace('$1', pageToCat) + d.error.code); }       }).fail(function { if (actionVal == 1) logError(i18.categoryCheckFail.replace('$1', pageToCat)); else logError(i18n.failedToGetContents.replace('$1', pageToCat)); });       setTimeout(init, delay);    } });