From aab74cac5ce8d9ea1bce4b0004771a2d4f58c4e6 Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sun, 5 Oct 2025 23:00:38 +0200 Subject: [PATCH 1/7] protect.js: implement CTOP logging --- src/modules/twinkleprotect.js | 63 +++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index 272330b2a..b9117b95e 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -467,13 +467,11 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct type: 'div', name: 'protectReason_notes', label: 'Notes:', - style: 'display:inline-block; margin-top:4px;', - tooltip: 'Add a note to the protection log that this was requested at RfPP.' + style: 'margin-top:4px;' }); field2.append({ type: 'checkbox', event: Twinkle.protect.callback.annotateProtectReason, - style: 'display:inline-block; margin-top:4px;', list: [ { label: 'RfPP request', @@ -481,7 +479,8 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct checked: false, value: 'requested at [[WP:RfPP]]' } - ] + ], + tooltip: 'Add a note to the protection log that this was requested at RfPP.' }); field2.append({ type: 'input', @@ -491,6 +490,27 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct value: '', tooltip: 'Optional revision ID of the RfPP page where protection was requested.' }); + field2.append({ + type: 'checkbox', + event: Twinkle.protect.callback.annotateProtectReason, + list: [ + { + label: 'CTOP action', + name: 'protectReason_notes_ctop', + checked: false, + value: 'arbitration enforcement per [[WP:CT]]' + } + ], + tooltip: 'Add a note to the protection log that this is an arbitration enforcement action.' + }); + field2.append({ + type: 'select', + event: Twinkle.protect.callback.annotateProtectReason, + label: 'CTOP code', + name: 'protectReason_notes_ctopCode', + list: Twinkle.protect.ctopCodes, + tooltip: 'Code of the contentious topic in which arbitration enforcement takes place.' + }); if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto' || mw.config.get('wgNamespaceNumber') === 710) { // tagging isn't relevant for non-existing, module, or TimedText pages break; } @@ -592,7 +612,8 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct // reduce vertical height of dialog $(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' }); - $(e.target.form).find('fieldset[name="field2"] input[name="protectReason_notes_rfppRevid"]').parent().css({display: 'inline-block', marginLeft: '15px'}).hide(); + $(e.target.form).find('fieldset[name="field2"] input[name="protectReason_notes_rfppRevid"]').parent().css({marginLeft: '15px'}).hide(); + $(e.target.form).find('fieldset[name="field2"] select[name="protectReason_notes_ctopCode"]').parent().css({ display: 'block', marginLeft: '15px'}).hide(); // override inline-block } // re-add protection level and log info, if it's available @@ -998,6 +1019,17 @@ Twinkle.protect.protectionTags = [ // Filter FlaggedRevs .filter((type) => hasFlaggedRevs || type.label !== 'Pending changes templates'); +Twinkle.protect.ctopCodes = [ + { label: 'Specify a code...', value: '' }, + { label: 'RUSUKR – Russo-Ukrainian War', value: 'RUSUKR' }, + { label: 'A-I – Arab-Israeli conflict', value: 'A-I' }, + { label: 'KURD – Kurds and Kurdistan', value: 'KURD' }, + { label: 'A-A – Armenia-Azerbaijan', value: 'A-A' }, + { label: 'APL – Antisemitism in Poland', value: 'APL' }, + { label: 'SASG – South Asian social groups', value: 'SASG' }, + { label: 'IMH – Indian military history', value: 'IMH' } +]; + Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { const form = e.target.form; @@ -1394,9 +1426,11 @@ Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) { }; Twinkle.protect.protectReasonAnnotations = []; +Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT]]'; +Twinkle.protect.protectCtopDisplay = false; Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallbackAnnotateProtectReason(e) { const form = e.target.form; - const protectReason = form.protectReason.value.replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectReasonAnnotations.join(': '))), ''); + const protectReason = form.protectReason.value.replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectReasonAnnotations.join(': '))), '').replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectCtop)), ''); if (this.name === 'protectReason_notes_rfpp') { if (this.checked) { @@ -1413,6 +1447,19 @@ Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallback const permalink = '[[Special:Permalink/' + e.target.value + '#' + Morebits.pageNameNorm + ']]'; Twinkle.protect.protectReasonAnnotations.push(permalink); } + } else if (this.name === 'protectReason_notes_ctop') { + Twinkle.protect.protectCtopDisplay = this.checked; + if (this.checked) { + $(form.protectReason_notes_ctopCode).parent().show(); + } else { + $(form.protectReason_notes_ctopCode).parent().hide(); + } + } else if (this.name === 'protectReason_notes_ctopCode') { + if (e.target.value.length) { + Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT/' + e.target.value + ']]'; + } else { + Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT]]'; + } } if (!Twinkle.protect.protectReasonAnnotations.length) { @@ -1420,6 +1467,10 @@ Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallback } else { form.protectReason.value = (protectReason ? protectReason + '; ' : '') + Twinkle.protect.protectReasonAnnotations.join(': '); } + + if (Twinkle.protect.protectCtopDisplay) { + form.protectReason.value += (form.protectReason.value ? '; ' : '') + Twinkle.protect.protectCtop; + } }; Twinkle.protect.callbacks = { From 1a0480db3f083b945f2e0f27c3b4fd3844d02842 Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Wed, 8 Oct 2025 11:34:25 +0200 Subject: [PATCH 2/7] protect.js: split GS from CTOP, add all codes --- src/modules/twinkleprotect.js | 122 +++++++++++++++++++++++++++++----- 1 file changed, 106 insertions(+), 16 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index b9117b95e..ea5a6a71c 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -511,6 +511,27 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct list: Twinkle.protect.ctopCodes, tooltip: 'Code of the contentious topic in which arbitration enforcement takes place.' }); + field2.append({ + type: 'checkbox', + event: Twinkle.protect.callback.annotateProtectReason, + list: [ + { + label: 'GS action', + name: 'protectReason_notes_gs', + checked: false, + value: 'general sanctions enforcement per [[WP:GS]]' + } + ], + tooltip: 'Add a note to the protection log that this is a general sanctions enforcement action.' + }); + field2.append({ + type: 'select', + event: Twinkle.protect.callback.annotateProtectReason, + label: 'GS code', + name: 'protectReason_notes_gsCode', + list: Twinkle.protect.gsCodes, + tooltip: 'Code of the general sanction in which enforcement takes place.' + }); if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto' || mw.config.get('wgNamespaceNumber') === 710) { // tagging isn't relevant for non-existing, module, or TimedText pages break; } @@ -614,6 +635,7 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct $(e.target.form).find('fieldset[name="field2"] select').parent().css({ display: 'inline-block', marginRight: '0.5em' }); $(e.target.form).find('fieldset[name="field2"] input[name="protectReason_notes_rfppRevid"]').parent().css({marginLeft: '15px'}).hide(); $(e.target.form).find('fieldset[name="field2"] select[name="protectReason_notes_ctopCode"]').parent().css({ display: 'block', marginLeft: '15px'}).hide(); // override inline-block + $(e.target.form).find('fieldset[name="field2"] select[name="protectReason_notes_gsCode"]').parent().css({ display: 'block', marginLeft: '15px'}).hide(); } // re-add protection level and log info, if it's available @@ -1020,16 +1042,65 @@ Twinkle.protect.protectionTags = [ .filter((type) => hasFlaggedRevs || type.label !== 'Pending changes templates'); Twinkle.protect.ctopCodes = [ - { label: 'Specify a code...', value: '' }, - { label: 'RUSUKR – Russo-Ukrainian War', value: 'RUSUKR' }, - { label: 'A-I – Arab-Israeli conflict', value: 'A-I' }, - { label: 'KURD – Kurds and Kurdistan', value: 'KURD' }, - { label: 'A-A – Armenia-Azerbaijan', value: 'A-A' }, - { label: 'APL – Antisemitism in Poland', value: 'APL' }, - { label: 'SASG – South Asian social groups', value: 'SASG' }, - { label: 'IMH – Indian military history', value: 'IMH' } + { value: '', label: 'Specify a code...' }, + { value: 'A-A', label: 'Armenia-Azerbaijan' }, + { value: 'AB', label: 'Abortion' }, + { value: 'A-I', label: 'Palestine-Israel articles' }, + { value: 'AP', label: 'American politics' }, + { value: 'APL', label: 'Antisemitism in Poland' }, + { value: 'AT', label: 'Article titles and capitalisation' }, + { value: 'BLP', label: 'Editing of Biographies of Living Persons' }, + { value: 'CAM', label: 'Acupuncture' }, + { value: 'CC', label: 'Climate change' }, + { value: 'CF', label: 'Pseudoscience' }, + { value: 'CID', label: 'Civility in infobox discussions' }, + { value: 'COVID', label: 'COVID-19' }, + { value: 'EE', label: 'Eastern Europe' }, + { value: 'FG', label: 'Falun Gong' }, + { value: 'GC', label: 'Gun control' }, + { value: 'GG', label: 'Gender and sexuality' }, + { value: 'GMO', label: 'Genetically modified organisms' }, + { value: 'HORN', label: 'Horn of Africa' }, + { value: 'IMH', label: 'Indian military history' }, + { value: 'IRP', label: 'Iranian politics' }, + { value: 'KURD', label: 'Kurds and Kurdistan' }, + { value: 'RI', label: 'Race and intelligence' }, + { value: 'RNE', label: 'Historical elections' }, + { value: 'SA', label: 'South Asia' }, + { value: 'SASG', label: 'South Asian social groups' }, + { value: 'TT', label: 'The Troubles' }, + { value: 'YA', label: 'Yasuke' } ]; +// We add the code at the front of each label +Twinkle.protect.ctopCodes.forEach((item) => { + if (item.value) { + item.label = `${item.value} – ${item.label}`; + } +}); + +Twinkle.protect.gsCodes = [ + { value: '', label: 'Specify a code...' }, + { value: 'AA', label: 'Armenia and Azerbaijan' }, + { value: 'ACAS', label: 'Assyrian, Chaldean, Aramean and Syriac topics' }, + { value: 'CRYPTO', label: 'Blockchain and cryptocurrency' }, + { value: 'KURD', label: 'Kurds and Kurdistan' }, + { value: 'MJ', label: 'Michael Jackson' }, + { value: 'PAGEANT', label: 'Beauty pageants' }, + { value: 'PW', label: 'Professional wrestling' }, + { value: 'RUSUKR', label: 'Russo-Ukrainian War' }, + { value: 'SCW&ISIL', label: 'Syrian Civil War and ISIL' }, + { value: 'UKU', label: 'Units in the United Kingdom' }, + { value: 'UYGHUR', label: 'Uyghur genocide' } +]; + +// We add the code at the front of each label +Twinkle.protect.gsCodes.forEach((item) => { + if (item.value) { + item.label = `${item.value} – ${item.label}`; + } +}); + Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { const form = e.target.form; @@ -1426,11 +1497,13 @@ Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) { }; Twinkle.protect.protectReasonAnnotations = []; -Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT]]'; -Twinkle.protect.protectCtopDisplay = false; +Twinkle.protect.protectText = {ctop: 'arbitration enforcement per [[WP:CT]]', gs: 'general sanctions enforcement per [[WP:GS]]'}; +Twinkle.protect.protectDisplay = {ctop: false, gs: false}; Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallbackAnnotateProtectReason(e) { const form = e.target.form; - const protectReason = form.protectReason.value.replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectReasonAnnotations.join(': '))), '').replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectCtop)), ''); + const protectReason = form.protectReason.value.replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectReasonAnnotations.join(': '))), '') + .replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectText.ctop)), '') + .replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectText.gs)), ''); if (this.name === 'protectReason_notes_rfpp') { if (this.checked) { @@ -1448,7 +1521,7 @@ Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallback Twinkle.protect.protectReasonAnnotations.push(permalink); } } else if (this.name === 'protectReason_notes_ctop') { - Twinkle.protect.protectCtopDisplay = this.checked; + Twinkle.protect.protectDisplay.ctop = this.checked; if (this.checked) { $(form.protectReason_notes_ctopCode).parent().show(); } else { @@ -1456,9 +1529,22 @@ Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallback } } else if (this.name === 'protectReason_notes_ctopCode') { if (e.target.value.length) { - Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT/' + e.target.value + ']]'; + Twinkle.protect.protectText.ctop = 'arbitration enforcement per [[WP:CT/' + e.target.value + ']]'; } else { - Twinkle.protect.protectCtop = 'arbitration enforcement per [[WP:CT]]'; + Twinkle.protect.protectText.ctop = 'arbitration enforcement per [[WP:CT]]'; + } + } else if (this.name === 'protectReason_notes_gs') { + Twinkle.protect.protectDisplay.gs = this.checked; + if (this.checked) { + $(form.protectReason_notes_gsCode).parent().show(); + } else { + $(form.protectReason_notes_gsCode).parent().hide(); + } + } else if (this.name === 'protectReason_notes_gsCode') { + if (e.target.value.length) { + Twinkle.protect.protectText.gs = 'general sanctions enforcement per [[WP:GS/' + e.target.value + ']]'; + } else { + Twinkle.protect.protectText.gs = 'general sanctions enforcement per [[WP:GS]]'; } } @@ -1468,8 +1554,12 @@ Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallback form.protectReason.value = (protectReason ? protectReason + '; ' : '') + Twinkle.protect.protectReasonAnnotations.join(': '); } - if (Twinkle.protect.protectCtopDisplay) { - form.protectReason.value += (form.protectReason.value ? '; ' : '') + Twinkle.protect.protectCtop; + if (Twinkle.protect.protectDisplay.ctop) { + form.protectReason.value += (form.protectReason.value ? '; ' : '') + Twinkle.protect.protectText.ctop; + } + + if (Twinkle.protect.protectDisplay.gs) { + form.protectReason.value += (form.protectReason.value ? '; ' : '') + Twinkle.protect.protectText.gs; } }; From 38dec9181e75887f82d92b1ed04ebb0fba3236ec Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sun, 2 Nov 2025 02:40:47 +0100 Subject: [PATCH 3/7] protect.js: use matching and aliases for ctops --- src/modules/twinkleprotect.js | 41 +++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index ea5a6a71c..a8763f8ce 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -636,6 +636,35 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct $(e.target.form).find('fieldset[name="field2"] input[name="protectReason_notes_rfppRevid"]').parent().css({marginLeft: '15px'}).hide(); $(e.target.form).find('fieldset[name="field2"] select[name="protectReason_notes_ctopCode"]').parent().css({ display: 'block', marginLeft: '15px'}).hide(); // override inline-block $(e.target.form).find('fieldset[name="field2"] select[name="protectReason_notes_gsCode"]').parent().css({ display: 'block', marginLeft: '15px'}).hide(); + + if (!Twinkle.getPref('oldSelect')) { + $('select[name=protectReason_notes_ctopCode], select[name=protectReason_notes_gsCode]') + .select2({ + theme: 'default select2-morebits', + width: '80%', + matcher: $.fn.select2.defaults.defaults.matcher, + templateResult: Morebits.select2.highlightSearchMatches, + language: { + searching: Morebits.select2.queryInterceptor + } + }) + .change(Twinkle.protect.callback.annotateProtectReason); + + $('.select2-selection').on('keydown', Morebits.select2.autoStart).trigger('focus'); + + mw.util.addCSS( + // Increase height + '.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 250px; }' + + + // Reduce padding + '.select2-results .select2-results__option { padding-top: 1px; padding-bottom: 1px; }' + + '.select2-results .select2-results__group { padding-top: 1px; padding-bottom: 1px; } ' + + + // Adjust font size + '.select2-container .select2-dropdown .select2-results { font-size: 13px; }' + + '.select2-container .selection .select2-selection__rendered { font-size: 13px; }' + ); + } } // re-add protection level and log info, if it's available @@ -1043,9 +1072,9 @@ Twinkle.protect.protectionTags = [ Twinkle.protect.ctopCodes = [ { value: '', label: 'Specify a code...' }, - { value: 'A-A', label: 'Armenia-Azerbaijan' }, + { value: 'A-A', label: 'Armenia-Azerbaijan', aliases: ['AA'] }, { value: 'AB', label: 'Abortion' }, - { value: 'A-I', label: 'Palestine-Israel articles' }, + { value: 'A-I', label: 'Palestine-Israel articles', aliases: ['AI', 'PIA'] }, { value: 'AP', label: 'American politics' }, { value: 'APL', label: 'Antisemitism in Poland' }, { value: 'AT', label: 'Article titles and capitalisation' }, @@ -1067,7 +1096,7 @@ Twinkle.protect.ctopCodes = [ { value: 'RI', label: 'Race and intelligence' }, { value: 'RNE', label: 'Historical elections' }, { value: 'SA', label: 'South Asia' }, - { value: 'SASG', label: 'South Asian social groups' }, + { value: 'SASG', label: 'South Asian social groups', aliases: ['GSCASTE'] }, { value: 'TT', label: 'The Troubles' }, { value: 'YA', label: 'Yasuke' } ]; @@ -1075,7 +1104,11 @@ Twinkle.protect.ctopCodes = [ // We add the code at the front of each label Twinkle.protect.ctopCodes.forEach((item) => { if (item.value) { - item.label = `${item.value} – ${item.label}`; + if (item.aliases) { + item.label = `${item.value}, ${item.aliases.join(', ')} – ${item.label}`; + } else { + item.label = `${item.value} – ${item.label}`; + } } }); From abd26e7a24b7991bda5d164ecd372b7b64dc1481 Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sun, 2 Nov 2025 19:37:50 +0100 Subject: [PATCH 4/7] protect.js: add dash-insensitive matching to reduce alias count --- src/modules/twinkleprotect.js | 6 +++--- src/morebits.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index a8763f8ce..4a9289c6f 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -642,7 +642,7 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct .select2({ theme: 'default select2-morebits', width: '80%', - matcher: $.fn.select2.defaults.defaults.matcher, + matcher: Morebits.select2.matchers.dashInsensitive, templateResult: Morebits.select2.highlightSearchMatches, language: { searching: Morebits.select2.queryInterceptor @@ -1072,9 +1072,9 @@ Twinkle.protect.protectionTags = [ Twinkle.protect.ctopCodes = [ { value: '', label: 'Specify a code...' }, - { value: 'A-A', label: 'Armenia-Azerbaijan', aliases: ['AA'] }, + { value: 'A-A', label: 'Armenia-Azerbaijan' }, { value: 'AB', label: 'Abortion' }, - { value: 'A-I', label: 'Palestine-Israel articles', aliases: ['AI', 'PIA'] }, + { value: 'A-I', label: 'Palestine-Israel articles', aliases: ['PIA'] }, { value: 'AP', label: 'American politics' }, { value: 'APL', label: 'Antisemitism in Poland' }, { value: 'AT', label: 'Article titles and capitalisation' }, diff --git a/src/morebits.js b/src/morebits.js index e0285e7a5..318983483 100644 --- a/src/morebits.js +++ b/src/morebits.js @@ -1668,6 +1668,23 @@ Morebits.select2 = { return result; } return null; + }, + + dashInsensitive: function(params, data) { + const originalMatcher = $.fn.select2.defaults.defaults.matcher; + const modifiedParams = $.extend(true, {}, params); + if (modifiedParams.term) { + modifiedParams.term = modifiedParams.term.replace(/-/g, ''); + } + const modifiedData = $.extend(true, {}, data); + modifiedData.text = modifiedData.text.replace(/-/g, ''); + + const result = originalMatcher(modifiedParams, modifiedData); + + if (result) { + return data; + } + return null; } }, From 6a0e87bdfc17fb2e8bee5727ad4066df2aa64c7d Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sat, 22 Nov 2025 01:25:50 +0100 Subject: [PATCH 5/7] protect.js: retrieve from on-wiki json --- src/modules/twinkleprotect.js | 106 ++++++++++++++++------------------ 1 file changed, 49 insertions(+), 57 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index 4a9289c6f..b9e0ca057 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -315,10 +315,12 @@ Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectC Morebits.Status[statusLevel]('Current protection level', protectionNode); }; -Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) { +Twinkle.protect.callback.changeAction = async function twinkleprotectCallbackChangeAction(e) { let field_preset; let field1; let field2; + let ctopCodes; + let gsCodes; switch (e.target.values) { case 'protect': @@ -503,12 +505,13 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct ], tooltip: 'Add a note to the protection log that this is an arbitration enforcement action.' }); + ctopCodes = await Twinkle.protect.ctopCodes; field2.append({ type: 'select', event: Twinkle.protect.callback.annotateProtectReason, label: 'CTOP code', name: 'protectReason_notes_ctopCode', - list: Twinkle.protect.ctopCodes, + list: ctopCodes, tooltip: 'Code of the contentious topic in which arbitration enforcement takes place.' }); field2.append({ @@ -524,12 +527,13 @@ Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAct ], tooltip: 'Add a note to the protection log that this is a general sanctions enforcement action.' }); + gsCodes = await Twinkle.protect.gsCodes; field2.append({ type: 'select', event: Twinkle.protect.callback.annotateProtectReason, label: 'GS code', name: 'protectReason_notes_gsCode', - list: Twinkle.protect.gsCodes, + list: gsCodes, tooltip: 'Code of the general sanction in which enforcement takes place.' }); if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto' || mw.config.get('wgNamespaceNumber') === 710) { // tagging isn't relevant for non-existing, module, or TimedText pages @@ -1070,70 +1074,58 @@ Twinkle.protect.protectionTags = [ // Filter FlaggedRevs .filter((type) => hasFlaggedRevs || type.label !== 'Pending changes templates'); -Twinkle.protect.ctopCodes = [ - { value: '', label: 'Specify a code...' }, - { value: 'A-A', label: 'Armenia-Azerbaijan' }, - { value: 'AB', label: 'Abortion' }, - { value: 'A-I', label: 'Palestine-Israel articles', aliases: ['PIA'] }, - { value: 'AP', label: 'American politics' }, - { value: 'APL', label: 'Antisemitism in Poland' }, - { value: 'AT', label: 'Article titles and capitalisation' }, - { value: 'BLP', label: 'Editing of Biographies of Living Persons' }, - { value: 'CAM', label: 'Acupuncture' }, - { value: 'CC', label: 'Climate change' }, - { value: 'CF', label: 'Pseudoscience' }, - { value: 'CID', label: 'Civility in infobox discussions' }, - { value: 'COVID', label: 'COVID-19' }, - { value: 'EE', label: 'Eastern Europe' }, - { value: 'FG', label: 'Falun Gong' }, - { value: 'GC', label: 'Gun control' }, - { value: 'GG', label: 'Gender and sexuality' }, - { value: 'GMO', label: 'Genetically modified organisms' }, - { value: 'HORN', label: 'Horn of Africa' }, - { value: 'IMH', label: 'Indian military history' }, - { value: 'IRP', label: 'Iranian politics' }, - { value: 'KURD', label: 'Kurds and Kurdistan' }, - { value: 'RI', label: 'Race and intelligence' }, - { value: 'RNE', label: 'Historical elections' }, - { value: 'SA', label: 'South Asia' }, - { value: 'SASG', label: 'South Asian social groups', aliases: ['GSCASTE'] }, - { value: 'TT', label: 'The Troubles' }, - { value: 'YA', label: 'Yasuke' } -]; +Twinkle.protect.ctopCodes = Morebits.wiki.getCachedJson('Template:Ds/topics.json'); + +Twinkle.protect.ctopCodes = Twinkle.protect.ctopCodes.then((ctopCodes) => { -// We add the code at the front of each label -Twinkle.protect.ctopCodes.forEach((item) => { - if (item.value) { - if (item.aliases) { - item.label = `${item.value}, ${item.aliases.join(', ')} – ${item.label}`; +// These codes are not added to the template as they are not standalone contentious topics +ctopCodes['Indian military history'] = { code: 'imh', label: 'Indian military history' }; +ctopCodes['South Asian social groups'] = { code: 'sasg', label: 'South Asian social groups', aliases: ['gscaste'] }; + +const codes = Object.values(ctopCodes).sort((a, b) => a.code.localeCompare(b.code)); +codes.forEach((val) => { + val.value = val.code.toUpperCase(); + if (val.value) { + if (val.page) { + val.label = val.page.split('/').pop(); + } + if (val.aliases) { + val.aliases = val.aliases.map((alias) => alias.toUpperCase()); + val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; } else { - item.label = `${item.value} – ${item.label}`; + val.label = `${val.value} – ${val.label}`; } + } else { + val.label = 'Specify a code...'; } }); -Twinkle.protect.gsCodes = [ - { value: '', label: 'Specify a code...' }, - { value: 'AA', label: 'Armenia and Azerbaijan' }, - { value: 'ACAS', label: 'Assyrian, Chaldean, Aramean and Syriac topics' }, - { value: 'CRYPTO', label: 'Blockchain and cryptocurrency' }, - { value: 'KURD', label: 'Kurds and Kurdistan' }, - { value: 'MJ', label: 'Michael Jackson' }, - { value: 'PAGEANT', label: 'Beauty pageants' }, - { value: 'PW', label: 'Professional wrestling' }, - { value: 'RUSUKR', label: 'Russo-Ukrainian War' }, - { value: 'SCW&ISIL', label: 'Syrian Civil War and ISIL' }, - { value: 'UKU', label: 'Units in the United Kingdom' }, - { value: 'UYGHUR', label: 'Uyghur genocide' } -]; +return codes; +}); + +Twinkle.protect.gsCodes = Morebits.wiki.getCachedJson('Template:Gs/topics.json'); -// We add the code at the front of each label -Twinkle.protect.gsCodes.forEach((item) => { - if (item.value) { - item.label = `${item.value} – ${item.label}`; +Twinkle.protect.gsCodes = Twinkle.protect.gsCodes.then((gsCodes) => { + +const codes = Object.values(gsCodes).sort((a, b) => a.code.localeCompare(b.code)); + +codes.forEach((val) => { + val.value = val.code.toUpperCase(); + if (val.value && val.page) { + if (val.aliases) { + val.aliases = val.aliases.map((alias) => alias.toUpperCase()); + val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; + } else { + val.label = `${val.value} – ${val.label}`; + } + } else { + val.label = 'Specify a code...'; } }); +return codes; +}); + Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { const form = e.target.form; From 07132e6b4f72024b10922d4769d2e833e422cbef Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sat, 22 Nov 2025 22:14:17 +0100 Subject: [PATCH 6/7] protect.js: fix loading --- src/modules/twinkleprotect.js | 84 ++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index b9e0ca057..655da344a 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -505,7 +505,7 @@ Twinkle.protect.callback.changeAction = async function twinkleprotectCallbackCha ], tooltip: 'Add a note to the protection log that this is an arbitration enforcement action.' }); - ctopCodes = await Twinkle.protect.ctopCodes; + [ctopCodes, gsCodes] = await Twinkle.protect.fetchTopics(); field2.append({ type: 'select', event: Twinkle.protect.callback.annotateProtectReason, @@ -527,7 +527,6 @@ Twinkle.protect.callback.changeAction = async function twinkleprotectCallbackCha ], tooltip: 'Add a note to the protection log that this is a general sanctions enforcement action.' }); - gsCodes = await Twinkle.protect.gsCodes; field2.append({ type: 'select', event: Twinkle.protect.callback.annotateProtectReason, @@ -1074,57 +1073,62 @@ Twinkle.protect.protectionTags = [ // Filter FlaggedRevs .filter((type) => hasFlaggedRevs || type.label !== 'Pending changes templates'); -Twinkle.protect.ctopCodes = Morebits.wiki.getCachedJson('Template:Ds/topics.json'); +Twinkle.protect.fetchTopics = async function twinkleprotectFetchTopics() { + let ctopCodes, gsCodes; + ctopCodes = Morebits.wiki.getCachedJson('Template:Ds/topics.json'); -Twinkle.protect.ctopCodes = Twinkle.protect.ctopCodes.then((ctopCodes) => { + ctopCodes = ctopCodes.then((ctopCodes) => { -// These codes are not added to the template as they are not standalone contentious topics -ctopCodes['Indian military history'] = { code: 'imh', label: 'Indian military history' }; -ctopCodes['South Asian social groups'] = { code: 'sasg', label: 'South Asian social groups', aliases: ['gscaste'] }; + // These codes are not added to the template as they are not standalone contentious topics + ctopCodes['Indian military history'] = { code: 'imh', label: 'Indian military history' }; + ctopCodes['South Asian social groups'] = { code: 'sasg', label: 'South Asian social groups', aliases: ['gscaste'] }; -const codes = Object.values(ctopCodes).sort((a, b) => a.code.localeCompare(b.code)); -codes.forEach((val) => { - val.value = val.code.toUpperCase(); - if (val.value) { - if (val.page) { - val.label = val.page.split('/').pop(); - } - if (val.aliases) { - val.aliases = val.aliases.map((alias) => alias.toUpperCase()); - val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; + const codes = Object.values(ctopCodes).sort((a, b) => a.code.localeCompare(b.code)); + codes.forEach((val) => { + val.value = val.code.toUpperCase(); + if (val.value) { + if (val.page) { + val.label = val.page.split('/').pop(); + } + if (val.aliases) { + val.aliases = val.aliases.map((alias) => alias.toUpperCase()); + val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; + } else { + val.label = `${val.value} – ${val.label}`; + } } else { - val.label = `${val.value} – ${val.label}`; + val.label = 'Specify a code...'; } - } else { - val.label = 'Specify a code...'; - } -}); + }); -return codes; -}); + return codes; + }); -Twinkle.protect.gsCodes = Morebits.wiki.getCachedJson('Template:Gs/topics.json'); + gsCodes = Morebits.wiki.getCachedJson('Template:Gs/topics.json'); -Twinkle.protect.gsCodes = Twinkle.protect.gsCodes.then((gsCodes) => { + gsCodes = gsCodes.then((gsCodes) => { -const codes = Object.values(gsCodes).sort((a, b) => a.code.localeCompare(b.code)); + const codes = Object.values(gsCodes).sort((a, b) => a.value.localeCompare(b.value)); -codes.forEach((val) => { - val.value = val.code.toUpperCase(); - if (val.value && val.page) { - if (val.aliases) { - val.aliases = val.aliases.map((alias) => alias.toUpperCase()); - val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; + codes.forEach((val) => { + val.value = val.code.toUpperCase(); + if (val.value && val.page) { + if (val.aliases) { + val.aliases = val.aliases.map((alias) => alias.toUpperCase()); + val.label = `${val.value}, ${val.aliases.join(', ')} – ${val.label}`; + } else { + val.label = `${val.value} – ${val.label}`; + } } else { - val.label = `${val.value} – ${val.label}`; + val.label = 'Specify a code...'; } - } else { - val.label = 'Specify a code...'; - } -}); + }); + + return codes; + }); -return codes; -}); + return Promise.all([ctopCodes, gsCodes]); +}; Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { const form = e.target.form; From f0c23fe0d11e4f0db5ab1af418b4dc23ba4be002 Mon Sep 17 00:00:00 2001 From: IlyasLebleu Date: Sun, 23 Nov 2025 13:29:13 +0100 Subject: [PATCH 7/7] protect: fix typo --- src/modules/twinkleprotect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/twinkleprotect.js b/src/modules/twinkleprotect.js index 655da344a..91b5a2fec 100644 --- a/src/modules/twinkleprotect.js +++ b/src/modules/twinkleprotect.js @@ -1111,7 +1111,7 @@ Twinkle.protect.fetchTopics = async function twinkleprotectFetchTopics() { const codes = Object.values(gsCodes).sort((a, b) => a.value.localeCompare(b.value)); codes.forEach((val) => { - val.value = val.code.toUpperCase(); + val.value = val.value.toUpperCase(); if (val.value && val.page) { if (val.aliases) { val.aliases = val.aliases.map((alias) => alias.toUpperCase());