From dea355bf959ed2c43629dae928b034c2c4f82e55 Mon Sep 17 00:00:00 2001 From: Michael Daniels Date: Sat, 7 Dec 2024 20:04:16 -0500 Subject: [PATCH] delete disabled files --- modules/friendlyshared-disabled.js | 237 --- modules/friendlytag-disabled.js | 2104 ------------------------ modules/friendlytalkback-disabled.js | 416 ----- modules/twinklearv-disabled.js | 981 ------------ modules/twinkleblock-disabled.js | 2063 ------------------------ modules/twinkleprotect-disabled.js | 1664 ------------------- modules/twinklewarn-disabled.js | 1946 ---------------------- modules/twinklexfd-disabled.js | 2216 -------------------------- 8 files changed, 11627 deletions(-) delete mode 100644 modules/friendlyshared-disabled.js delete mode 100644 modules/friendlytag-disabled.js delete mode 100644 modules/friendlytalkback-disabled.js delete mode 100644 modules/twinklearv-disabled.js delete mode 100644 modules/twinkleblock-disabled.js delete mode 100644 modules/twinkleprotect-disabled.js delete mode 100644 modules/twinklewarn-disabled.js delete mode 100644 modules/twinklexfd-disabled.js diff --git a/modules/friendlyshared-disabled.js b/modules/friendlyshared-disabled.js deleted file mode 100644 index cbdd5d638..000000000 --- a/modules/friendlyshared-disabled.js +++ /dev/null @@ -1,237 +0,0 @@ -// - - -(function($) { - - -/* - **************************************** - *** friendlyshared.js: Shared IP tagging module - **************************************** - * Mode of invocation: Tab ("Shared") - * Active on: IP user talk pages - */ - -Twinkle.shared = function friendlyshared() { - if (mw.config.get('wgNamespaceNumber') === 3 && mw.util.isIPAddress(mw.config.get('wgTitle'))) { - var username = mw.config.get('wgRelevantUserName'); - Twinkle.addPortletLink(function() { - Twinkle.shared.callback(username); - }, 'Shared IP', 'friendly-shared', 'Shared IP tagging'); - } -}; - -Twinkle.shared.callback = function friendlysharedCallback() { - var Window = new Morebits.simpleWindow(600, 450); - Window.setTitle('Shared IP address tagging'); - Window.setScriptName('Twinkle'); - Window.addFooterLink('Shared prefs', 'WP:TW/PREF#shared'); - Window.addFooterLink('Twinkle help', 'WP:TW/DOC#shared'); - Window.addFooterLink('Give feedback', 'WT:TW'); - - var form = new Morebits.quickForm(Twinkle.shared.callback.evaluate); - - var div = form.append({ - type: 'div', - id: 'sharedip-templatelist', - className: 'morebits-scrollbox' - } - ); - div.append({ type: 'header', label: 'Shared IP address templates' }); - div.append({ type: 'radio', name: 'template', list: Twinkle.shared.standardList, - event: function(e) { - Twinkle.shared.callback.change_shared(e); - e.stopPropagation(); - } - }); - - var org = form.append({ type: 'field', label: 'Fill in other details (optional) and click "Submit"' }); - org.append({ - type: 'input', - name: 'organization', - label: 'IP address owner/operator', - disabled: true, - tooltip: 'You can optionally enter the name of the organization that owns/operates the IP address. You can use wikimarkup if necessary.' - } - ); - org.append({ - type: 'input', - name: 'host', - label: 'Host name (optional)', - disabled: true, - tooltip: 'The host name (for example, proxy.example.com) can be optionally entered here and will be linked by the template.' - } - ); - org.append({ - type: 'input', - name: 'contact', - label: 'Contact information (only if requested)', - disabled: true, - tooltip: 'You can optionally enter some contact details for the organization. Use this parameter only if the organization has specifically requested that it be added. You can use wikimarkup if necessary.' - } - ); - - var previewlink = document.createElement('a'); - $(previewlink).click(function() { - Twinkle.shared.preview(result); - }); - previewlink.style.cursor = 'pointer'; - previewlink.textContent = 'Preview'; - form.append({ type: 'div', id: 'sharedpreview', label: [ previewlink ] }); - form.append({ type: 'submit' }); - - var result = form.render(); - Window.setContent(result); - Window.display(); -}; - -Twinkle.shared.standardList = [ - { - label: '{{Shared IP}}: standard shared IP address template', - value: 'Shared IP', - tooltip: 'IP user talk page template that shows helpful information to IP users and those wishing to warn, block or ban them' - }, - { - label: '{{Shared IP edu}}: shared IP address template modified for educational institutions', - value: 'Shared IP edu' - }, - { - label: '{{Shared IP corp}}: shared IP address template modified for businesses', - value: 'Shared IP corp' - }, - { - label: '{{Shared IP public}}: shared IP address template modified for public terminals', - value: 'Shared IP public' - }, - { - label: '{{Shared IP gov}}: shared IP address template modified for government agencies or facilities', - value: 'Shared IP gov' - }, - { - label: '{{Dynamic IP}}: shared IP address template modified for organizations with dynamic addressing', - value: 'Dynamic IP' - }, - { - label: '{{Static IP}}: shared IP address template modified for static IP addresses', - value: 'Static IP' - }, - { - label: '{{ISP}}: shared IP address template modified for ISP organizations (specifically proxies)', - value: 'ISP' - }, - { - label: '{{Mobile IP}}: shared IP address template modified for mobile phone companies and their customers', - value: 'Mobile IP' - }, - { - label: '{{Whois}}: template for IP addresses in need of monitoring, but unknown whether static, dynamic or shared', - value: 'Whois' - } -]; - -Twinkle.shared.callback.change_shared = function friendlysharedCallbackChangeShared(e) { - e.target.form.contact.disabled = e.target.value !== 'Shared IP edu'; // only supported by {{Shared IP edu}} - e.target.form.organization.disabled = false; - e.target.form.host.disabled = e.target.value === 'Whois'; // host= not supported by {{Whois}} -}; - -Twinkle.shared.callbacks = { - main: function(pageobj) { - var params = pageobj.getCallbackParameters(); - var pageText = pageobj.getPageText(); - var found = false; - - for (var i = 0; i < Twinkle.shared.standardList.length; i++) { - var tagRe = new RegExp('(\\{\\{' + Twinkle.shared.standardList[i].value + '(\\||\\}\\}))', 'im'); - if (tagRe.exec(pageText)) { - Morebits.status.warn('Info', 'Found {{' + Twinkle.shared.standardList[i].value + '}} on the user\'s talk page already...aborting'); - found = true; - } - } - - if (found) { - return; - } - - Morebits.status.info('Info', 'Will add the shared IP address template to the top of the user\'s talk page.'); - var text = Twinkle.shared.getTemplateWikitext(params); - - var summaryText = 'Added {{[[Template:' + params.template + '|' + params.template + ']]}} template.'; - pageobj.setPageText(text + pageText); - pageobj.setEditSummary(summaryText); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setMinorEdit(Twinkle.getPref('markSharedIPAsMinor')); - pageobj.setCreateOption('recreate'); - pageobj.save(); - } -}; - -Twinkle.shared.preview = function(form) { - var input = Morebits.quickForm.getInputData(form); - if (input.template) { - var previewDialog = new Morebits.simpleWindow(700, 500); - previewDialog.setTitle('Shared IP template preview'); - previewDialog.setScriptName('Add Shared IP template'); - previewDialog.setModality(true); - - var previewdiv = document.createElement('div'); - previewdiv.style.marginLeft = previewdiv.style.marginRight = '0.5em'; - previewdiv.style.fontSize = 'small'; - previewDialog.setContent(previewdiv); - - var previewer = new Morebits.wiki.preview(previewdiv); - previewer.beginRender(Twinkle.shared.getTemplateWikitext(input), mw.config.get('wgPageName')); - - var submit = document.createElement('input'); - submit.setAttribute('type', 'submit'); - submit.setAttribute('value', 'Close'); - previewDialog.addContent(submit); - - previewDialog.display(); - - $(submit).click(function() { - previewDialog.close(); - }); - } -}; - -Twinkle.shared.getTemplateWikitext = function(input) { - var text = '{{' + input.template + '|' + input.organization; - if (input.contact) { - text += '|' + input.contact; - } - if (input.host) { - text += '|host=' + input.host; - } - text += '}}\n\n'; - return text; -}; - -Twinkle.shared.callback.evaluate = function friendlysharedCallbackEvaluate(e) { - var params = Morebits.quickForm.getInputData(e.target); - if (!params.template) { - alert('You must select a shared IP address template to use!'); - return; - } - if (!params.organization) { - alert('You must input an organization for the {{' + params.template + '}} template!'); - return; - } - - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(e.target); - - Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); - Morebits.wiki.actionCompleted.notice = 'Tagging complete, reloading talk page in a few seconds'; - - var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'User talk page modification'); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.shared.callbacks.main); -}; - -Twinkle.addInitCallback(Twinkle.shared, 'shared'); -})(jQuery); - - -// diff --git a/modules/friendlytag-disabled.js b/modules/friendlytag-disabled.js deleted file mode 100644 index 3f6527af4..000000000 --- a/modules/friendlytag-disabled.js +++ /dev/null @@ -1,2104 +0,0 @@ -// - -(function($) { - - -/* - **************************************** - *** friendlytag.js: Tag module - **************************************** - * Mode of invocation: Tab ("Tag") - * Active on: Existing articles and drafts; file pages with a corresponding file - * which is local (not on Commons); all redirects - */ - -Twinkle.tag = function friendlytag() { - // redirect tagging - if (Morebits.isPageRedirect()) { - Twinkle.tag.mode = 'redirect'; - Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Tag redirect'); - // file tagging - } else if (mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory')) { - Twinkle.tag.mode = 'file'; - Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Add maintenance tags to file'); - // article/draft article tagging - } else if ([0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId')) { - Twinkle.tag.mode = 'article'; - // Can't remove tags when not viewing current version - Twinkle.tag.canRemove = (mw.config.get('wgCurRevisionId') === mw.config.get('wgRevisionId')) && - // Disabled on latest diff because the diff slider could be used to slide - // away from the latest diff without causing the script to reload - !mw.config.get('wgDiffNewId'); - Twinkle.addPortletLink(Twinkle.tag.callback, 'Tag', 'friendly-tag', 'Add or remove article maintenance tags'); - } -}; - -Twinkle.tag.checkedTags = []; - -Twinkle.tag.callback = function friendlytagCallback() { - var Window = new Morebits.simpleWindow(630, Twinkle.tag.mode === 'article' ? 500 : 400); - Window.setScriptName('Twinkle'); - // anyone got a good policy/guideline/info page/instructional page link?? - Window.addFooterLink('Tag prefs', 'WP:TW/PREF#tag'); - Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag'); - Window.addFooterLink('Give feedback', 'WT:TW'); - - var form = new Morebits.quickForm(Twinkle.tag.callback.evaluate); - - // if page is unreviewed, add a checkbox to the form so that user can pick whether or not to review it - let isPatroller = mw.config.get('wgUserGroups').some(r => ['patroller', 'sysop'].includes(r)); - if (isPatroller) { - new mw.Api().get({ - action: 'pagetriagelist', - format: 'json', - page_id: mw.config.get('wgArticleId') - }).then(function(response) { - // figure out whether the article is marked as reviewed in PageTriage - var isReviewed = false; - var isOldPage = response.pagetriagelist.result !== 'success' || response.pagetriagelist.pages.length === 0; - var isMarkedAsReviewed = response.pagetriagelist.pages[0].patrol_status > 0; - if (isOldPage || isMarkedAsReviewed) { - isReviewed = true; - } - - // if article is not marked as reviewed, show the "mark as reviewed" check box - if (!isReviewed) { - // Quickform is probably already rendered. Instead of using form.append(), we need to make an element and then append it using JQuery. - var checkbox = new Morebits.quickForm.element({ - type: 'checkbox', - list: [ - { - label: 'Mark the page as patrolled/reviewed', - value: 'patrol', - name: 'patrol', - checked: Twinkle.getPref('markTaggedPagesAsPatrolled') - } - ] - }); - var html = checkbox.render(); - $('.quickform').prepend(html); - } - }); - } - - form.append({ - type: 'input', - label: 'Filter tag list:', - name: 'quickfilter', - size: '30', - event: function twinkletagquickfilter() { - // flush the DOM of all existing underline spans - $allCheckboxDivs.find('.search-hit').each(function(i, e) { - var label_element = e.parentElement; - // This would convert - // to - label_element.innerHTML = label_element.textContent; - }); - - if (this.value) { - $allCheckboxDivs.hide(); - $allHeaders.hide(); - var searchString = this.value; - var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i'); - - $allCheckboxDivs.find('label').each(function () { - var label_text = this.textContent; - var searchHit = searchRegex.exec(label_text); - if (searchHit) { - var range = document.createRange(); - var textnode = this.childNodes[0]; - range.selectNodeContents(textnode); - range.setStart(textnode, searchHit.index); - range.setEnd(textnode, searchHit.index + searchString.length); - var underline_span = $('').addClass('search-hit').css('text-decoration', 'underline')[0]; - range.surroundContents(underline_span); - this.parentElement.style.display = 'block'; // show - } - }); - } else { - $allCheckboxDivs.show(); - $allHeaders.show(); - } - } - }); - - switch (Twinkle.tag.mode) { - case 'article': - Window.setTitle('Article maintenance tagging'); - - // Object.values is unavailable in IE 11 - var obj_values = Object.values || function (obj) { - return Object.keys(obj).map(function (key) { - return obj[key]; - }); - }; - - // Build sorting and lookup object flatObject, which is always - // needed but also used to generate the alphabetical list - Twinkle.tag.article.flatObject = {}; - obj_values(Twinkle.tag.article.tagList).forEach(function (group) { - obj_values(group).forEach(function (subgroup) { - if (Array.isArray(subgroup)) { - subgroup.forEach(function (item) { - Twinkle.tag.article.flatObject[item.tag] = item; - }); - } else { - Twinkle.tag.article.flatObject[subgroup.tag] = subgroup; - } - }); - }); - - - form.append({ - type: 'select', - name: 'sortorder', - label: 'View this list:', - tooltip: 'You can change the default view order in your Twinkle preferences (WP:TWPREFS).', - event: Twinkle.tag.updateSortOrder, - list: [ - { type: 'option', value: 'cat', label: 'By categories', selected: Twinkle.getPref('tagArticleSortOrder') === 'cat' }, - { type: 'option', value: 'alpha', label: 'In alphabetical order', selected: Twinkle.getPref('tagArticleSortOrder') === 'alpha' } - ] - }); - - - if (!Twinkle.tag.canRemove) { - var divElement = document.createElement('div'); - divElement.innerHTML = 'For removal of existing tags, please open Tag menu from the current version of article'; - form.append({ - type: 'div', - name: 'untagnotice', - label: divElement - }); - } - - form.append({ - type: 'div', - id: 'tagWorkArea', - className: 'morebits-scrollbox', - style: 'max-height: 28em' - }); - - form.append({ - type: 'checkbox', - list: [ - { - label: 'Group inside {{multiple issues}} if possible', - value: 'group', - name: 'group', - tooltip: 'If applying two or more templates supported by {{multiple issues}} and this box is checked, all supported templates will be grouped inside a {{multiple issues}} template.', - checked: Twinkle.getPref('groupByDefault') - } - ] - }); - - form.append({ - type: 'input', - label: 'Reason', - name: 'reason', - tooltip: 'Optional reason to be appended in edit summary. Recommended when removing tags.', - size: '60' - }); - - break; - - case 'file': - Window.setTitle('File maintenance tagging'); - - $.each(Twinkle.tag.fileList, function(groupName, group) { - form.append({ type: 'header', label: groupName }); - form.append({ type: 'checkbox', name: 'tags', list: group }); - }); - - if (Twinkle.getPref('customFileTagList').length) { - form.append({ type: 'header', label: 'Custom tags' }); - form.append({ type: 'checkbox', name: 'tags', list: Twinkle.getPref('customFileTagList') }); - } - break; - - case 'redirect': - Window.setTitle('Redirect tagging'); - - var i = 1; - $.each(Twinkle.tag.redirectList, function(groupName, group) { - form.append({ type: 'header', id: 'tagHeader' + i, label: groupName }); - var subdiv = form.append({ type: 'div', id: 'tagSubdiv' + i++ }); - $.each(group, function(subgroupName, subgroup) { - subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subgroupName) ] }); - subdiv.append({ - type: 'checkbox', - name: 'tags', - list: subgroup.map(function (item) { - return { value: item.tag, label: '{{' + item.tag + '}}: ' + item.description, subgroup: item.subgroup }; - }) - }); - }); - }); - - if (Twinkle.getPref('customRedirectTagList').length) { - form.append({ type: 'header', label: 'Custom tags' }); - form.append({ type: 'checkbox', name: 'tags', list: Twinkle.getPref('customRedirectTagList') }); - } - break; - - default: - alert('Twinkle.tag: unknown mode ' + Twinkle.tag.mode); - break; - } - - form.append({ type: 'submit', className: 'tw-tag-submit' }); - - var result = form.render(); - Window.setContent(result); - Window.display(); - - // for quick filter: - $allCheckboxDivs = $(result).find('[name$=tags]').parent(); - $allHeaders = $(result).find('h5, .quickformDescription'); - result.quickfilter.focus(); // place cursor in the quick filter field as soon as window is opened - result.quickfilter.autocomplete = 'off'; // disable browser suggestions - result.quickfilter.addEventListener('keypress', function(e) { - if (e.keyCode === 13) { // prevent enter key from accidentally submitting the form - e.preventDefault(); - return false; - } - }); - - if (Twinkle.tag.mode === 'article') { - - Twinkle.tag.alreadyPresentTags = []; - - if (Twinkle.tag.canRemove) { - // Look for existing maintenance tags in the lead section and put them in array - - // All tags are HTML table elements that are direct children of .mw-parser-output, - // except when they are within {{multiple issues}} - $('.mw-parser-output').children().each(function parsehtml(i, e) { - - // break out on encountering the first heading, which means we are no - // longer in the lead section - if (e.tagName === 'H2') { - return false; - } - - // The ability to remove tags depends on the template's {{ambox}} |name= - // parameter bearing the template's correct name (preferably) or a name that at - // least redirects to the actual name - - // All tags have their first class name as "box-" + template name - if (e.className.indexOf('box-') === 0) { - if (e.classList[0] === 'box-Multiple_issues') { - $(e).find('.ambox').each(function(idx, e) { - if (e.classList[0].indexOf('box-') === 0) { - var tag = e.classList[0].slice('box-'.length).replace(/_/g, ' '); - Twinkle.tag.alreadyPresentTags.push(tag); - } - }); - return true; // continue - } - - var tag = e.classList[0].slice('box-'.length).replace(/_/g, ' '); - Twinkle.tag.alreadyPresentTags.push(tag); - } - }); - - // {{Uncategorized}} and {{Improve categories}} are usually placed at the end - if ($('.box-Uncategorized').length) { - Twinkle.tag.alreadyPresentTags.push('Uncategorized'); - } - if ($('.box-Improve_categories').length) { - Twinkle.tag.alreadyPresentTags.push('Improve categories'); - } - - } - - // Add status text node after Submit button - var statusNode = document.createElement('small'); - statusNode.id = 'tw-tag-status'; - Twinkle.tag.status = { - // initial state; defined like this because these need to be available for reference - // in the click event handler - numAdded: 0, - numRemoved: 0 - }; - $('button.tw-tag-submit').after(statusNode); - - // fake a change event on the sort dropdown, to initialize the tag list - var evt = document.createEvent('Event'); - evt.initEvent('change', true, true); - result.sortorder.dispatchEvent(evt); - - } else { - // Redirects and files: Add a link to each template's description page - Morebits.quickForm.getElements(result, 'tags').forEach(generateLinks); - } -}; - - -// $allCheckboxDivs and $allHeaders are defined globally, rather than in the -// quickfilter event function, to avoid having to recompute them on every keydown -var $allCheckboxDivs, $allHeaders; - -Twinkle.tag.updateSortOrder = function(e) { - var form = e.target.form; - var sortorder = e.target.value; - Twinkle.tag.checkedTags = form.getChecked('tags'); - - var container = new Morebits.quickForm.element({ type: 'fragment' }); - - // function to generate a checkbox, with appropriate subgroup if needed - var makeCheckbox = function (item) { - var tag = item.tag, description = item.description; - var checkbox = { value: tag, label: '{{' + tag + '}}: ' + description }; - if (Twinkle.tag.checkedTags.indexOf(tag) !== -1) { - checkbox.checked = true; - } - checkbox.subgroup = item.subgroup; - return checkbox; - }; - - var makeCheckboxesForAlreadyPresentTags = function() { - container.append({ type: 'header', id: 'tagHeader0', label: 'Tags already present' }); - var subdiv = container.append({ type: 'div', id: 'tagSubdiv0' }); - var checkboxes = []; - var unCheckedTags = e.target.form.getUnchecked('existingTags'); - Twinkle.tag.alreadyPresentTags.forEach(function(tag) { - var checkbox = - { - value: tag, - label: '{{' + tag + '}}' + (Twinkle.tag.article.flatObject[tag] ? ': ' + Twinkle.tag.article.flatObject[tag].description : ''), - checked: unCheckedTags.indexOf(tag) === -1, - style: 'font-style: italic' - }; - - checkboxes.push(checkbox); - }); - subdiv.append({ - type: 'checkbox', - name: 'existingTags', - list: checkboxes - }); - }; - - - if (sortorder === 'cat') { // categorical sort order - // function to iterate through the tags and create a checkbox for each one - var doCategoryCheckboxes = function(subdiv, subgroup) { - var checkboxes = []; - $.each(subgroup, function(k, item) { - if (Twinkle.tag.alreadyPresentTags.indexOf(item.tag) === -1) { - checkboxes.push(makeCheckbox(item)); - } - }); - subdiv.append({ - type: 'checkbox', - name: 'tags', - list: checkboxes - }); - }; - - if (Twinkle.tag.alreadyPresentTags.length > 0) { - makeCheckboxesForAlreadyPresentTags(); - } - var i = 1; - // go through each category and sub-category and append lists of checkboxes - $.each(Twinkle.tag.article.tagList, function(groupName, group) { - container.append({ type: 'header', id: 'tagHeader' + i, label: groupName }); - var subdiv = container.append({ type: 'div', id: 'tagSubdiv' + i++ }); - if (Array.isArray(group)) { - doCategoryCheckboxes(subdiv, group); - } else { - $.each(group, function(subgroupName, subgroup) { - subdiv.append({ type: 'div', label: [ Morebits.htmlNode('b', subgroupName) ] }); - doCategoryCheckboxes(subdiv, subgroup); - }); - } - }); - } else { // alphabetical sort order - if (Twinkle.tag.alreadyPresentTags.length > 0) { - makeCheckboxesForAlreadyPresentTags(); - container.append({ type: 'header', id: 'tagHeader1', label: 'Available tags' }); - } - - // Avoid repeatedly resorting - Twinkle.tag.article.alphabeticalList = Twinkle.tag.article.alphabeticalList || Object.keys(Twinkle.tag.article.flatObject).sort(); - var checkboxes = []; - Twinkle.tag.article.alphabeticalList.forEach(function(tag) { - if (Twinkle.tag.alreadyPresentTags.indexOf(tag) === -1) { - checkboxes.push(makeCheckbox(Twinkle.tag.article.flatObject[tag])); - } - }); - container.append({ - type: 'checkbox', - name: 'tags', - list: checkboxes - }); - } - - // append any custom tags - if (Twinkle.getPref('customTagList').length) { - container.append({ type: 'header', label: 'Custom tags' }); - container.append({ type: 'checkbox', name: 'tags', - list: Twinkle.getPref('customTagList').map(function(el) { - el.checked = Twinkle.tag.checkedTags.indexOf(el.value) !== -1; - return el; - }) - }); - } - - var $workarea = $(form).find('#tagWorkArea'); - var rendered = container.render(); - $workarea.empty().append(rendered); - - // for quick filter: - $allCheckboxDivs = $workarea.find('[name=tags], [name=existingTags]').parent(); - $allHeaders = $workarea.find('h5, .quickformDescription'); - form.quickfilter.value = ''; // clear search, because the search results are not preserved over mode change - form.quickfilter.focus(); - - // style adjustments - $workarea.find('h5').css({ 'font-size': '110%' }); - $workarea.find('h5:not(:first-child)').css({ 'margin-top': '1em' }); - $workarea.find('div').filter(':has(span.quickformDescription)').css({ 'margin-top': '0.4em' }); - - Morebits.quickForm.getElements(form, 'existingTags').forEach(generateLinks); - Morebits.quickForm.getElements(form, 'tags').forEach(generateLinks); - - // tally tags added/removed, update statusNode text - var statusNode = document.getElementById('tw-tag-status'); - $('[name=tags], [name=existingTags]').click(function() { - if (this.name === 'tags') { - Twinkle.tag.status.numAdded += this.checked ? 1 : -1; - } else if (this.name === 'existingTags') { - Twinkle.tag.status.numRemoved += this.checked ? -1 : 1; - } - - var firstPart = 'Adding ' + Twinkle.tag.status.numAdded + ' tag' + (Twinkle.tag.status.numAdded > 1 ? 's' : ''); - var secondPart = 'Removing ' + Twinkle.tag.status.numRemoved + ' tag' + (Twinkle.tag.status.numRemoved > 1 ? 's' : ''); - statusNode.textContent = - (Twinkle.tag.status.numAdded ? ' ' + firstPart : '') + - (Twinkle.tag.status.numRemoved ? (Twinkle.tag.status.numAdded ? '; ' : ' ') + secondPart : ''); - }); -}; - -/** - * Adds a link to each template's description page - * @param {Morebits.quickForm.element} checkbox associated with the template - */ -var generateLinks = function(checkbox) { - var link = Morebits.htmlNode('a', '>'); - link.setAttribute('class', 'tag-template-link'); - var tagname = checkbox.values; - link.setAttribute('href', mw.util.getUrl( - (tagname.indexOf(':') === -1 ? 'Template:' : '') + - (tagname.indexOf('|') === -1 ? tagname : tagname.slice(0, tagname.indexOf('|'))) - )); - link.setAttribute('target', '_blank'); - $(checkbox).parent().append(['\u00A0', link]); -}; - - -// Tags for ARTICLES start here -Twinkle.tag.article = {}; - -// Shared across {{Rough translation}} and {{Not English}} -var translationSubgroups = [ - { - name: 'translationLanguage', - parameter: '1', - type: 'input', - label: 'Language of article (if known):', - tooltip: 'Consider looking at [[WP:LRC]] for help. If listing the article at PNT, please try to avoid leaving this box blank, unless you are completely unsure.' - } -].concat(mw.config.get('wgNamespaceNumber') === 0 ? [ - { - type: 'checkbox', - list: [ { - name: 'translationPostAtPNT', - label: 'List this article at Wikipedia:Pages needing translation into English (PNT)', - checked: true - } ] - }, - { - name: 'translationComments', - type: 'textarea', - label: 'Additional comments to post at PNT', - tooltip: 'Optional, and only relevant if "List this article ..." above is checked.' - } -] : []); - -// Subgroups for {{merge}}, {{merge-to}} and {{merge-from}} -var getMergeSubgroups = function(tag) { - var otherTagName = 'Merge'; - switch (tag) { - case 'Merge from': - otherTagName = 'Merge to'; - break; - case 'Merge to': - otherTagName = 'Merge from'; - break; - // no default - } - return [ - { - name: 'mergeTarget', - type: 'input', - label: 'Other article(s):', - tooltip: 'If specifying multiple articles, separate them with pipe characters: Article one|Article two', - required: true - }, - { - type: 'checkbox', - list: [ - { - name: 'mergeTagOther', - label: 'Tag the other article with a {{' + otherTagName + '}} tag', - checked: true, - tooltip: 'Only available if a single article name is entered.' - } - ] - } - ].concat(mw.config.get('wgNamespaceNumber') === 0 ? { - name: 'mergeReason', - type: 'textarea', - label: 'Rationale for merge (will be posted on ' + - (tag === 'Merge to' ? 'the other article\'s' : 'this article\'s') + ' talk page):', - tooltip: 'Optional, but strongly recommended. Leave blank if not wanted. Only available if a single article name is entered.' - } : []); -}; - -// Tags arranged by category; will be used to generate the alphabetical list, -// but tags should be in alphabetical order within the categories -// excludeMI: true indicate a tag that *does not* work inside {{multiple issues}} -// Add new categories with discretion - the list is long enough as is! -Twinkle.tag.article.tagList = { - 'Cleanup and maintenance tags': { - 'General cleanup': [ - { - tag: 'Cleanup', description: 'requires cleanup', - subgroup: { - name: 'cleanup', - parameter: 'reason', - type: 'input', - label: 'Specific reason why cleanup is needed:', - tooltip: 'Required.', - size: 35, - required: true - } - }, // has a subgroup with text input - { - tag: 'Cleanup rewrite', - description: "needs to be rewritten entirely to comply with Wikipedia's quality standards" - }, - { - tag: 'Copy edit', - description: 'requires copy editing for grammar, style, cohesion, tone, or spelling', - subgroup: { - name: 'copyEdit', - parameter: 'for', - type: 'input', - label: '"This article may require copy editing for..."', - tooltip: 'e.g. "consistent spelling". Optional.', - size: 35 - } - } // has a subgroup with text input - ], - 'Potentially unwanted content': [ - { - tag: 'Close paraphrasing', - description: 'contains close paraphrasing of a non-free copyrighted source', - subgroup: { - name: 'closeParaphrasing', - parameter: 'source', - type: 'input', - label: 'Source:', - tooltip: 'Source that has been closely paraphrased' - } - }, - { - tag: 'Copypaste', - description: 'appears to have been copied and pasted from another location', - excludeMI: true, - subgroup: { - name: 'copypaste', - parameter: 'url', - type: 'input', - label: 'Source URL:', - tooltip: 'If known.', - size: 50 - } - }, // has a subgroup with text input - { tag: 'AI-generated', description: 'content appears to be generated by a large language model' }, - { tag: 'External links', description: 'external links may not follow content policies or guidelines' }, - { tag: 'Non-free', description: 'may contain excessive or improper use of copyrighted materials' } - ], - 'Structure, formatting, and lead section': [ - { tag: 'Cleanup reorganize', description: "needs reorganization to comply with Wikipedia's layout guidelines" }, - { tag: 'Lead missing', description: 'no lead section' }, - { tag: 'Lead rewrite', description: 'lead section needs to be rewritten to comply with guidelines' }, - { tag: 'Lead too long', description: 'lead section is too long for the length of the article' }, - { tag: 'Lead too short', description: 'lead section is too short and should be expanded to summarize key points' }, - { tag: 'Sections', description: 'needs to be divided into sections by topic' }, - { tag: 'Too many sections', description: 'too many section headers dividing up content, should be condensed' }, - { tag: 'Very long', description: 'too long to read and navigate comfortably' } - ], - 'Fiction-related cleanup': [ - { tag: 'All plot', description: 'almost entirely a plot summary' }, - { tag: 'Fiction', description: 'fails to distinguish between fact and fiction' }, - { tag: 'In-universe', description: 'subject is fictional and needs rewriting to provide a non-fictional perspective' }, - { tag: 'Long plot', description: 'plot summary is too long or excessively detailed' }, - { tag: 'No plot', description: 'needs a plot summary' } - ] - }, - 'General content issues': { - 'Importance and notability': [ - { tag: 'Notability', description: 'subject may not meet the general notability guideline', - subgroup: { - name: 'notability', - parameter: '1', - type: 'select', - list: [ - { label: "{{notability}}: article's subject may not meet the general notability guideline", value: '' }, - { label: '{{notability|Academics}}: notability guideline for academics', value: 'Academics' }, - { label: '{{notability|Astro}}: notability guideline for astronomical objects', value: 'Astro' }, - { label: '{{notability|Biographies}}: notability guideline for biographies', value: 'Biographies' }, - { label: '{{notability|Books}}: notability guideline for books', value: 'Books' }, - { label: '{{notability|Companies}}: notability guidelines for companies and organizations', value: 'Companies' }, - { label: '{{notability|Events}}: notability guideline for events', value: 'Events' }, - { label: '{{notability|Films}}: notability guideline for films', value: 'Films' }, - { label: '{{notability|Geographic}}: notability guideline for geographic features', value: 'Geographic' }, - { label: '{{notability|Lists}}: notability guideline for stand-alone lists', value: 'Lists' }, - { label: '{{notability|Music}}: notability guideline for music', value: 'Music' }, - { label: '{{notability|Neologisms}}: notability guideline for neologisms', value: 'Neologisms' }, - { label: '{{notability|Numbers}}: notability guideline for numbers', value: 'Numbers' }, - { label: '{{notability|Products}}: notability guideline for products and services', value: 'Products' }, - { label: '{{notability|Sports}}: notability guideline for sports and athletics', value: 'Sports' }, - { label: '{{notability|Television}}: notability guideline for television shows', value: 'Television' }, - { label: '{{notability|Web}}: notability guideline for web content', value: 'Web' } - ] - } - } - ], - 'Style of writing': [ - { tag: 'Advert', description: 'written like an advertisement' }, - { tag: 'Cleanup tense', description: 'does not follow guidelines on use of different tenses.' }, - { tag: 'Essay-like', description: 'written like a personal reflection, personal essay, or argumentative essay' }, - { tag: 'Fanpov', description: "written from a fan's point of view" }, - { tag: 'Inappropriate person', description: 'uses first-person or second-person inappropiately' }, - { tag: 'Like resume', description: 'written like a resume' }, - { tag: 'Manual', description: 'written like a manual or guidebook' }, - { tag: 'Cleanup-PR', description: 'reads like a press release or news article', - subgroup: { - type: 'hidden', - name: 'cleanupPR1', - parameter: '1', - value: 'article' - } - }, - { tag: 'Over-quotation', description: 'too many or too-lengthy quotations for an encyclopedic entry' }, - { tag: 'Prose', description: 'written in a list format but may read better as prose' }, - { tag: 'Technical', description: 'too technical for most readers to understand' }, - { tag: 'Tone', description: 'tone or style may not reflect the encyclopedic tone used on Wikipedia' } - ], - 'Sense (or lack thereof)': [ - { tag: 'Confusing', description: 'confusing or unclear' }, - { tag: 'Incomprehensible', description: 'very hard to understand or incomprehensible' }, - { tag: 'Unfocused', description: 'lacks focus or is about more than one topic' } - ], - 'Information and detail': [ - { tag: 'Context', description: 'insufficient context for those unfamiliar with the subject' }, - { tag: 'Excessive examples', description: 'may contain indiscriminate, excessive, or irrelevant examples' }, - { tag: 'Expert needed', description: 'needs attention from an expert on the subject', - subgroup: [ - { - name: 'expertNeeded', - parameter: '1', - type: 'input', - label: 'Name of relevant WikiProject:', - tooltip: 'Optionally, enter the name of a WikiProject which might be able to help recruit an expert. Don\'t include the "WikiProject" prefix.' - }, - { - name: 'expertNeededReason', - parameter: 'reason', - type: 'input', - label: 'Reason:', - tooltip: 'Short explanation describing the issue. Either Reason or Talk link is required.' - }, - { - name: 'expertNeededTalk', - parameter: 'talk', - type: 'input', - label: 'Talk discussion:', - tooltip: 'Name of the section of this article\'s talk page where the issue is being discussed. Do not give a link, just the name of the section. Either Reason or Talk link is required.' - } - ] - }, - { tag: 'Overly detailed', description: 'excessive amount of intricate detail' }, - { tag: 'Undue weight', description: 'lends undue weight to certain ideas, incidents, or controversies' } - ], - 'Timeliness': [ - { tag: 'Current', description: 'documents a current event', excludeMI: true }, // Works but not intended for use in MI - { tag: 'Current related', description: 'documents a topic affected by a current event', excludeMI: true }, // Works but not intended for use in MI - { tag: 'Update', description: 'needs additional up-to-date information added', - subgroup: [ - { - name: 'updatePart', - parameter: 'part', - type: 'input', - label: 'What part of the article:', - tooltip: 'Part that needs updating', - size: '45' - }, - { - name: 'updateReason', - parameter: 'reason', - type: 'input', - label: 'Reason:', - tooltip: 'Explanation why the article is out of date', - size: '55' - } - ] - } - ], - 'Neutrality, bias, and factual accuracy': [ - { tag: 'Autobiography', description: 'autobiography and may not be written neutrally' }, - { tag: 'COI', description: 'creator or major contributor may have a conflict of interest', subgroup: mw.config.get('wgNamespaceNumber') === 0 ? { - name: 'coiReason', - type: 'textarea', - label: 'Explanation for COI tag (will be posted on this article\'s talk page):', - tooltip: 'Optional, but strongly recommended. Leave blank if not wanted.' - } : [] }, - { tag: 'Disputed', description: 'questionable factual accuracy' }, - { tag: 'Hoax', description: 'may partially or completely be a hoax' }, - { tag: 'Globalize', description: 'may not represent a worldwide view of the subject', - subgroup: [ - { - type: 'hidden', - name: 'globalize1', - parameter: '1', - value: 'article' - }, { - name: 'globalizeRegion', - parameter: '2', - type: 'input', - label: 'Over-represented country or region' - } - ] - }, - { tag: 'Paid contributions', description: 'contains paid contributions, and may therefore require cleanup' }, - { tag: 'Peacock', description: 'contains wording that promotes the subject in a subjective manner without adding information' }, - { tag: 'POV', description: 'does not maintain a neutral point of view' }, - { tag: 'Recentism', description: 'slanted towards recent events' }, - { tag: 'Too few opinions', description: 'may not include all significant viewpoints' }, - { tag: 'Undisclosed paid', description: 'may have been created or edited in return for undisclosed payments' }, - { tag: 'Weasel', description: 'neutrality or verifiability is compromised by the use of weasel words' } - ], - 'Verifiability and sources': [ - { tag: 'BLP sources', description: 'BLP that needs additional sources for verification' }, - { tag: 'BLP unsourced', description: 'BLP that has no sources at all (use BLP PROD instead for new articles)' }, - { tag: 'More citations needed', description: 'needs additional references or sources for verification' }, - { tag: 'One source', description: 'relies largely or entirely on a single source' }, - { tag: 'Original research', description: 'contains original research' }, - { tag: 'Primary sources', description: 'relies too much on references to primary sources, and needs secondary sources' }, - { tag: 'Self-published', description: 'contains excessive or inappropriate references to self-published sources' }, - { tag: 'Sources exist', description: 'notable topic, sources are available that could be added to article' }, - { tag: 'Third-party', description: 'relies too heavily on sources too closely associated with the subject' }, - { tag: 'Unreferenced', description: 'does not cite any sources at all' }, - { tag: 'Unreliable sources', description: 'some references may not be reliable' } - ] - }, - 'Specific content issues': { - 'Language': [ - { tag: 'Not English', description: 'written in a language other than English and needs translation', - excludeMI: true, - subgroup: translationSubgroups.slice(0, 1).concat([{ - type: 'checkbox', - list: [ - { - name: 'translationNotify', - label: 'Notify article creator', - checked: true, - tooltip: "Places {{uw-notenglish}} on the creator's talk page." - } - ] - }]).concat(translationSubgroups.slice(1)) - }, - { tag: 'Rough translation', description: 'poor translation from another language', excludeMI: true, - subgroup: translationSubgroups - }, - { tag: 'Expand language', description: 'should be expanded with text translated from a foreign-language article', - excludeMI: true, - subgroup: [{ - type: 'hidden', - name: 'expandLangTopic', - parameter: 'topic', - value: '', - required: true // force empty topic param in output - }, { - name: 'expandLanguageLangCode', - parameter: 'langcode', - type: 'input', - label: 'Language code:', - tooltip: 'Language code of the language from which article is to be expanded from', - required: true - }, { - name: 'expandLanguageArticle', - parameter: 'otherarticle', - type: 'input', - label: 'Name of article:', - tooltip: 'Name of article to be expanded from, without the interwiki prefix' - }] - } - ], - 'Links': [ - { tag: 'Dead end', description: 'article has no links to other articles' }, - { tag: 'Orphan', description: 'linked to from no other articles' }, - { tag: 'Overlinked', description: 'too many duplicate and/or irrelevant links to other articles' }, - { tag: 'Underlinked', description: 'needs more wikilinks to other articles' } - ], - 'Referencing technique': [ - { tag: 'Citation style', description: 'unclear or inconsistent citation style' }, - { tag: 'Cleanup bare URLs', description: 'uses bare URLs for references, which are prone to link rot' }, - { tag: 'More footnotes needed', description: 'has some references, but insufficient inline citations' }, - { tag: 'No footnotes', description: 'has references, but lacks inline citations' } - ], - 'Categories': [ - { tag: 'Improve categories', description: 'needs additional or more specific categories', excludeMI: true }, - { tag: 'Uncategorized', description: 'not added to any categories', excludeMI: true } - ] - }, - 'Merging': [ - { - tag: 'History merge', - description: 'another page should be history merged into this one', - excludeMI: true, - subgroup: [ - { - name: 'histmergeOriginalPage', - parameter: 'originalpage', - type: 'input', - label: 'Other article:', - tooltip: 'Name of the page that should be merged into this one (required).', - required: true - }, - { - name: 'histmergeReason', - parameter: 'reason', - type: 'input', - label: 'Reason:', - tooltip: 'Short explanation describing the reason a history merge is needed. Should probably begin with "because" and end with a period.' - }, - { - name: 'histmergeSysopDetails', - parameter: 'details', - type: 'input', - label: 'Extra details:', - tooltip: 'For complex cases, provide extra instructions for the reviewing administrator.' - } - ] - }, - { tag: 'Merge', description: 'should be merged with another given article', excludeMI: true, - subgroup: getMergeSubgroups('Merge') }, - { tag: 'Merge from', description: 'another given article should be merged into this one', excludeMI: true, - subgroup: getMergeSubgroups('Merge from') }, - { tag: 'Merge to', description: 'should be merged into another given article', excludeMI: true, - subgroup: getMergeSubgroups('Merge to') } - ], - 'Informational': [ - { tag: 'GOCEinuse', description: 'currently undergoing a major copy edit by the Guild of Copy Editors', excludeMI: true }, - { tag: 'In use', description: 'undergoing a major edit for a short while', excludeMI: true }, - { tag: 'Under construction', description: 'in the process of an expansion or major restructuring', excludeMI: true } - ] -}; - -// Tags for REDIRECTS start here -// Not by policy, but the list roughly approximates items with >500 -// transclusions from Template:R template index -Twinkle.tag.redirectList = { - 'Grammar, punctuation, and spelling': { - 'Abbreviation': [ - { tag: 'R from acronym', description: 'redirect from an acronym (e.g. POTUS) to its expanded form' }, - { tag: 'R from initialism', description: 'redirect from an initialism (e.g. AGF) to its expanded form' }, - { tag: 'R from MathSciNet abbreviation', description: 'redirect from MathSciNet publication title abbreviation to the unabbreviated title' }, - { tag: 'R from NLM abbreviation', description: 'redirect from a NLM publication title abbreviation to the unabbreviated title' } - ], - 'Capitalisation': [ - { tag: 'R from CamelCase', description: 'redirect from a CamelCase title' }, - { tag: 'R from other capitalisation', description: 'redirect from a title with another method of capitalisation' }, - { tag: 'R from miscapitalisation', description: 'redirect from a capitalisation error' } - ], - 'Grammar & punctuation': [ - { tag: 'R from modification', description: 'redirect from a modification of the target\'s title, such as with words rearranged' }, - { tag: 'R from plural', description: 'redirect from a plural word to the singular equivalent' }, - { tag: 'R to plural', description: 'redirect from a singular noun to its plural form' } - ], - 'Parts of speech': [ - { tag: 'R from verb', description: 'redirect from an English-language verb or verb phrase' }, - { tag: 'R from adjective', description: 'redirect from an adjective (word or phrase that describes a noun)' } - ], - 'Spelling': [ - { tag: 'R from alternative spelling', description: 'redirect from a title with a different spelling' }, - { tag: 'R from alternative transliteration', description: 'redirect from an alternative English transliteration to a more common variation' }, - { tag: 'R from ASCII-only', description: 'redirect from a title in only basic ASCII to the formal title, with differences that are not diacritical marks or ligatures' }, - { tag: 'R to ASCII-only', description: 'redirect to a title in only basic ASCII from the formal title, with differences that are not diacritical marks or ligatures' }, - { tag: 'R from diacritic', description: 'redirect from a page name that has diacritical marks (accents, umlauts, etc.)' }, - { tag: 'R to diacritic', description: 'redirect to the article title with diacritical marks (accents, umlauts, etc.)' }, - { tag: 'R from misspelling', description: 'redirect from a misspelling or typographical error' } - ] - }, - 'Alternative names': { - General: [ - { - tag: 'R from alternative language', - description: 'redirect from or to a title in another language', - subgroup: [ - { - name: 'altLangFrom', - type: 'input', - label: 'From language (two-letter code):', - tooltip: 'Enter the two-letter code of the language the redirect name is in; such as en for English, de for German' - }, - { - name: 'altLangTo', - type: 'input', - label: 'To language (two-letter code):', - tooltip: 'Enter the two-letter code of the language the target name is in; such as en for English, de for German' - }, - { - name: 'altLangInfo', - type: 'div', - label: $.parseHTML('

For a list of language codes, see Wikipedia:Template messages/Redirect language codes

') - } - ] - }, - { tag: 'R from alternative name', description: 'redirect from a title that is another name, a pseudonym, a nickname, or a synonym' }, - { tag: 'R from ambiguous sort name', description: 'redirect from an ambiguous sort name to a page or list that disambiguates it' }, - { tag: 'R from former name', description: 'redirect from a former or historic name or a working title' }, - { tag: 'R from incomplete name', description: 'R from incomplete name' }, - { tag: 'R from incorrect name', description: 'redirect from an erroneus name that is unsuitable as a title' }, - { tag: 'R from less specific name', description: 'redirect from a less specific title to a more specific, less general one' }, - { tag: 'R from long name', description: 'redirect from a more complete title' }, - { tag: 'R from more specific name', description: 'redirect from a more specific title to a less specific, more general one' }, - { tag: 'R from non-neutral name', description: 'redirect from a title that contains a non-neutral, pejorative, controversial, or offensive word, phrase, or name' }, - { tag: 'R from short name', description: 'redirect from a title that is a shortened form of a person\'s full name, a book title, or other more complete title' }, - { tag: 'R from sort name', description: 'redirect from the target\'s sort name, such as beginning with their surname rather than given name' }, - { tag: 'R from synonym', description: 'redirect from a semantic synonym of the target page title' } - ], - People: [ - { tag: 'R from birth name', description: 'redirect from a person\'s birth name to a more common name' }, - { tag: 'R from given name', description: 'redirect from a person\'s given name' }, - { tag: 'R from married name', description: 'redirect from a person\'s married name to a more common name' }, - { tag: 'R from name with title', description: 'redirect from a person\'s name preceded or followed by a title to the name with no title or with the title in parentheses' }, - { tag: 'R from person', description: 'redirect from a person or persons to a related article' }, - { tag: 'R from personal name', description: 'redirect from an individual\'s personal name to an article titled with their professional or other better known moniker' }, - { tag: 'R from pseudonym', description: 'redirect from a pseudonym' }, - { tag: 'R from surname', description: 'redirect from a title that is a surname' } - ], - Technical: [ - { tag: 'R from drug trade name', description: 'redirect from (or to) the trade name of a drug to (or from) the international nonproprietary name (INN)' }, - { tag: 'R from filename', description: 'redirect from a title that is a filename of the target' }, - { tag: 'R from molecular formula', description: 'redirect from a molecular/chemical formula to its technical or trivial name' }, - - { tag: 'R from gene symbol', description: 'redirect from a Human Genome Organisation (HUGO) symbol for a gene to an article about the gene' } - ], - Organisms: [ - { tag: 'R to scientific name', description: 'redirect from the common name to the scientific name' }, - { tag: 'R from scientific name', description: 'redirect from the scientific name to the common name' }, - { tag: 'R from alternative scientific name', description: 'redirect from an alternative scientific name to the accepted scientific name' }, - { tag: 'R from scientific abbreviation', description: 'redirect from a scientific abbreviation' }, - { tag: 'R to monotypic taxon', description: 'redirect from the only lower-ranking member of a monotypic taxon to its monotypic taxon' }, - { tag: 'R from monotypic taxon', description: 'redirect from a monotypic taxon to its only lower-ranking member' }, - { tag: 'R taxon with possibilities', description: 'redirect from a title related to a living organism that potentially could be expanded into an article' } - ], - Geography: [ - { tag: 'R from name and country', description: 'redirect from the specific name to the briefer name' }, - { tag: 'R from more specific geographic name', description: 'redirect from a geographic location that includes extraneous identifiers such as the county or region of a city' } - ] - }, - 'Navigation aids': { - 'Navigation': [ - { tag: 'R to anchor', description: 'redirect from a topic that does not have its own page to an anchored part of a page on the subject' }, - { - tag: 'R avoided double redirect', - description: 'redirect from an alternative title for another redirect', - subgroup: { - name: 'doubleRedirectTarget', - type: 'input', - label: 'Redirect target name', - tooltip: 'Enter the page this redirect would target if the page wasn\'t also a redirect' - } - }, - { tag: 'R from file metadata link', description: 'redirect of a wikilink created from EXIF, XMP, or other information (i.e. the "metadata" section on some image description pages)' }, - { tag: 'R to list entry', description: 'redirect to a list which contains brief descriptions of subjects not notable enough to have separate articles' }, - - { tag: 'R mentioned in hatnote', description: 'redirect from a title that is mentioned in a hatnote at the redirect target' }, - { tag: 'R to section', description: 'similar to {{R to list entry}}, but when list is organized in sections, such as list of characters in a fictional universe' }, - { tag: 'R from shortcut', description: 'redirect from a Wikipedia shortcut' }, - { tag: 'R to subpage', description: 'redirect to a subpage' } - ], - 'Disambiguation': [ - { tag: 'R from ambiguous term', description: 'redirect from an ambiguous page name to a page that disambiguates it. This template should never appear on a page that has "(disambiguation)" in its title, use R to disambiguation page instead' }, - { tag: 'R to disambiguation page', description: 'redirect to a disambiguation page' }, - { tag: 'R from incomplete disambiguation', description: 'redirect from a page name that is too ambiguous to be the title of an article and should redirect to an appropriate disambiguation page' }, - { tag: 'R from incorrect disambiguation', description: 'redirect from a page name with incorrect disambiguation due to an error or previous editorial misconception' }, - { tag: 'R from other disambiguation', description: 'redirect from a page name with an alternative disambiguation qualifier' }, - { tag: 'R from unnecessary disambiguation', description: 'redirect from a page name that has an unneeded disambiguation qualifier' } - ], - 'Merge, duplicate & move': [ - { tag: 'R from duplicated article', description: 'redirect to a similar article in order to preserve its edit history' }, - { tag: 'R with history', description: 'redirect from a page containing substantive page history, kept to preserve content and attributions' }, - { tag: 'R from move', description: 'redirect from a page that has been moved/renamed' }, - { tag: 'R from merge', description: 'redirect from a merged page in order to preserve its edit history' } - ], - 'Namespace': [ - { tag: 'R from remote talk page', description: 'redirect from a talk page in any talk namespace to a corresponding page that is more heavily watched' }, - { tag: 'R to category namespace', description: 'redirect from a page outside the category namespace to a category page' }, - { tag: 'R to help namespace', description: 'redirect from any page inside or outside of help namespace to a page in that namespace' }, - { tag: 'R to main namespace', description: 'redirect from a page outside the main-article namespace to an article in mainspace' }, - { tag: 'R to portal namespace', description: 'redirect from any page inside or outside of portal space to a page in that namespace' }, - { tag: 'R to project namespace', description: 'redirect from any page inside or outside of project (Wikipedia: or WP:) space to any page in the project namespace' }, - { tag: 'R to user namespace', description: 'redirect from a page outside the user namespace to a user page (not to a user talk page)' } - ] - }, - 'Media': { - General: [ - { tag: 'R from album', description: 'redirect from an album to a related topic such as the recording artist or a list of albums' }, - { tag: 'R from book', description: 'redirect from a book title to a more general, relevant article' }, - { tag: 'R from film', description: 'redirect from a film title that is a subtopic of the redirect target or a title in an alternative language that has been produced in that language' }, - { tag: 'R from song', description: 'redirect from a song title to a more general, relevant article' }, - { tag: 'R from television episode', description: 'redirect from a television episode title to a related work or lists of episodes' } - ], - Fiction: [ - { tag: 'R from fictional character', description: 'redirect from a fictional character to a related fictional work or list of characters' }, - { tag: 'R from fictional element', description: 'redirect from a fictional element (such as an object or concept) to a related fictional work or list of similar elements' }, - { tag: 'R from fictional location', description: 'redirect from a fictional location or setting to a related fictional work or list of places' } - ] - }, - 'Miscellaneous': { - 'Related information': [ - { tag: 'R to article without mention', description: 'redirect to an article without any mention of the redirected word or phrase' }, - { tag: 'R to decade', description: 'redirect from a year to the decade article' }, - { tag: 'R from domain name', description: 'redirect from a domain name to an article about a website' }, - { tag: 'R from phrase', description: 'redirect from a phrase to a more general relevant article covering the topic' }, - { tag: 'R from list topic', description: 'redirect from the topic of a list to the equivalent list' }, - { tag: 'R from member', description: 'redirect from a member of a group to a related topic such as the group or organization' }, - { tag: 'R to related topic', description: 'redirect to an article about a similar topic' }, - { tag: 'R from related word', description: 'redirect from a related word' }, - { tag: 'R from school', description: 'redirect from a school article that had very little information' }, - { tag: 'R from subtopic', description: 'redirect from a title that is a subtopic of the target article' }, - { tag: 'R to subtopic', description: 'redirect to a subtopic of the redirect\'s title' }, - { tag: 'R from Unicode character', description: 'redirect from a single Unicode character to an article or Wikipedia project page that infers meaning for the symbol' }, - { tag: 'R from Unicode code', description: 'redirect from a Unicode code point to an article about the character it represents' } - ], - 'With possibilities': [ - { tag: 'R with possibilities', description: 'redirect from a specific title to a more general, less detailed article (something which can and should be expanded)' } - ], - 'ISO codes': [ - { tag: 'R from ISO 4 abbreviation', description: 'redirect from an ISO 4 publication title abbreviation to the unabbreviated title' }, - { tag: 'R from ISO 639 code', description: 'redirect from a title that is an ISO 639 language code to an article about the language' } - ], - 'Printworthiness': [ - { tag: 'R printworthy', description: 'redirect from a title that would be helpful in a printed or CD/DVD version of Wikipedia' }, - { tag: 'R unprintworthy', description: 'redirect from a title that would NOT be helpful in a printed or CD/DVD version of Wikipedia' } - ] - } -}; - -// maintenance tags for FILES start here - -Twinkle.tag.fileList = { - 'License and sourcing problem tags': [ - { label: '{{Better source requested}}: source info consists of bare image URL/generic base URL only', value: 'Better source requested' }, - { label: '{{Non-free reduce}}: non-low-resolution fair use image (or too-long audio clip, etc)', value: 'Non-free reduce' }, - { label: '{{Orphaned non-free revisions}}: fair use media with old revisions that need to be deleted', value: 'Orphaned non-free revisions' } - ], - 'Wikimedia Commons-related tags': [ - { label: '{{Copy to Commons}}: free media that should be copied to Commons', value: 'Copy to Commons' }, - { - label: '{{Do not move to Commons}}: file not suitable for moving to Commons', - value: 'Do not move to Commons', - subgroup: [ - { - type: 'input', - name: 'DoNotMoveToCommons_reason', - label: 'Reason:', - tooltip: 'Enter the reason why this image should not be moved to Commons (required). If the file is PD in the US but not in country of origin, enter "US only"', - required: true - }, - { - type: 'number', - name: 'DoNotMoveToCommons_expiry', - label: 'Expiration year:', - min: new Morebits.date().getFullYear(), - tooltip: 'If this file can be moved to Commons beginning in a certain year, you can enter it here (optional).' - } - ] - }, - { - label: '{{Keep local}}: request to keep local copy of a Commons file', - value: 'Keep local', - subgroup: { - type: 'input', - name: 'keeplocalName', - label: 'Commons image name if different:', - tooltip: 'Name of the image on Commons (if different from local name), excluding the File: prefix:' - } - }, - { - label: '{{Now Commons}}: file has been copied to Commons', - value: 'Now Commons', - subgroup: { - type: 'input', - name: 'nowcommonsName', - label: 'Commons image name if different:', - tooltip: 'Name of the image on Commons (if different from local name), excluding the File: prefix:' - } - } - ], - 'Cleanup tags': [ - { label: '{{Artifacts}}: PNG contains residual compression artifacts', value: 'Artifacts' }, - { label: '{{Bad font}}: SVG uses fonts not available on the thumbnail server', value: 'Bad font' }, - { label: '{{Bad format}}: PDF/DOC/... file should be converted to a more useful format', value: 'Bad format' }, - { label: '{{Bad GIF}}: GIF that should be PNG, JPEG, or SVG', value: 'Bad GIF' }, - { label: '{{Bad JPEG}}: JPEG that should be PNG or SVG', value: 'Bad JPEG' }, - { label: '{{Bad SVG}}: SVG containing raster graphics', value: 'Bad SVG' }, - { label: '{{Bad trace}}: auto-traced SVG requiring cleanup', value: 'Bad trace' }, - { - label: '{{Cleanup image}}: general cleanup', value: 'Cleanup image', - subgroup: { - type: 'input', - name: 'cleanupimageReason', - label: 'Reason:', - tooltip: 'Enter the reason for cleanup (required)', - required: true - } - }, - { label: '{{ClearType}}: image (not screenshot) with ClearType anti-aliasing', value: 'ClearType' }, - { label: '{{Imagewatermark}}: image contains visible or invisible watermarking', value: 'Imagewatermark' }, - { label: '{{NoCoins}}: image using coins to indicate scale', value: 'NoCoins' }, - { label: '{{Overcompressed JPEG}}: JPEG with high levels of artifacts', value: 'Overcompressed JPEG' }, - { label: '{{Opaque}}: opaque background should be transparent', value: 'Opaque' }, - { label: '{{Remove border}}: unneeded border, white space, etc.', value: 'Remove border' }, - { - label: '{{Rename media}}: file should be renamed according to the criteria at [[WP:FMV]]', - value: 'Rename media', - subgroup: [ - { - type: 'input', - name: 'renamemediaNewname', - label: 'New name:', - tooltip: 'Enter the new name for the image (optional)' - }, - { - type: 'input', - name: 'renamemediaReason', - label: 'Reason:', - tooltip: 'Enter the reason for the rename (optional)' - } - ] - }, - { label: '{{Should be PNG}}: GIF or JPEG should be lossless', value: 'Should be PNG' }, - { - label: '{{Should be SVG}}: PNG, GIF or JPEG should be vector graphics', value: 'Should be SVG', - subgroup: { - name: 'svgCategory', - type: 'select', - list: [ - { label: '{{Should be SVG|other}}', value: 'other' }, - { label: '{{Should be SVG|alphabet}}: character images, font examples, etc.', value: 'alphabet' }, - { label: '{{Should be SVG|chemical}}: chemical diagrams, etc.', value: 'chemical' }, - { label: '{{Should be SVG|circuit}}: electronic circuit diagrams, etc.', value: 'circuit' }, - { label: '{{Should be SVG|coat of arms}}: coats of arms', value: 'coat of arms' }, - { label: '{{Should be SVG|diagram}}: diagrams that do not fit any other subcategory', value: 'diagram' }, - { label: '{{Should be SVG|emblem}}: emblems, free/libre logos, insignias, etc.', value: 'emblem' }, - { label: '{{Should be SVG|fair use}}: fair-use images, fair-use logos', value: 'fair use' }, - { label: '{{Should be SVG|flag}}: flags', value: 'flag' }, - { label: '{{Should be SVG|graph}}: visual plots of data', value: 'graph' }, - { label: '{{Should be SVG|logo}}: logos', value: 'logo' }, - { label: '{{Should be SVG|map}}: maps', value: 'map' }, - { label: '{{Should be SVG|music}}: musical scales, notes, etc.', value: 'music' }, - { label: '{{Should be SVG|physical}}: "realistic" images of physical objects, people, etc.', value: 'physical' }, - { label: '{{Should be SVG|symbol}}: miscellaneous symbols, icons, etc.', value: 'symbol' } - ] - } - }, - { label: '{{Should be text}}: image should be represented as text, tables, or math markup', value: 'Should be text' } - ], - 'Image quality tags': [ - { label: '{{Image hoax}}: Image may be manipulated or constitute a hoax', value: 'Image hoax' }, - { label: '{{Image-blownout}}', value: 'Image-blownout' }, - { label: '{{Image-out-of-focus}}', value: 'Image-out-of-focus' }, - { - label: '{{Image-Poor-Quality}}', value: 'Image-Poor-Quality', - subgroup: { - type: 'input', - name: 'ImagePoorQualityReason', - label: 'Reason:', - tooltip: 'Enter the reason why this image is so bad (required)', - required: true - } - }, - { label: '{{Image-underexposure}}', value: 'Image-underexposure' }, - { - label: '{{Low quality chem}}: disputed chemical structures', value: 'Low quality chem', - subgroup: { - type: 'input', - name: 'lowQualityChemReason', - label: 'Reason:', - tooltip: 'Enter the reason why the diagram is disputed (required)', - required: true - } - } - ], - 'Replacement tags': [ - { label: '{{Obsolete}}: improved version available', value: 'Obsolete' }, - { label: '{{PNG version available}}', value: 'PNG version available' }, - { label: '{{Vector version available}}', value: 'Vector version available' } - ] -}; -Twinkle.tag.fileList['Replacement tags'].forEach(function(el) { - el.subgroup = { - type: 'input', - label: 'Replacement file:', - tooltip: 'Enter the name of the file which replaces this one (required)', - name: el.value.replace(/ /g, '_') + 'File', - required: true - }; -}); - - -Twinkle.tag.callbacks = { - article: function articleCallback(pageobj) { - - // Remove tags that become superfluous with this action - var pageText = pageobj.getPageText().replace(/\{\{\s*([Uu]serspace draft)\s*(\|(?:\{\{[^{}]*\}\}|[^{}])*)?\}\}\s*/g, ''); - var params = pageobj.getCallbackParameters(); - - /** - * Saves the page following the removal of tags if any. The last step. - * Called from removeTags() - */ - var postRemoval = function() { - if (params.tagsToRemove.length) { - // Remove empty {{multiple issues}} if found - pageText = pageText.replace(/\{\{(multiple ?issues|article ?issues|mi)\s*\|\s*\}\}\n?/im, ''); - // Remove single-element {{multiple issues}} if found - pageText = pageText.replace(/\{\{(?:multiple ?issues|article ?issues|mi)\s*\|\s*(\{\{[^}]+\}\})\s*\}\}/im, '$1'); - } - - // Build edit summary - var makeSentence = function(array) { - if (array.length < 3) { - return array.join(' and '); - } - var last = array.pop(); - return array.join(', ') + ', and ' + last; - }; - var makeTemplateLink = function(tag) { - var text = '{{[['; - // if it is a custom tag with a parameter - if (tag.indexOf('|') !== -1) { - tag = tag.slice(0, tag.indexOf('|')); - } - text += tag.indexOf(':') !== -1 ? tag : 'Template:' + tag + '|' + tag; - return text + ']]}}'; - }; - - var summaryText; - var addedTags = params.tags.map(makeTemplateLink); - var removedTags = params.tagsToRemove.map(makeTemplateLink); - if (addedTags.length) { - summaryText = 'Added ' + makeSentence(addedTags); - summaryText += removedTags.length ? '; and removed ' + makeSentence(removedTags) : ''; - } else { - summaryText = 'Removed ' + makeSentence(removedTags); - } - summaryText += ' tag' + (addedTags.length + removedTags.length > 1 ? 's' : ''); - if (params.reason) { - summaryText += ': ' + params.reason; - } - - // avoid truncated summaries - if (summaryText.length > 499) { - summaryText = summaryText.replace(/\[\[[^|]+\|([^\]]+)\]\]/g, '$1'); - } - - pageobj.setPageText(pageText); - pageobj.setEditSummary(summaryText); - if ((mw.config.get('wgNamespaceNumber') === 0 && Twinkle.getPref('watchTaggedVenues').indexOf('articles') !== -1) || (mw.config.get('wgNamespaceNumber') === 118 && Twinkle.getPref('watchTaggedVenues').indexOf('drafts') !== -1)) { - pageobj.setWatchlist(Twinkle.getPref('watchTaggedPages')); - } - pageobj.setMinorEdit(Twinkle.getPref('markTaggedPagesAsMinor')); - pageobj.setCreateOption('nocreate'); - pageobj.save(function() { - // COI: Start the discussion on the talk page (mainspace only) - if (params.coiReason) { - var coiTalkPage = new Morebits.wiki.page('Talk:' + Morebits.pageNameNorm, 'Starting discussion on talk page'); - coiTalkPage.setNewSectionText(params.coiReason + ' ~~~~'); - coiTalkPage.setNewSectionTitle('COI tag (' + new Morebits.date(pageobj.getLoadTime()).format('MMMM Y', 'utc') + ')'); - coiTalkPage.setChangeTags(Twinkle.changeTags); - coiTalkPage.setCreateOption('recreate'); - coiTalkPage.newSection(); - } - - // Special functions for merge tags - // Post a rationale on the talk page (mainspace only) - if (params.mergeReason) { - var mergeTalkPage = new Morebits.wiki.page('Talk:' + params.discussArticle, 'Posting rationale on talk page'); - mergeTalkPage.setNewSectionText(params.mergeReason.trim() + ' ~~~~'); - mergeTalkPage.setNewSectionTitle(params.talkDiscussionTitleLinked); - mergeTalkPage.setChangeTags(Twinkle.changeTags); - mergeTalkPage.setWatchlist(Twinkle.getPref('watchMergeDiscussions')); - mergeTalkPage.setCreateOption('recreate'); - mergeTalkPage.newSection(); - } - // Tag the target page (if requested) - if (params.mergeTagOther) { - var otherTagName = 'Merge'; - if (params.mergeTag === 'Merge from') { - otherTagName = 'Merge to'; - } else if (params.mergeTag === 'Merge to') { - otherTagName = 'Merge from'; - } - var newParams = { - tags: [otherTagName], - tagsToRemove: [], - tagsToRemain: [], - mergeTarget: Morebits.pageNameNorm, - discussArticle: params.discussArticle, - talkDiscussionTitle: params.talkDiscussionTitle, - talkDiscussionTitleLinked: params.talkDiscussionTitleLinked - }; - var otherpage = new Morebits.wiki.page(params.mergeTarget, 'Tagging other page (' + - params.mergeTarget + ')'); - otherpage.setChangeTags(Twinkle.changeTags); - otherpage.setCallbackParameters(newParams); - otherpage.load(Twinkle.tag.callbacks.article); - } - - // Special functions for {{not English}} and {{rough translation}} - // Post at WP:PNT (mainspace only) - if (params.translationPostAtPNT) { - var pntPage = new Morebits.wiki.page('Wikipedia:Pages needing translation into English', - 'Listing article at Wikipedia:Pages needing translation into English'); - pntPage.setFollowRedirect(true); - pntPage.load(function friendlytagCallbacksTranslationListPage(pageobj) { - var old_text = pageobj.getPageText(); - - var lang = params.translationLanguage; - var reason = params.translationComments; - - var templateText = '{{subst:needtrans|pg=' + Morebits.pageNameNorm + '|Language=' + - (lang || 'uncertain') + '|Comments=' + reason.trim() + '}} ~~~~'; - - var text, summary; - if (params.tags.indexOf('Rough translation') !== -1) { - text = old_text + '\n\n' + templateText; - summary = 'Translation cleanup requested on '; - } else { - text = old_text.replace(/\n+(==\s?Translated pages that could still use some cleanup\s?==)/, - '\n\n' + templateText + '\n\n$1'); - summary = 'Translation' + (lang ? ' from ' + lang : '') + ' requested on '; - } - - if (text === old_text) { - pageobj.getStatusElement().error('failed to find target spot for the discussion'); - return; - } - pageobj.setPageText(text); - pageobj.setEditSummary(summary + ' [[:' + Morebits.pageNameNorm + ']]'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setCreateOption('recreate'); - pageobj.save(); - }); - } - // Notify the user ({{Not English}} only) - if (params.translationNotify) { - new Morebits.wiki.page(Morebits.pageNameNorm).lookupCreation(function(innerPageobj) { - var initialContrib = innerPageobj.getCreator(); - - // Disallow warning yourself - if (initialContrib === mw.config.get('wgUserName')) { - innerPageobj.getStatusElement().warn('You (' + initialContrib + ') created this page; skipping user notification'); - return; - } - - var userTalkPage = new Morebits.wiki.page('User talk:' + initialContrib, - 'Notifying initial contributor (' + initialContrib + ')'); - userTalkPage.setNewSectionTitle('Your article [[' + Morebits.pageNameNorm + ']]'); - userTalkPage.setNewSectionText('{{subst:uw-notenglish|1=' + Morebits.pageNameNorm + - (params.translationPostAtPNT ? '' : '|nopnt=yes') + '}} ~~~~'); - userTalkPage.setEditSummary('Notice: Please use English when contributing to the English Wikipedia.'); - userTalkPage.setChangeTags(Twinkle.changeTags); - userTalkPage.setCreateOption('recreate'); - userTalkPage.setFollowRedirect(true, false); - userTalkPage.newSection(); - }); - } - }); - - if (params.patrol) { - pageobj.triage(); - } - }; - - /** - * Removes the existing tags that were deselected (if any) - * Calls postRemoval() when done - */ - var removeTags = function removeTags() { - - if (params.tagsToRemove.length === 0) { - postRemoval(); - return; - } - - Morebits.status.info('Info', 'Removing deselected tags that were already present'); - - var getRedirectsFor = []; - - // Remove the tags from the page text, if found in its proper name, - // otherwise moves it to `getRedirectsFor` array earmarking it for - // later removal - params.tagsToRemove.forEach(function removeTag(tag) { - var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]+)?\\}\\}\\n?'); - - if (tag_re.test(pageText)) { - pageText = pageText.replace(tag_re, ''); - } else { - getRedirectsFor.push('Template:' + tag); - } - }); - - if (!getRedirectsFor.length) { - postRemoval(); - return; - } - - // Remove tags which appear in page text as redirects - var api = new Morebits.wiki.api('Getting template redirects', { - action: 'query', - prop: 'linkshere', - titles: getRedirectsFor.join('|'), - redirects: 1, // follow redirect if the class name turns out to be a redirect page - lhnamespace: '10', // template namespace only - lhshow: 'redirect', - lhlimit: 'max', // 500 is max for normal users, 5000 for bots and sysops - format: 'json' - }, function removeRedirectTag(apiobj) { - var pages = apiobj.getResponse().query.pages.filter(function(p) { - return !p.missing && !!p.linkshere; - }); - pages.forEach(function(page) { - var removed = false; - page.linkshere.forEach(function(el) { - var tag = el.title.slice(9); - var tag_re = new RegExp('\\{\\{' + Morebits.pageNameRegex(tag) + '\\s*(\\|[^}]*)?\\}\\}\\n?'); - if (tag_re.test(pageText)) { - pageText = pageText.replace(tag_re, ''); - removed = true; - return false; // break out of $.each - } - }); - if (!removed) { - Morebits.status.warn('Info', 'Failed to find {{' + - page.title.slice(9) + '}} on the page... excluding'); - } - - }); - - postRemoval(); - - }); - api.post(); - - }; - - if (!params.tags.length) { - removeTags(); - return; - } - - var tagRe, tagText = '', tags = [], groupableTags = [], groupableExistingTags = []; - // Executes first: addition of selected tags - - /** - * Updates `tagText` with the syntax of `tagName` template with its parameters - * @param {number} tagIndex - * @param {string} tagName - */ - var addTag = function articleAddTag(tagIndex, tagName) { - var currentTag = ''; - if (tagName === 'Uncategorized' || tagName === 'Improve categories') { - pageText += '\n\n{{' + tagName + '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}'; - } else { - currentTag += '{{' + tagName; - // fill in other parameters, based on the tag - - var subgroupObj = Twinkle.tag.article.flatObject[tagName] && - Twinkle.tag.article.flatObject[tagName].subgroup; - if (subgroupObj) { - var subgroups = Array.isArray(subgroupObj) ? subgroupObj : [ subgroupObj ]; - subgroups.forEach(function(gr) { - if (gr.parameter && (params[gr.name] || gr.required)) { - currentTag += '|' + gr.parameter + '=' + (params[gr.name] || ''); - } - }); - } - - switch (tagName) { - case 'Not English': - case 'Rough translation': - if (params.translationPostAtPNT) { - currentTag += '|listed=yes'; - } - break; - case 'Merge': - case 'Merge to': - case 'Merge from': - params.mergeTag = tagName; - // normalize the merge target for now and later - params.mergeTarget = Morebits.string.toUpperCaseFirstChar(params.mergeTarget.replace(/_/g, ' ')); - - currentTag += '|' + params.mergeTarget; - - // link to the correct section on the talk page, for article space only - if (mw.config.get('wgNamespaceNumber') === 0 && (params.mergeReason || params.discussArticle)) { - if (!params.discussArticle) { - // discussArticle is the article whose talk page will contain the discussion - params.discussArticle = tagName === 'Merge to' ? params.mergeTarget : mw.config.get('wgTitle'); - // nonDiscussArticle is the article which won't have the discussion - params.nonDiscussArticle = tagName === 'Merge to' ? mw.config.get('wgTitle') : params.mergeTarget; - var direction = '[[' + params.nonDiscussArticle + ']]' + (params.mergeTag === 'Merge' ? ' with ' : ' into ') + '[[' + params.discussArticle + ']]'; - params.talkDiscussionTitleLinked = 'Proposed merge of ' + direction; - params.talkDiscussionTitle = params.talkDiscussionTitleLinked.replace(/\[\[(.*?)\]\]/g, '$1'); - } - var titleWithSectionRemoved = params.discussArticle.replace(/^([^#]*)#.*$/, '$1'); // If article name is Test#Section, delete #Section - currentTag += '|discuss=Talk:' + titleWithSectionRemoved + '#' + params.talkDiscussionTitle; - } - break; - default: - break; - } - - currentTag += '|date={{subst:CURRENTMONTHNAME}} {{subst:CURRENTYEAR}}}}\n'; - tagText += currentTag; - } - }; - - /** - * Adds the tags which go outside {{multiple issues}}, either because - * these tags aren't supported in {{multiple issues}} or because - * {{multiple issues}} is not being added to the page at all - */ - var addUngroupedTags = function() { - $.each(tags, addTag); - - // Insert tag after short description or any hatnotes, - // as well as deletion/protection-related templates - var wikipage = new Morebits.wikitext.page(pageText); - var templatesAfter = Twinkle.hatnoteRegex + - // Protection templates - 'pp|pp-.*?|' + - // CSD - 'db|delete|db-.*?|speedy deletion-.*?|' + - // PROD - '(?:proposed deletion|prod blp)\\/dated(?:\\s*\\|(?:concern|user|timestamp|help).*)+|' + - // not a hatnote, but sometimes under a CSD or AfD - 'salt|proposed deletion endorsed'; - // AfD is special, as the tag includes html comments before and after the actual template - // trailing whitespace/newline needed since this subst's a newline - var afdRegex = '(?:', - templateName: 'anonblock', - sig: '~~~~' - }, - 'blocked proxy': { - expiry: '1 year', - forAnonOnly: true, - nocreate: true, - nonstandard: true, - hardblock: true, - reason: '{{blocked proxy}}', - sig: null - }, - 'CheckUser block': { - expiry: '1 week', - forAnonOnly: true, - nocreate: true, - nonstandard: true, - reason: '{{CheckUser block}}', - sig: '~~~~' - }, - 'checkuserblock-account': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - nonstandard: true, - reason: '{{checkuserblock-account}}', - sig: '~~~~' - }, - 'checkuserblock-wide': { - forAnonOnly: true, - nocreate: true, - nonstandard: true, - reason: '{{checkuserblock-wide}}', - sig: '~~~~' - }, - 'colocationwebhost': { - expiry: '1 year', - forAnonOnly: true, - nonstandard: true, - reason: '{{colocationwebhost}}', - sig: null - }, - 'oversightblock': { - autoblock: true, - expiry: 'infinity', - nocreate: true, - nonstandard: true, - reason: '{{OversightBlock}}', - sig: '~~~~' - }, - 'school block': { - forAnonOnly: true, - nocreate: true, - nonstandard: true, - reason: '{{school block}}', - sig: '~~~~' - }, - 'spamblacklistblock': { - forAnonOnly: true, - expiry: '1 month', - disabletalk: true, - nocreate: true, - reason: '{{spamblacklistblock}} ' - }, - 'rangeblock': { - reason: '{{rangeblock}}', - nocreate: true, - nonstandard: true, - forAnonOnly: true, - sig: '~~~~' - }, - 'tor': { - expiry: '1 year', - forAnonOnly: true, - nonstandard: true, - reason: '{{Tor}}', - sig: null - }, - 'webhostblock': { - expiry: '1 year', - forAnonOnly: true, - nonstandard: true, - reason: '{{webhostblock}}', - sig: null - }, - // uw-prefixed - 'uw-3block': { - autoblock: true, - expiry: '24 hours', - nocreate: true, - pageParam: true, - reason: 'Violation of the [[WP:Three-revert rule|three-revert rule]]', - summary: 'You have been blocked from editing for violation of the [[WP:3RR|three-revert rule]]' - }, - 'uw-ablock': { - autoblock: true, - expiry: '31 hours', - forAnonOnly: true, - nocreate: true, - pageParam: true, - reasonParam: true, - summary: 'Your IP address has been blocked from editing', - suppressArticleInSummary: true - }, - 'uw-adblock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: 'Using Wikipedia for [[WP:Spam|spam]] or [[WP:NOTADVERTISING|advertising]] purposes', - summary: 'You have been blocked from editing for [[WP:SOAP|advertising or self-promotion]]' - }, - 'uw-aeblock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]', - reasonParam: true, - summary: 'You have been blocked from editing for violating an [[WP:Arbitration|arbitration decision]]' - }, - 'uw-bioblock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: 'Violations of the [[WP:Biographies of living persons|biographies of living persons]] policy', - summary: 'You have been blocked from editing for violations of Wikipedia\'s [[WP:BLP|biographies of living persons policy]]' - }, - 'uw-block': { - autoblock: true, - expiry: '24 hours', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reasonParam: true, - summary: 'You have been blocked from editing', - suppressArticleInSummary: true - }, - 'uw-blockindef': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reasonParam: true, - summary: 'You have been indefinitely blocked from editing', - suppressArticleInSummary: true - }, - 'uw-blocknotalk': { - disabletalk: true, - pageParam: true, - reasonParam: true, - summary: 'You have been blocked from editing and your user talk page access has been disabled', - suppressArticleInSummary: true - }, - 'uw-botblock': { - forRegisteredOnly: true, - pageParam: true, - reason: 'Running a [[WP:BOT|bot script]] without [[WP:BRFA|approval]]', - summary: 'You have been blocked from editing because it appears you are running a [[WP:BOT|bot script]] without [[WP:BRFA|approval]]' - }, - 'uw-botublock': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-botublock}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] indicates this is a [[WP:BOT|bot]] account, which is currently not approved' - }, - 'uw-botuhblock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: '{{uw-botuhblock}} ', - summary: 'You have been indefinitely blocked from editing because your username is a blatant violation of the [[WP:U|username policy]]' - }, - 'uw-causeblock': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-causeblock}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] gives the impression that the account represents a group, organization or website' - }, - 'uw-compblock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: 'Compromised account', - summary: 'You have been indefinitely blocked from editing because it is believed that your [[WP:SECURE|account has been compromised]]' - }, - 'uw-copyrightblock': { - autoblock: true, - expiry: 'infinity', - nocreate: true, - pageParam: true, - reason: '[[WP:Copyright violations|Copyright violations]]', - summary: 'You have been blocked from editing for continued [[WP:COPYVIO|copyright infringement]]' - }, - 'uw-dblock': { - autoblock: true, - nocreate: true, - reason: 'Persistent removal of content', - pageParam: true, - summary: 'You have been blocked from editing for continued [[WP:VAND|removal of material]]' - }, - 'uw-disruptblock': { - autoblock: true, - nocreate: true, - reason: '[[WP:Disruptive editing|Disruptive editing]]', - summary: 'You have been blocked from editing for [[WP:DE|disruptive editing]]' - }, - 'uw-efblock': { - autoblock: true, - nocreate: true, - reason: 'Repeatedly triggering the [[WP:Edit filter|Edit filter]]', - summary: 'You have been blocked from editing for disruptive edits that repeatedly triggered the [[WP:EF|edit filter]]' - }, - 'uw-ewblock': { - autoblock: true, - expiry: '24 hours', - nocreate: true, - pageParam: true, - reason: '[[WP:Edit warring|Edit warring]]', - summary: 'You have been blocked from editing to prevent further [[WP:DE|disruption]] caused by your engagement in an [[WP:EW|edit war]]' - }, - 'uw-hblock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: '[[WP:No personal attacks|Personal attacks]] or [[WP:Harassment|harassment]]', - summary: 'You have been blocked from editing for attempting to [[WP:HARASS|harass]] other users' - }, - 'uw-ipevadeblock': { - forAnonOnly: true, - nocreate: true, - reason: '[[WP:Blocking policy#Evasion of blocks|Block evasion]]', - summary: 'Your IP address has been blocked from editing because it has been used to [[WP:EVADE|evade a previous block]]' - }, - 'uw-lblock': { - autoblock: true, - expiry: 'infinity', - nocreate: true, - reason: 'Making [[WP:No legal threats|legal threats]]', - summary: 'You have been blocked from editing for making [[WP:NLT|legal threats or taking legal action]]' - }, - 'uw-nothereblock': { - autoblock: true, - expiry: 'infinity', - nocreate: true, - reason: 'Clearly [[WP:NOTHERE|not here to build an encyclopedia]]', - forRegisteredOnly: true, - summary: 'You have been indefinitely blocked from editing because it appears that you are not here to [[WP:NOTHERE|build an encyclopedia]]' - }, - 'uw-npblock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: 'Creating [[WP:Patent nonsense|patent nonsense]] or other inappropriate pages', - summary: 'You have been blocked from editing for creating [[WP:PN|nonsense pages]]' - }, - 'uw-pablock': { - autoblock: true, - expiry: '31 hours', - nocreate: true, - reason: '[[WP:No personal attacks|Personal attacks]] or [[WP:Harassment|harassment]]', - summary: 'You have been blocked from editing for making [[WP:NPA|personal attacks]] toward other users' - }, - 'uw-sblock': { - autoblock: true, - nocreate: true, - reason: 'Using Wikipedia for [[WP:SPAM|spam]] purposes', - summary: 'You have been blocked from editing for using Wikipedia for [[WP:SPAM|spam]] purposes' - }, - 'uw-soablock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reason: '[[WP:Spam|Spam]] / [[WP:NOTADVERTISING|advertising]]-only account', - summary: 'You have been indefinitely blocked from editing because your account is being used only for [[WP:SPAM|spam, advertising, or promotion]]' - }, - 'uw-socialmediablock': { - autoblock: true, - nocreate: true, - pageParam: true, - reason: 'Using Wikipedia as a [[WP:NOTMYSPACE|blog, web host, social networking site or forum]]', - summary: 'You have been blocked from editing for using user and/or article pages as a [[WP:NOTMYSPACE|blog, web host, social networking site or forum]]' - }, - 'uw-sockblock': { - autoblock: true, - forRegisteredOnly: true, - nocreate: true, - reason: 'Abusing [[WP:Sock puppetry|multiple accounts]]', - summary: 'You have been blocked from editing for abusing [[WP:SOCK|multiple accounts]]' - }, - 'uw-softerblock': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-softerblock}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] gives the impression that the account represents a group, organization or website' - }, - 'uw-spamublock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: '{{uw-spamublock}} ', - summary: 'You have been indefinitely blocked from editing because your account is being used only for [[WP:SPAM|spam or advertising]] and your username is a violation of the [[WP:U|username policy]]' - }, - 'uw-spoablock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: '[[WP:SOCK|Sock puppetry]]', - summary: 'This account has been blocked as a [[WP:SOCK|sock puppet]] created to violate Wikipedia policy' - }, - 'uw-talkrevoked': { - disabletalk: true, - reason: 'Revoking talk page access: inappropriate use of user talk page while blocked', - prependReason: true, - summary: 'Your user talk page access has been disabled', - useInitialOptions: true - }, - 'uw-ublock': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-ublock}} ', - reasonParam: true, - summary: 'You have been indefinitely blocked from editing because your username is a violation of the [[WP:U|username policy]]' - }, - 'uw-ublock-double': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-ublock-double}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] is too similar to the username of another Wikipedia user' - }, - 'uw-ucblock': { - autoblock: true, - expiry: '31 hours', - nocreate: true, - pageParam: true, - reason: 'Persistent addition of [[WP:INTREF|unsourced content]]', - summary: 'You have been blocked from editing for persistent addition of [[WP:INTREF|unsourced content]]' - }, - 'uw-uhblock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: '{{uw-uhblock}} ', - reasonParam: true, - summary: 'You have been indefinitely blocked from editing because your username is a blatant violation of the [[WP:U|username policy]]' - }, - 'uw-ublock-wellknown': { - expiry: 'infinity', - forRegisteredOnly: true, - reason: '{{uw-ublock-wellknown}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] matches the name of a well-known living individual' - }, - 'uw-uhblock-double': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - reason: '{{uw-uhblock-double}} ', - summary: 'You have been indefinitely blocked from editing because your [[WP:U|username]] appears to impersonate another established Wikipedia user' - }, - 'uw-upeblock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reason: '[[WP:PAID|Undisclosed paid editing]] in violation of the WMF [[WP:TOU|Terms of Use]]', - summary: 'You have been indefinitely blocked from editing because your account is being used in violation of [[WP:PAID|Wikipedia policy on undisclosed paid advocacy]]' - }, - 'uw-vaublock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reason: '{{uw-vaublock}} ', - summary: 'You have been indefinitely blocked from editing because your account is being [[WP:DISRUPTONLY|used only for vandalism]] and your username is a blatant violation of the [[WP:U|username policy]]' - }, - 'uw-vblock': { - autoblock: true, - expiry: '31 hours', - nocreate: true, - pageParam: true, - reason: '[[WP:Vandalism|Vandalism]]', - summary: 'You have been blocked from editing to prevent further [[WP:VAND|vandalism]]' - }, - 'uw-voablock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: true, - reason: '[[WP:DISRUPTONLY|Vandalism-only account]]', - summary: 'You have been indefinitely blocked from editing because your account is being [[WP:DISRUPTONLY|used only for vandalism]]' - }, - 'zombie proxy': { - expiry: '1 month', - forAnonOnly: true, - nocreate: true, - nonstandard: true, - reason: '{{zombie proxy}}', - sig: null - }, - - // Begin partial block templates, accessed in Twinkle.block.blockGroupsPartial - 'uw-acpblock': { - autoblock: true, - expiry: '48 hours', - nocreate: true, - pageParam: false, - reasonParam: true, - reason: 'Misusing [[WP:Sock puppetry|multiple accounts]]', - summary: 'You have been [[WP:PB|blocked from creating accounts]] for misusing [[WP:SOCK|multiple accounts]]' - }, - 'uw-acpblockindef': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: true, - pageParam: false, - reasonParam: true, - reason: 'Misusing [[WP:Sock puppetry|multiple accounts]]', - summary: 'You have been indefinitely [[WP:PB|blocked from creating accounts]] for misusing [[WP:SOCK|multiple accounts]]' - }, - 'uw-aepblock': { - autoblock: true, - nocreate: false, - pageParam: false, - reason: '[[WP:Arbitration enforcement|Arbitration enforcement]]', - reasonParam: true, - summary: 'You have been [[WP:PB|partially blocked]] from editing for violating an [[WP:Arbitration|arbitration decision]]' - }, - 'uw-epblock': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: false, - noemail: true, - pageParam: false, - reasonParam: true, - reason: 'Email [[WP:Harassment|harassment]]', - summary: 'You have been [[WP:PB|blocked from emailing]] other editors for [[WP:Harassment|harassment]]' - }, - 'uw-ewpblock': { - autoblock: true, - expiry: '24 hours', - nocreate: false, - pageParam: false, - reasonParam: true, - reason: '[[WP:Edit warring|Edit warring]]', - summary: 'You have been [[WP:PB|partially blocked]] from editing certain areas of the encyclopedia to prevent further [[WP:DE|disruption]] due to [[WP:EW|edit warring]]' - }, - 'uw-pblock': { - autoblock: true, - expiry: '24 hours', - nocreate: false, - pageParam: false, - reasonParam: true, - summary: 'You have been [[WP:PB|partially blocked]] from certain areas of the encyclopedia' - }, - 'uw-pblockindef': { - autoblock: true, - expiry: 'infinity', - forRegisteredOnly: true, - nocreate: false, - pageParam: false, - reasonParam: true, - summary: 'You have been indefinitely [[WP:PB|partially blocked]] from certain areas of the encyclopedia' - } -}; - -Twinkle.block.transformBlockPresets = function twinkleblockTransformBlockPresets() { - // supply sensible defaults - $.each(Twinkle.block.blockPresetsInfo, function(preset, settings) { - settings.summary = settings.summary || settings.reason; - settings.sig = settings.sig !== undefined ? settings.sig : 'yes'; - settings.indefinite = settings.indefinite || Morebits.string.isInfinity(settings.expiry); - - if (!Twinkle.block.isRegistered && settings.indefinite) { - settings.expiry = '31 hours'; - } else { - settings.expiry = settings.expiry || '31 hours'; - } - - Twinkle.block.blockPresetsInfo[preset] = settings; - }); -}; - -// These are the groups of presets and defines the order in which they appear. For each list item: -// label: -// value: -Twinkle.block.blockGroups = [ - { - label: 'Common block reasons', - list: [ - { label: 'anonblock', value: 'anonblock' }, - { label: 'anonblock - likely a school', value: 'anonblock - school' }, - { label: 'school block', value: 'school block' }, - { label: 'Generic block (custom reason)', value: 'uw-block' }, // ends up being default for registered users - { label: 'Generic block (custom reason) - IP', value: 'uw-ablock', selected: true }, // set only when blocking IP - { label: 'Generic block (custom reason) - indefinite', value: 'uw-blockindef' }, - { label: 'Disruptive editing', value: 'uw-disruptblock' }, - { label: 'Inappropriate use of user talk page while blocked', value: 'uw-talkrevoked' }, - { label: 'Not here to build an encyclopedia', value: 'uw-nothereblock' }, - { label: 'Unsourced content', value: 'uw-ucblock' }, - { label: 'Vandalism', value: 'uw-vblock' }, - { label: 'Vandalism-only account', value: 'uw-voablock' } - ] - }, - { - label: 'Extended reasons', - list: [ - { label: 'Advertising', value: 'uw-adblock' }, - { label: 'Arbitration enforcement', value: 'uw-aeblock' }, - { label: 'Block evasion - IP', value: 'uw-ipevadeblock' }, - { label: 'BLP violations', value: 'uw-bioblock' }, - { label: 'Copyright violations', value: 'uw-copyrightblock' }, - { label: 'Creating nonsense pages', value: 'uw-npblock' }, - { label: 'Edit filter-related', value: 'uw-efblock' }, - { label: 'Edit warring', value: 'uw-ewblock' }, - { label: 'Generic block with talk page access revoked', value: 'uw-blocknotalk' }, - { label: 'Harassment', value: 'uw-hblock' }, - { label: 'Legal threats', value: 'uw-lblock' }, - { label: 'Personal attacks or harassment', value: 'uw-pablock' }, - { label: 'Possible compromised account', value: 'uw-compblock' }, - { label: 'Removal of content', value: 'uw-dblock' }, - { label: 'Sock puppetry (master)', value: 'uw-sockblock' }, - { label: 'Sock puppetry (puppet)', value: 'uw-spoablock' }, - { label: 'Social networking', value: 'uw-socialmediablock' }, - { label: 'Spam', value: 'uw-sblock' }, - { label: 'Spam/advertising-only account', value: 'uw-soablock' }, - { label: 'Unapproved bot', value: 'uw-botblock' }, - { label: 'Undisclosed paid editing', value: 'uw-upeblock' }, - { label: 'Violating the three-revert rule', value: 'uw-3block' } - ] - }, - { - label: 'Username violations', - list: [ - { label: 'Bot username, soft block', value: 'uw-botublock' }, - { label: 'Bot username, hard block', value: 'uw-botuhblock' }, - { label: 'Promotional username, hard block', value: 'uw-spamublock' }, - { label: 'Promotional username, soft block', value: 'uw-softerblock' }, - { label: 'Similar username, soft block', value: 'uw-ublock-double' }, - { label: 'Username violation, soft block', value: 'uw-ublock' }, - { label: 'Username violation, hard block', value: 'uw-uhblock' }, - { label: 'Username impersonation, hard block', value: 'uw-uhblock-double' }, - { label: 'Username represents a well-known person, soft block', value: 'uw-ublock-wellknown' }, - { label: 'Username represents a non-profit, soft block', value: 'uw-causeblock' }, - { label: 'Username violation, vandalism-only account', value: 'uw-vaublock' } - ] - }, - { - label: 'Templated reasons', - list: [ - { label: 'blocked proxy', value: 'blocked proxy' }, - { label: 'CheckUser block', value: 'CheckUser block' }, - { label: 'checkuserblock-account', value: 'checkuserblock-account' }, - { label: 'checkuserblock-wide', value: 'checkuserblock-wide' }, - { label: 'colocationwebhost', value: 'colocationwebhost' }, - { label: 'oversightblock', value: 'oversightblock' }, - { label: 'rangeblock', value: 'rangeblock' }, // Only for IP ranges, selected for non-/64 ranges in filtered_block_groups - { label: 'spamblacklistblock', value: 'spamblacklistblock' }, - { label: 'tor', value: 'tor' }, - { label: 'webhostblock', value: 'webhostblock' }, - { label: 'zombie proxy', value: 'zombie proxy' } - ] - } -]; - -Twinkle.block.blockGroupsPartial = [ - { - label: 'Common partial block reasons', - list: [ - { label: 'Generic partial block (custom reason)', value: 'uw-pblock', selected: true }, - { label: 'Generic partial block (custom reason) - indefinite', value: 'uw-pblockindef' }, - { label: 'Edit warring', value: 'uw-ewpblock' } - ] - }, - { - label: 'Extended partial block reasons', - list: [ - { label: 'Arbitration enforcement', value: 'uw-aepblock' }, - { label: 'Email harassment', value: 'uw-epblock' }, - { label: 'Misusing multiple accounts', value: 'uw-acpblock' }, - { label: 'Misusing multiple accounts - indefinite', value: 'uw-acpblockindef' } - ] - } -]; - - -Twinkle.block.callback.filtered_block_groups = function twinkleblockCallbackFilteredBlockGroups(group, show_template) { - return $.map(group, function(blockGroup) { - var list = $.map(blockGroup.list, function(blockPreset) { - switch (blockPreset.value) { - case 'uw-talkrevoked': - if (blockedUserName !== relevantUserName) { - return; - } - break; - case 'rangeblock': - if (!Morebits.ip.isRange(relevantUserName)) { - return; - } - blockPreset.selected = !Morebits.ip.get64(relevantUserName); - break; - case 'CheckUser block': - case 'checkuserblock-account': - case 'checkuserblock-wide': - if (!Morebits.userIsInGroup('checkuser')) { - return; - } - break; - case 'oversightblock': - if (!Morebits.userIsInGroup('suppress')) { - return; - } - break; - default: - break; - } - - var blockSettings = Twinkle.block.blockPresetsInfo[blockPreset.value]; - - var registrationRestrict; - if (blockSettings.forRegisteredOnly) { - registrationRestrict = Twinkle.block.isRegistered; - } else if (blockSettings.forAnonOnly) { - registrationRestrict = !Twinkle.block.isRegistered; - } else { - registrationRestrict = true; - } - - if (!(blockSettings.templateName && show_template) && registrationRestrict) { - var templateName = blockSettings.templateName || blockPreset.value; - return { - label: (show_template ? '{{' + templateName + '}}: ' : '') + blockPreset.label, - value: blockPreset.value, - data: [{ - name: 'template-name', - value: templateName - }], - selected: !!blockPreset.selected, - disabled: !!blockPreset.disabled - }; - } - }); - if (list.length) { - return { - label: blockGroup.label, - list: list - }; - } - }); -}; - -Twinkle.block.callback.change_preset = function twinkleblockCallbackChangePreset(e) { - var form = e.target.form, key = form.preset.value; - if (!key) { - return; - } - - Twinkle.block.callback.update_form(e, Twinkle.block.blockPresetsInfo[key]); - if (form.template) { - form.template.value = Twinkle.block.blockPresetsInfo[key].templateName || key; - Twinkle.block.callback.change_template(e); - } else { - Morebits.quickForm.setElementVisibility(form.dstopic.parentNode, key === 'uw-aeblock' || key === 'uw-aepblock'); - } -}; - -Twinkle.block.callback.change_expiry = function twinkleblockCallbackChangeExpiry(e) { - var expiry = e.target.form.expiry; - if (e.target.value === 'custom') { - Morebits.quickForm.setElementVisibility(expiry.parentNode, true); - } else { - Morebits.quickForm.setElementVisibility(expiry.parentNode, false); - expiry.value = e.target.value; - } -}; - -Twinkle.block.seeAlsos = []; -Twinkle.block.callback.toggle_see_alsos = function twinkleblockCallbackToggleSeeAlso() { - var reason = this.form.reason.value.replace( - new RegExp('( )?'), '' - ); - - Twinkle.block.seeAlsos = Twinkle.block.seeAlsos.filter(function(el) { - return el !== this.value; - }.bind(this)); - - if (this.checked) { - Twinkle.block.seeAlsos.push(this.value); - } - var seeAlsoMessage = Twinkle.block.seeAlsos.join(' and '); - - if (!Twinkle.block.seeAlsos.length) { - this.form.reason.value = reason; - } else if (reason.indexOf('{{') !== -1) { - this.form.reason.value = reason + ' '; - } else { - this.form.reason.value = reason + '; see also ' + seeAlsoMessage; - } -}; - -Twinkle.block.dsReason = ''; -Twinkle.block.callback.toggle_ds_reason = function twinkleblockCallbackToggleDSReason() { - var reason = this.form.reason.value.replace( - new RegExp(' ?\\(\\[\\[' + Twinkle.block.dsReason + '\\]\\]\\)'), '' - ); - - Twinkle.block.dsinfo.then(function(dsinfo) { - var sanctionCode = this.selectedIndex; - var sanctionName = this.options[sanctionCode].label; - Twinkle.block.dsReason = dsinfo[sanctionName].page; - if (!this.value) { - this.form.reason.value = reason; - } else { - this.form.reason.value = reason + ' ([[' + Twinkle.block.dsReason + ']])'; - } - }.bind(this)); -}; - -Twinkle.block.callback.update_form = function twinkleblockCallbackUpdateForm(e, data) { - var form = e.target.form, expiry = data.expiry; - - // don't override original expiry if useInitialOptions is set - if (!data.useInitialOptions) { - if (Date.parse(expiry)) { - expiry = new Date(expiry).toGMTString(); - form.expiry_preset.value = 'custom'; - } else { - form.expiry_preset.value = data.expiry || 'custom'; - } - - form.expiry.value = expiry; - if (form.expiry_preset.value === 'custom') { - Morebits.quickForm.setElementVisibility(form.expiry.parentNode, true); - } else { - Morebits.quickForm.setElementVisibility(form.expiry.parentNode, false); - } - } - - // boolean-flipped options, more at [[mw:API:Block]] - data.disabletalk = data.disabletalk !== undefined ? data.disabletalk : false; - data.hardblock = data.hardblock !== undefined ? data.hardblock : false; - - // disable autoblock if blocking a bot - if (Twinkle.block.userIsBot || /bot\b/i.test(relevantUserName)) { - data.autoblock = false; - } - - $(form).find('[name=field_block_options]').find(':checkbox').each(function(i, el) { - // don't override original options if useInitialOptions is set - if (data.useInitialOptions && data[el.name] === undefined) { - return; - } - - var check = data[el.name] === '' || !!data[el.name]; - $(el).prop('checked', check); - }); - - if (data.prependReason && data.reason) { - form.reason.value = data.reason + '; ' + form.reason.value; - } else { - form.reason.value = data.reason || ''; - } - - // Clear and/or set any partial page or namespace restrictions - if (form.pagerestrictions) { - var $pageSelect = $(form).find('[name=pagerestrictions]'); - var $namespaceSelect = $(form).find('[name=namespacerestrictions]'); - - // Respect useInitialOptions by clearing data when switching presets - // In practice, this will always clear, since no partial presets use it - if (!data.useInitialOptions) { - $pageSelect.val(null).trigger('change'); - $namespaceSelect.val(null).trigger('change'); - } - - // Add any preset options; in practice, just used for prior block settings - if (data.restrictions) { - if (data.restrictions.pages && !$pageSelect.val().length) { - var pages = data.restrictions.pages.map(function(pr) { - return pr.title; - }); - // since page restrictions use an ajax source, we - // short-circuit that and just add a new option - pages.forEach(function(page) { - if (!$pageSelect.find("option[value='" + $.escapeSelector(page) + "']").length) { - var newOption = new Option(page, page, true, true); - $pageSelect.append(newOption); - } - }); - $pageSelect.val($pageSelect.val().concat(pages)).trigger('change'); - } - if (data.restrictions.namespaces) { - $namespaceSelect.val($namespaceSelect.val().concat(data.restrictions.namespaces)).trigger('change'); - } - } - } -}; - -Twinkle.block.callback.change_template = function twinkleblockcallbackChangeTemplate(e) { - var form = e.target.form, value = form.template.value, settings = Twinkle.block.blockPresetsInfo[value]; - - var blockBox = $(form).find('[name=actiontype][value=block]').is(':checked'); - var partialBox = $(form).find('[name=actiontype][value=partial]').is(':checked'); - var templateBox = $(form).find('[name=actiontype][value=template]').is(':checked'); - - // Block form is not present - if (!blockBox) { - if (settings.indefinite || settings.nonstandard) { - if (Twinkle.block.prev_template_expiry === null) { - Twinkle.block.prev_template_expiry = form.template_expiry.value || ''; - } - form.template_expiry.parentNode.style.display = 'none'; - form.template_expiry.value = 'infinity'; - } else if (form.template_expiry.parentNode.style.display === 'none') { - if (Twinkle.block.prev_template_expiry !== null) { - form.template_expiry.value = Twinkle.block.prev_template_expiry; - Twinkle.block.prev_template_expiry = null; - } - form.template_expiry.parentNode.style.display = 'block'; - } - if (Twinkle.block.prev_template_expiry) { - form.expiry.value = Twinkle.block.prev_template_expiry; - } - Morebits.quickForm.setElementVisibility(form.notalk.parentNode, !settings.nonstandard); - // Partial - Morebits.quickForm.setElementVisibility(form.noemail_template.parentNode, partialBox); - Morebits.quickForm.setElementVisibility(form.nocreate_template.parentNode, partialBox); - } else if (templateBox) { // Only present if block && template forms both visible - Morebits.quickForm.setElementVisibility( - form.blank_duration.parentNode, - !settings.indefinite && !settings.nonstandard - ); - } - - Morebits.quickForm.setElementVisibility(form.dstopic.parentNode, value === 'uw-aeblock' || value === 'uw-aepblock'); - - // Only particularly relevant if template form is present - Morebits.quickForm.setElementVisibility(form.article.parentNode, settings && !!settings.pageParam); - Morebits.quickForm.setElementVisibility(form.block_reason.parentNode, settings && !!settings.reasonParam); - - // Partial block - Morebits.quickForm.setElementVisibility(form.area.parentNode, partialBox && !blockBox); - - form.root.previewer.closePreview(); -}; -Twinkle.block.prev_template_expiry = null; - -Twinkle.block.callback.preview = function twinkleblockcallbackPreview(form) { - var params = { - article: form.article.value, - blank_duration: form.blank_duration ? form.blank_duration.checked : false, - disabletalk: form.disabletalk.checked || (form.notalk ? form.notalk.checked : false), - expiry: form.template_expiry ? form.template_expiry.value : form.expiry.value, - hardblock: Twinkle.block.isRegistered ? form.autoblock.checked : form.hardblock.checked, - indefinite: Morebits.string.isInfinity(form.template_expiry ? form.template_expiry.value : form.expiry.value), - reason: form.block_reason.value, - template: form.template.value, - dstopic: form.dstopic.value, - partial: $(form).find('[name=actiontype][value=partial]').is(':checked'), - pagerestrictions: $(form.pagerestrictions).val() || [], - namespacerestrictions: $(form.namespacerestrictions).val() || [], - noemail: form.noemail.checked || (form.noemail_template ? form.noemail_template.checked : false), - nocreate: form.nocreate.checked || (form.nocreate_template ? form.nocreate_template.checked : false), - area: form.area.value - }; - - var templateText = Twinkle.block.callback.getBlockNoticeWikitext(params); - - form.previewer.beginRender(templateText, 'User_talk:' + relevantUserName); // Force wikitext/correct username -}; - -Twinkle.block.callback.evaluate = function twinkleblockCallbackEvaluate(e) { - var $form = $(e.target), - toBlock = $form.find('[name=actiontype][value=block]').is(':checked'), - toWarn = $form.find('[name=actiontype][value=template]').is(':checked'), - toPartial = $form.find('[name=actiontype][value=partial]').is(':checked'), - blockoptions = {}, templateoptions = {}; - - Twinkle.block.callback.saveFieldset($form.find('[name=field_block_options]')); - Twinkle.block.callback.saveFieldset($form.find('[name=field_template_options]')); - - blockoptions = Twinkle.block.field_block_options; - - templateoptions = Twinkle.block.field_template_options; - - templateoptions.disabletalk = !!(templateoptions.disabletalk || blockoptions.disabletalk); - templateoptions.hardblock = !!blockoptions.hardblock; - - delete blockoptions.expiry_preset; // remove extraneous - - // Partial API requires this to be gone, not false or 0 - if (toPartial) { - blockoptions.partial = templateoptions.partial = true; - } - templateoptions.pagerestrictions = $form.find('[name=pagerestrictions]').val() || []; - templateoptions.namespacerestrictions = $form.find('[name=namespacerestrictions]').val() || []; - // Format for API here rather than in saveFieldset - blockoptions.pagerestrictions = templateoptions.pagerestrictions.join('|'); - blockoptions.namespacerestrictions = templateoptions.namespacerestrictions.join('|'); - - // use block settings as warn options where not supplied - templateoptions.summary = templateoptions.summary || blockoptions.reason; - templateoptions.expiry = templateoptions.template_expiry || blockoptions.expiry; - - if (toBlock) { - if (blockoptions.partial) { - if (blockoptions.disabletalk && blockoptions.namespacerestrictions.indexOf('3') === -1) { - return alert('Partial blocks cannot prevent talk page access unless also restricting them from editing User talk space!'); - } - if (!blockoptions.namespacerestrictions && !blockoptions.pagerestrictions) { - if (!blockoptions.noemail && !blockoptions.nocreate) { // Blank entries technically allowed [[phab:T208645]] - return alert('No pages or namespaces were selected, nor were email or account creation restrictions applied; please select at least one option to apply a partial block!'); - } else if ((templateoptions.template !== 'uw-epblock' || $form.find('select[name="preset"]').val() !== 'uw-epblock') && - // Don't require confirmation if email harassment defaults are set - !confirm('You are about to block with no restrictions on page or namespace editing, are you sure you want to proceed?')) { - return; - } - } - } - if (!blockoptions.expiry) { - return alert('Please provide an expiry!'); - } else if (Morebits.string.isInfinity(blockoptions.expiry) && !Twinkle.block.isRegistered) { - return alert("Can't indefinitely block an IP address!"); - } - if (!blockoptions.reason) { - return alert('Please provide a reason for the block!'); - } - - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(e.target); - var statusElement = new Morebits.status('Executing block'); - blockoptions.action = 'block'; - - blockoptions.user = relevantUserName; - - // boolean-flipped options - blockoptions.anononly = blockoptions.hardblock ? undefined : true; - blockoptions.allowusertalk = blockoptions.disabletalk ? undefined : true; - - /* - Check if block status changed while processing the form. - - There's a lot to consider here. list=blocks provides the - current block status, but there are at least two issues with - relying on it. First, the id doesn't update on a reblock, - meaning the individual parameters need to be compared. This - can be done roughly with JSON.stringify - we can thankfully - rely on order from the server, although sorting would be - fine if not - but falsey values are problematic and is - non-ideal. More importantly, list=blocks won't indicate if a - non-blocked user is blocked then unblocked. This should be - exceedingy rare, but regardless, we thus need to check - list=logevents, which has a nicely updating logid - parameter. We can't rely just on that, though, since it - doesn't account for blocks that have expired on their own. - - As such, we use both. Using some ternaries, the logid - variables are false if there's no logevents, so if they - aren't equal we defintely have a changed entry (send - confirmation). If they are equal, then either the user was - never blocked (the block statuses will be equal, no - confirmation) or there's no new block, in which case either - a block expired (different statuses, confirmation) or the - same block is still active (same status, no confirmation). - */ - var query = { - format: 'json', - action: 'query', - list: 'blocks|logevents', - letype: 'block', - lelimit: 1, - letitle: 'User:' + blockoptions.user - }; - // bkusers doesn't catch single IPs blocked as part of a range block - if (mw.util.isIPAddress(blockoptions.user, true)) { - query.bkip = blockoptions.user; - } else { - query.bkusers = blockoptions.user; - } - api.get(query).then(function(data) { - var block = data.query.blocks[0]; - // As with the initial data fetch, if an IP is blocked - // *and* rangeblocked, this would only grab whichever - // block is more recent, which would likely mean a - // mismatch. However, if the rangeblock is updated - // while filling out the form, this won't detect that, - // but that's probably fine. - if (data.query.blocks.length > 1 && block.user !== relevantUserName) { - block = data.query.blocks[1]; - } - var logevents = data.query.logevents[0]; - var logid = data.query.logevents.length ? logevents.logid : false; - - if (logid !== Twinkle.block.blockLogId || !!block !== !!Twinkle.block.currentBlockInfo) { - var message = 'The block status of ' + blockoptions.user + ' has changed. '; - if (block) { - message += 'New status: '; - } else { - message += 'Last entry: '; - } - - var logExpiry = ''; - if (logevents.params.duration) { - if (logevents.params.duration === 'infinity') { - logExpiry = 'indefinitely'; - } else { - var expiryDate = new Morebits.date(logevents.params.expiry); - logExpiry += (expiryDate.isBefore(new Date()) ? ', expired ' : ' until ') + expiryDate.calendar(); - } - } else { // no duration, action=unblock, just show timestamp - logExpiry = ' ' + new Morebits.date(logevents.timestamp).calendar(); - } - message += Morebits.string.toUpperCaseFirstChar(logevents.action) + 'ed by ' + logevents.user + logExpiry + - ' for "' + logevents.comment + '". Do you want to override with your settings?'; - - if (!confirm(message)) { - Morebits.status.info('Executing block', 'Canceled by user'); - return; - } - blockoptions.reblock = 1; // Writing over a block will fail otherwise - } - - // execute block - blockoptions.tags = Twinkle.changeTags; - blockoptions.token = mw.user.tokens.get('csrfToken'); - var mbApi = new Morebits.wiki.api('Executing block', blockoptions, function() { - statusElement.info('Completed'); - if (toWarn) { - Twinkle.block.callback.issue_template(templateoptions); - } - }); - mbApi.post(); - }); - } else if (toWarn) { - Morebits.simpleWindow.setButtonsEnabled(false); - - Morebits.status.init(e.target); - Twinkle.block.callback.issue_template(templateoptions); - } else { - return alert('Please give Twinkle something to do!'); - } -}; - -Twinkle.block.callback.issue_template = function twinkleblockCallbackIssueTemplate(formData) { - // Use wgRelevantUserName to ensure the block template goes to a single IP and not to the - // "talk page" of an IP range (which does not exist) - var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName'); - - var params = $.extend(formData, { - messageData: Twinkle.block.blockPresetsInfo[formData.template], - reason: Twinkle.block.field_template_options.block_reason, - disabletalk: Twinkle.block.field_template_options.notalk, - noemail: Twinkle.block.field_template_options.noemail_template, - nocreate: Twinkle.block.field_template_options.nocreate_template - }); - - Morebits.wiki.actionCompleted.redirect = userTalkPage; - Morebits.wiki.actionCompleted.notice = 'Actions complete, loading user talk page in a few seconds'; - - var wikipedia_page = new Morebits.wiki.page(userTalkPage, 'User talk page modification'); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.block.callback.main); -}; - -Twinkle.block.callback.getBlockNoticeWikitext = function(params) { - var text = '{{', settings = Twinkle.block.blockPresetsInfo[params.template]; - if (!settings.nonstandard) { - text += 'subst:' + params.template; - if (params.article && settings.pageParam) { - text += '|page=' + params.article; - } - if (params.dstopic) { - text += '|topic=' + params.dstopic; - } - - if (!/te?mp|^\s*$|min/.exec(params.expiry)) { - if (params.indefinite) { - text += '|indef=yes'; - } else if (!params.blank_duration && !new Morebits.date(params.expiry).isValid()) { - // Block template wants a duration, not date - text += '|time=' + params.expiry; - } - } - - if (!Twinkle.block.isRegistered && !params.hardblock) { - text += '|anon=yes'; - } - - if (params.reason) { - text += '|reason=' + params.reason; - } - if (params.disabletalk) { - text += '|notalk=yes'; - } - - // Currently, all partial block templates are "standard" - // Building the template, however, takes a fair bit of logic - if (params.partial) { - if (params.pagerestrictions.length || params.namespacerestrictions.length) { - var makeSentence = function (array) { - if (array.length < 3) { - return array.join(' and '); - } - var last = array.pop(); - return array.join(', ') + ', and ' + last; - - }; - text += '|area=' + (params.indefinite ? 'certain ' : 'from certain '); - if (params.pagerestrictions.length) { - text += 'pages (' + makeSentence(params.pagerestrictions.map(function(p) { - return '[[:' + p + ']]'; - })); - text += params.namespacerestrictions.length ? ') and certain ' : ')'; - } - if (params.namespacerestrictions.length) { - // 1 => Talk, 2 => User, etc. - var namespaceNames = params.namespacerestrictions.map(function(id) { - return menuFormattedNamespaces[id]; - }); - text += '[[Wikipedia:Namespace|namespaces]] (' + makeSentence(namespaceNames) + ')'; - } - } else if (params.area) { - text += '|area=' + params.area; - } else { - if (params.noemail) { - text += '|email=yes'; - } - if (params.nocreate) { - text += '|accountcreate=yes'; - } - } - } - } else { - text += params.template; - } - - if (settings.sig) { - text += '|sig=' + settings.sig; - } - return text + '}}'; -}; - -Twinkle.block.callback.main = function twinkleblockcallbackMain(pageobj) { - var params = pageobj.getCallbackParameters(), - date = new Morebits.date(pageobj.getLoadTime()), - messageData = params.messageData, - text; - - params.indefinite = Morebits.string.isInfinity(params.expiry); - - if (Twinkle.getPref('blankTalkpageOnIndefBlock') && params.template !== 'uw-lblock' && params.indefinite) { - Morebits.status.info('Info', 'Blanking talk page per preferences and creating a new talk page section for this month'); - text = date.monthHeader() + '\n'; - } else { - text = pageobj.getPageText(); - - var dateHeaderRegex = date.monthHeaderRegex(), dateHeaderRegexLast, dateHeaderRegexResult; - while ((dateHeaderRegexLast = dateHeaderRegex.exec(text)) !== null) { - dateHeaderRegexResult = dateHeaderRegexLast; - } - // If dateHeaderRegexResult is null then lastHeaderIndex is never checked. If it is not null but - // \n== is not found, then the date header must be at the very start of the page. lastIndexOf - // returns -1 in this case, so lastHeaderIndex gets set to 0 as desired. - var lastHeaderIndex = text.lastIndexOf('\n==') + 1; - - if (text.length > 0) { - text += '\n\n'; - } - - if (!dateHeaderRegexResult || dateHeaderRegexResult.index !== lastHeaderIndex) { - Morebits.status.info('Info', 'Will create a new talk page section for this month, as none was found'); - text += date.monthHeader() + '\n'; - } - } - - params.expiry = typeof params.template_expiry !== 'undefined' ? params.template_expiry : params.expiry; - - text += Twinkle.block.callback.getBlockNoticeWikitext(params); - - // build the edit summary - var summary = messageData.summary; - if (messageData.suppressArticleInSummary !== true && params.article) { - summary += ' on [[:' + params.article + ']]'; - } - summary += '.'; - - pageobj.setPageText(text); - pageobj.setEditSummary(summary); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('watchWarnings')); - pageobj.save(); -}; - -Twinkle.addInitCallback(Twinkle.block, 'block'); -})(jQuery); - - -//
diff --git a/modules/twinkleprotect-disabled.js b/modules/twinkleprotect-disabled.js deleted file mode 100644 index c69f01c88..000000000 --- a/modules/twinkleprotect-disabled.js +++ /dev/null @@ -1,1664 +0,0 @@ -// - - -(function($) { - - -/* - **************************************** - *** twinkleprotect.js: Protect/RPP module - **************************************** - * Mode of invocation: Tab ("PP"/"RPP") - * Active on: Non-special, non-MediaWiki pages - */ - -// Note: a lot of code in this module is re-used/called by batchprotect. - -Twinkle.protect = function twinkleprotect() { - if (mw.config.get('wgNamespaceNumber') < 0 || mw.config.get('wgNamespaceNumber') === 8) { - return; - } - - Twinkle.addPortletLink(Twinkle.protect.callback, Morebits.userIsSysop ? 'PP' : 'RPP', 'tw-rpp', - Morebits.userIsSysop ? 'Protect page' : 'Request page protection'); -}; - -Twinkle.protect.callback = function twinkleprotectCallback() { - var Window = new Morebits.simpleWindow(620, 530); - Window.setTitle(Morebits.userIsSysop ? 'Apply, request or tag page protection' : 'Request or tag page protection'); - Window.setScriptName('Twinkle'); - Window.addFooterLink('Protection templates', 'Template:Protection templates'); - Window.addFooterLink('Protection policy', 'WP:PROT'); - Window.addFooterLink('Twinkle help', 'WP:TW/DOC#protect'); - Window.addFooterLink('Give feedback', 'WT:TW'); - - var form = new Morebits.quickForm(Twinkle.protect.callback.evaluate); - var actionfield = form.append({ - type: 'field', - label: 'Type of action' - }); - if (Morebits.userIsSysop) { - actionfield.append({ - type: 'radio', - name: 'actiontype', - event: Twinkle.protect.callback.changeAction, - list: [ - { - label: 'Protect page', - value: 'protect', - tooltip: 'Apply actual protection to the page.', - checked: true - } - ] - }); - } - actionfield.append({ - type: 'radio', - name: 'actiontype', - event: Twinkle.protect.callback.changeAction, - list: [ - { - label: 'Request page protection', - value: 'request', - tooltip: 'If you want to request protection via WP:RPP' + (Morebits.userIsSysop ? ' instead of doing the protection by yourself.' : '.'), - checked: !Morebits.userIsSysop - }, - { - label: 'Tag page with protection template', - value: 'tag', - tooltip: 'If the protecting admin forgot to apply a protection template, or you have just protected the page without tagging, you can use this to apply the appropriate protection tag.', - disabled: mw.config.get('wgArticleId') === 0 || mw.config.get('wgPageContentModel') === 'Scribunto' - } - ] - }); - - form.append({ type: 'field', label: 'Preset', name: 'field_preset' }); - form.append({ type: 'field', label: '1', name: 'field1' }); - form.append({ type: 'field', label: '2', name: 'field2' }); - - form.append({ type: 'submit' }); - - var result = form.render(); - Window.setContent(result); - Window.display(); - - // We must init the controls - var evt = document.createEvent('Event'); - evt.initEvent('change', true, true); - result.actiontype[0].dispatchEvent(evt); - - // get current protection level asynchronously - Twinkle.protect.fetchProtectionLevel(); -}; - - -// A list of bots who may be the protecting sysop, for whom we shouldn't -// remind the user contact before requesting unprotection (evaluate) -Twinkle.protect.trustedBots = ['MusikBot II', 'TFA Protector Bot']; - -// Customizable namespace and FlaggedRevs settings -// In theory it'd be nice to have restrictionlevels defined here, -// but those are only available via a siteinfo query - -// mw.loader.getState('ext.flaggedRevs.review') returns null if the -// FlaggedRevs extension is not registered. Previously, this was done with -// wgFlaggedRevsParams, but after 1.34-wmf4 it is no longer exported if empty -// (https://gerrit.wikimedia.org/r/c/mediawiki/extensions/FlaggedRevs/+/508427) -var hasFlaggedRevs = mw.loader.getState('ext.flaggedRevs.review') && -// FlaggedRevs only valid in some namespaces, hardcoded until [[phab:T218479]] -(mw.config.get('wgNamespaceNumber') === 0 || mw.config.get('wgNamespaceNumber') === 4); -// Limit template editor; a Twinkle restriction, not a site setting -var isTemplate = mw.config.get('wgNamespaceNumber') === 10 || mw.config.get('wgNamespaceNumber') === 828; - - -// Contains the current protection level in an object -// Once filled, it will look something like: -// { edit: { level: "sysop", expiry: , cascade: true }, ... } -Twinkle.protect.currentProtectionLevels = {}; - -// returns a jQuery Deferred object, usage: -// Twinkle.protect.fetchProtectingAdmin(apiObject, pageName, protect/stable).done(function(admin_username) { ...code... }); -Twinkle.protect.fetchProtectingAdmin = function twinkleprotectFetchProtectingAdmin(api, pageName, protType, logIds) { - logIds = logIds || []; - - return api.get({ - format: 'json', - action: 'query', - list: 'logevents', - letitle: pageName, - letype: protType - }).then(function(data) { - // don't check log entries that have already been checked (e.g. don't go into an infinite loop!) - var event = data.query ? $.grep(data.query.logevents, function(le) { - return $.inArray(le.logid, logIds); - })[0] : null; - if (!event) { - // fail gracefully - return null; - } else if (event.action === 'move_prot' || event.action === 'move_stable') { - return twinkleprotectFetchProtectingAdmin(api, protType === 'protect' ? event.params.oldtitle_title : event.params.oldtitle, protType, logIds.concat(event.logid)); - } - return event.user; - }); -}; - -Twinkle.protect.fetchProtectionLevel = function twinkleprotectFetchProtectionLevel() { - - var api = new mw.Api(); - var protectDeferred = api.get({ - format: 'json', - indexpageids: true, - action: 'query', - list: 'logevents', - letype: 'protect', - letitle: mw.config.get('wgPageName'), - prop: hasFlaggedRevs ? 'info|flagged' : 'info', - inprop: 'protection|watched', - titles: mw.config.get('wgPageName') - }); - var stableDeferred = api.get({ - format: 'json', - action: 'query', - list: 'logevents', - letype: 'stable', - letitle: mw.config.get('wgPageName') - }); - - var earlyDecision = [protectDeferred]; - if (hasFlaggedRevs) { - earlyDecision.push(stableDeferred); - } - - $.when.apply($, earlyDecision).done(function(protectData, stableData) { - // $.when.apply is supposed to take an unknown number of promises - // via an array, which it does, but the type of data returned varies. - // If there are two or more deferreds, it returns an array (of objects), - // but if there's just one deferred, it retuns a simple object. - // This is annoying. - protectData = $(protectData).toArray(); - - var pageid = protectData[0].query.pageids[0]; - var page = protectData[0].query.pages[pageid]; - var current = {}, adminEditDeferred; - - // Save requested page's watched status for later in case needed when filing request - Twinkle.protect.watched = page.watchlistexpiry || page.watched === ''; - - $.each(page.protection, function(index, protection) { - // Don't overwrite actual page protection with cascading protection - if (!protection.source) { - current[protection.type] = { - level: protection.level, - expiry: protection.expiry, - cascade: protection.cascade === '' - }; - // logs report last admin who made changes to either edit/move/create protection, regardless if they only modified one of them - if (!adminEditDeferred) { - adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'protect'); - } - } else { - // Account for the page being covered by cascading protection - current.cascading = { - expiry: protection.expiry, - source: protection.source, - level: protection.level // should always be sysop, unused - }; - } - }); - - if (page.flagged) { - current.stabilize = { - level: page.flagged.protection_level, - expiry: page.flagged.protection_expiry - }; - adminEditDeferred = Twinkle.protect.fetchProtectingAdmin(api, mw.config.get('wgPageName'), 'stable'); - } - - // show the protection level and log info - Twinkle.protect.hasProtectLog = !!protectData[0].query.logevents.length; - Twinkle.protect.protectLog = Twinkle.protect.hasProtectLog && protectData[0].query.logevents; - Twinkle.protect.hasStableLog = hasFlaggedRevs ? !!stableData[0].query.logevents.length : false; - Twinkle.protect.stableLog = Twinkle.protect.hasStableLog && stableData[0].query.logevents; - Twinkle.protect.currentProtectionLevels = current; - - if (adminEditDeferred) { - adminEditDeferred.done(function(admin) { - if (admin) { - $.each(['edit', 'move', 'create', 'stabilize', 'cascading'], function(i, type) { - if (Twinkle.protect.currentProtectionLevels[type]) { - Twinkle.protect.currentProtectionLevels[type].admin = admin; - } - }); - } - Twinkle.protect.callback.showLogAndCurrentProtectInfo(); - }); - } else { - Twinkle.protect.callback.showLogAndCurrentProtectInfo(); - } - }); -}; - -Twinkle.protect.callback.showLogAndCurrentProtectInfo = function twinkleprotectCallbackShowLogAndCurrentProtectInfo() { - var currentlyProtected = !$.isEmptyObject(Twinkle.protect.currentProtectionLevels); - - if (Twinkle.protect.hasProtectLog || Twinkle.protect.hasStableLog) { - var $linkMarkup = $(''); - - if (Twinkle.protect.hasProtectLog) { - $linkMarkup.append( - $('protection log')); - if (!currentlyProtected || (!Twinkle.protect.currentProtectionLevels.edit && !Twinkle.protect.currentProtectionLevels.move)) { - var lastProtectAction = Twinkle.protect.protectLog[0]; - if (lastProtectAction.action === 'unprotect') { - $linkMarkup.append(' (unprotected ' + new Morebits.date(lastProtectAction.timestamp).calendar('utc') + ')'); - } else { // protect or modify - $linkMarkup.append(' (expired ' + new Morebits.date(lastProtectAction.params.details[0].expiry).calendar('utc') + ')'); - } - } - $linkMarkup.append(Twinkle.protect.hasStableLog ? $('') : null); - } - - if (Twinkle.protect.hasStableLog) { - $linkMarkup.append($('pending changes log)')); - if (!currentlyProtected || !Twinkle.protect.currentProtectionLevels.stabilize) { - var lastStabilizeAction = Twinkle.protect.stableLog[0]; - if (lastStabilizeAction.action === 'reset') { - $linkMarkup.append(' (reset ' + new Morebits.date(lastStabilizeAction.timestamp).calendar('utc') + ')'); - } else { // config or modify - $linkMarkup.append(' (expired ' + new Morebits.date(lastStabilizeAction.params.expiry).calendar('utc') + ')'); - } - } - } - - Morebits.status.init($('div[name="hasprotectlog"] span')[0]); - Morebits.status.warn( - currentlyProtected ? 'Previous protections' : 'This page has been protected in the past', - $linkMarkup[0] - ); - } - - Morebits.status.init($('div[name="currentprot"] span')[0]); - var protectionNode = [], statusLevel = 'info'; - - if (currentlyProtected) { - $.each(Twinkle.protect.currentProtectionLevels, function(type, settings) { - var label = type === 'stabilize' ? 'Pending Changes' : Morebits.string.toUpperCaseFirstChar(type); - - if (type === 'cascading') { // Covered by another page - label = 'Cascading protection '; - protectionNode.push($('' + label + '')[0]); - if (settings.source) { // Should by definition exist - var sourceLink = '' + settings.source + ''; - protectionNode.push($('from ' + sourceLink + '')[0]); - } - } else { - var level = settings.level; - // Make cascading protection more prominent - if (settings.cascade) { - level += ' (cascading)'; - } - protectionNode.push($('' + label + ': ' + level + '')[0]); - } - - if (settings.expiry === 'infinity') { - protectionNode.push(' (indefinite) '); - } else { - protectionNode.push(' (expires ' + new Morebits.date(settings.expiry).calendar('utc') + ') '); - } - if (settings.admin) { - var adminLink = '' + settings.admin + ''; - protectionNode.push($('by ' + adminLink + '')[0]); - } - protectionNode.push($(' \u2022 ')[0]); - }); - protectionNode = protectionNode.slice(0, -1); // remove the trailing bullet - statusLevel = 'warn'; - } else { - protectionNode.push($('no protection')[0]); - } - - Morebits.status[statusLevel]('Current protection level', protectionNode); -}; - -Twinkle.protect.callback.changeAction = function twinkleprotectCallbackChangeAction(e) { - var field_preset; - var field1; - var field2; - - switch (e.target.values) { - case 'protect': - field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Preset', name: 'field_preset' }); - field_preset.append({ - type: 'select', - name: 'category', - label: 'Choose a preset:', - event: Twinkle.protect.callback.changePreset, - list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate - }); - - field2 = new Morebits.quickForm.element({ type: 'field', label: 'Protection options', name: 'field2' }); - field2.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback - field2.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); - // for existing pages - if (mw.config.get('wgArticleId')) { - field2.append({ - type: 'checkbox', - event: Twinkle.protect.formevents.editmodify, - list: [ - { - label: 'Modify edit protection', - name: 'editmodify', - tooltip: 'If this is turned off, the edit protection level, and expiry time, will be left as is.', - checked: true - } - ] - }); - field2.append({ - type: 'select', - name: 'editlevel', - label: 'Who can edit:', - event: Twinkle.protect.formevents.editlevel, - list: Twinkle.protect.protectionLevels.filter(function(level) { - // Filter TE outside of templates and modules - return isTemplate || level.value !== 'templateeditor'; - }) - }); - field2.append({ - type: 'select', - name: 'editexpiry', - label: 'Expires:', - event: function(e) { - if (e.target.value === 'custom') { - Twinkle.protect.doCustomExpiry(e.target); - } - }, - // default expiry selection (2 days) is conditionally set in Twinkle.protect.callback.changePreset - list: Twinkle.protect.protectionLengths - }); - field2.append({ - type: 'checkbox', - event: Twinkle.protect.formevents.movemodify, - list: [ - { - label: 'Modify move protection', - name: 'movemodify', - tooltip: 'If this is turned off, the move protection level, and expiry time, will be left as is.', - checked: true - } - ] - }); - field2.append({ - type: 'select', - name: 'movelevel', - label: 'Who can move:', - event: Twinkle.protect.formevents.movelevel, - list: Twinkle.protect.protectionLevels.filter(function(level) { - // Autoconfirmed is required for a move, redundant - return level.value !== 'autoconfirmed' && (isTemplate || level.value !== 'templateeditor'); - }) - }); - field2.append({ - type: 'select', - name: 'moveexpiry', - label: 'Expires:', - event: function(e) { - if (e.target.value === 'custom') { - Twinkle.protect.doCustomExpiry(e.target); - } - }, - // default expiry selection (2 days) is conditionally set in Twinkle.protect.callback.changePreset - list: Twinkle.protect.protectionLengths - }); - if (hasFlaggedRevs) { - field2.append({ - type: 'checkbox', - event: Twinkle.protect.formevents.pcmodify, - list: [ - { - label: 'Modify pending changes protection', - name: 'pcmodify', - tooltip: 'If this is turned off, the pending changes level, and expiry time, will be left as is.', - checked: true - } - ] - }); - field2.append({ - type: 'select', - name: 'pclevel', - label: 'Pending changes:', - event: Twinkle.protect.formevents.pclevel, - list: [ - { label: 'None', value: 'none' }, - { label: 'Pending change', value: 'autoconfirmed', selected: true } - ] - }); - field2.append({ - type: 'select', - name: 'pcexpiry', - label: 'Expires:', - event: function(e) { - if (e.target.value === 'custom') { - Twinkle.protect.doCustomExpiry(e.target); - } - }, - // default expiry selection (1 month) is conditionally set in Twinkle.protect.callback.changePreset - list: Twinkle.protect.protectionLengths - }); - } - } else { // for non-existing pages - field2.append({ - type: 'select', - name: 'createlevel', - label: 'Create protection:', - event: Twinkle.protect.formevents.createlevel, - list: Twinkle.protect.protectionLevels.filter(function(level) { - // Filter TE always, and autoconfirmed in mainspace, redundant since WP:ACPERM - return level.value !== 'templateeditor' && (mw.config.get('wgNamespaceNumber') !== 0 || level.value !== 'autoconfirmed'); - }) - }); - field2.append({ - type: 'select', - name: 'createexpiry', - label: 'Expires:', - event: function(e) { - if (e.target.value === 'custom') { - Twinkle.protect.doCustomExpiry(e.target); - } - }, - // default expiry selection (indefinite) is conditionally set in Twinkle.protect.callback.changePreset - list: Twinkle.protect.protectionLengths - }); - } - field2.append({ - type: 'textarea', - name: 'protectReason', - label: 'Reason (for protection log):' - }); - field2.append({ - 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.' - }); - field2.append({ - type: 'checkbox', - event: Twinkle.protect.callback.annotateProtectReason, - style: 'display:inline-block; margin-top:4px;', - list: [ - { - label: 'RfPP request', - name: 'protectReason_notes_rfpp', - checked: false, - value: 'requested at [[WP:RfPP]]' - } - ] - }); - field2.append({ - type: 'input', - event: Twinkle.protect.callback.annotateProtectReason, - label: 'RfPP revision ID', - name: 'protectReason_notes_rfppRevid', - value: '', - tooltip: 'Optional revision ID of the RfPP page where protection was requested.' - }); - if (!mw.config.get('wgArticleId') || mw.config.get('wgPageContentModel') === 'Scribunto') { // tagging isn't relevant for non-existing or module pages - break; - } - /* falls through */ - case 'tag': - field1 = new Morebits.quickForm.element({ type: 'field', label: 'Tagging options', name: 'field1' }); - field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback - field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); - field1.append({ - type: 'select', - name: 'tagtype', - label: 'Choose protection template:', - list: Twinkle.protect.protectionTags, - event: Twinkle.protect.formevents.tagtype - }); - field1.append({ - type: 'checkbox', - list: [ - { - name: 'small', - label: 'Iconify (small=yes)', - tooltip: 'Will use the |small=yes feature of the template, and only render it as a keylock', - checked: true - }, - { - name: 'noinclude', - label: 'Wrap protection template with <noinclude>', - tooltip: 'Will wrap the protection template in <noinclude> tags, so that it won\'t transclude', - checked: mw.config.get('wgNamespaceNumber') === 10 || (mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').project && mw.config.get('wgTitle').indexOf('Articles for deletion/') === 0) - } - ] - }); - break; - - case 'request': - field_preset = new Morebits.quickForm.element({ type: 'field', label: 'Type of protection', name: 'field_preset' }); - field_preset.append({ - type: 'select', - name: 'category', - label: 'Type and reason:', - event: Twinkle.protect.callback.changePreset, - list: mw.config.get('wgArticleId') ? Twinkle.protect.protectionTypes : Twinkle.protect.protectionTypesCreate - }); - - field1 = new Morebits.quickForm.element({ type: 'field', label: 'Options', name: 'field1' }); - field1.append({ type: 'div', name: 'currentprot', label: ' ' }); // holds the current protection level, as filled out by the async callback - field1.append({ type: 'div', name: 'hasprotectlog', label: ' ' }); - field1.append({ - type: 'select', - name: 'expiry', - label: 'Duration:', - list: [ - { label: '', selected: true, value: '' }, - { label: 'Temporary', value: 'temporary' }, - { label: 'Indefinite', value: 'infinity' } - ] - }); - field1.append({ - type: 'textarea', - name: 'reason', - label: 'Reason:' - }); - break; - default: - alert("Something's afoot in twinkleprotect"); - break; - } - - var oldfield; - - if (field_preset) { - oldfield = $(e.target.form).find('fieldset[name="field_preset"]')[0]; - oldfield.parentNode.replaceChild(field_preset.render(), oldfield); - } else { - $(e.target.form).find('fieldset[name="field_preset"]').css('display', 'none'); - } - if (field1) { - oldfield = $(e.target.form).find('fieldset[name="field1"]')[0]; - oldfield.parentNode.replaceChild(field1.render(), oldfield); - } else { - $(e.target.form).find('fieldset[name="field1"]').css('display', 'none'); - } - if (field2) { - oldfield = $(e.target.form).find('fieldset[name="field2"]')[0]; - oldfield.parentNode.replaceChild(field2.render(), oldfield); - } else { - $(e.target.form).find('fieldset[name="field2"]').css('display', 'none'); - } - - if (e.target.values === 'protect') { - // fake a change event on the preset dropdown - var evt = document.createEvent('Event'); - evt.initEvent('change', true, true); - e.target.form.category.dispatchEvent(evt); - - // 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(); - } - - // re-add protection level and log info, if it's available - Twinkle.protect.callback.showLogAndCurrentProtectInfo(); -}; - -// NOTE: This function is used by batchprotect as well -Twinkle.protect.formevents = { - editmodify: function twinkleprotectFormEditmodifyEvent(e) { - e.target.form.editlevel.disabled = !e.target.checked; - e.target.form.editexpiry.disabled = !e.target.checked || (e.target.form.editlevel.value === 'all'); - e.target.form.editlevel.style.color = e.target.form.editexpiry.style.color = e.target.checked ? '' : 'transparent'; - }, - editlevel: function twinkleprotectFormEditlevelEvent(e) { - e.target.form.editexpiry.disabled = e.target.value === 'all'; - }, - movemodify: function twinkleprotectFormMovemodifyEvent(e) { - // sync move settings with edit settings if applicable - if (e.target.form.movelevel.disabled && !e.target.form.editlevel.disabled) { - e.target.form.movelevel.value = e.target.form.editlevel.value; - e.target.form.moveexpiry.value = e.target.form.editexpiry.value; - } else if (e.target.form.editlevel.disabled) { - e.target.form.movelevel.value = 'sysop'; - e.target.form.moveexpiry.value = 'infinity'; - } - e.target.form.movelevel.disabled = !e.target.checked; - e.target.form.moveexpiry.disabled = !e.target.checked || (e.target.form.movelevel.value === 'all'); - e.target.form.movelevel.style.color = e.target.form.moveexpiry.style.color = e.target.checked ? '' : 'transparent'; - }, - movelevel: function twinkleprotectFormMovelevelEvent(e) { - e.target.form.moveexpiry.disabled = e.target.value === 'all'; - }, - pcmodify: function twinkleprotectFormPcmodifyEvent(e) { - e.target.form.pclevel.disabled = !e.target.checked; - e.target.form.pcexpiry.disabled = !e.target.checked || (e.target.form.pclevel.value === 'none'); - e.target.form.pclevel.style.color = e.target.form.pcexpiry.style.color = e.target.checked ? '' : 'transparent'; - }, - pclevel: function twinkleprotectFormPclevelEvent(e) { - e.target.form.pcexpiry.disabled = e.target.value === 'none'; - }, - createlevel: function twinkleprotectFormCreatelevelEvent(e) { - e.target.form.createexpiry.disabled = e.target.value === 'all'; - }, - tagtype: function twinkleprotectFormTagtypeEvent(e) { - e.target.form.small.disabled = e.target.form.noinclude.disabled = (e.target.value === 'none') || (e.target.value === 'noop'); - } -}; - -Twinkle.protect.doCustomExpiry = function twinkleprotectDoCustomExpiry(target) { - var custom = prompt('Enter a custom expiry time. \nYou can use relative times, like "1 minute" or "19 days", or absolute timestamps, "yyyymmddhhmm" (e.g. "200602011405" is Feb 1, 2006, at 14:05 UTC).', ''); - if (custom) { - var option = document.createElement('option'); - option.setAttribute('value', custom); - option.textContent = custom; - target.appendChild(option); - target.value = custom; - } else { - target.selectedIndex = 0; - } -}; - -// NOTE: This list is used by batchprotect as well -Twinkle.protect.protectionLevels = [ - { label: 'All', value: 'all' }, - { label: 'Autoconfirmed', value: 'autoconfirmed' }, - { label: 'Extended confirmed', value: 'extendedconfirmed' }, - { label: 'Template editor', value: 'templateeditor' }, - { label: 'Sysop', value: 'sysop', selected: true } -]; - -// default expiry selection is conditionally set in Twinkle.protect.callback.changePreset -// NOTE: This list is used by batchprotect as well -Twinkle.protect.protectionLengths = [ - { label: '1 hour', value: '1 hour' }, - { label: '2 hours', value: '2 hours' }, - { label: '3 hours', value: '3 hours' }, - { label: '6 hours', value: '6 hours' }, - { label: '12 hours', value: '12 hours' }, - { label: '1 day', value: '1 day' }, - { label: '2 days', value: '2 days' }, - { label: '3 days', value: '3 days' }, - { label: '4 days', value: '4 days' }, - { label: '1 week', value: '1 week' }, - { label: '2 weeks', value: '2 weeks' }, - { label: '1 month', value: '1 month' }, - { label: '2 months', value: '2 months' }, - { label: '3 months', value: '3 months' }, - { label: '1 year', value: '1 year' }, - { label: 'indefinite', value: 'infinity' }, - { label: 'Custom...', value: 'custom' } -]; - -Twinkle.protect.protectionTypes = [ - { label: 'Unprotection', value: 'unprotect' }, - { - label: 'Full protection', - list: [ - { label: 'Generic (full)', value: 'pp-protected' }, - { label: 'Content dispute/edit warring (full)', value: 'pp-dispute' }, - { label: 'Persistent vandalism (full)', value: 'pp-vandalism' }, - { label: 'User talk of blocked user (full)', value: 'pp-usertalk' } - ] - }, - { - label: 'Template protection', - list: [ - { label: 'Highly visible template (TE)', value: 'pp-template' } - ] - }, - { - label: 'Extended confirmed protection', - list: [ - { label: 'Generic (ECP)', value: 'pp-30-500' }, - { label: 'Arbitration enforcement (ECP)', selected: true, value: 'pp-30-500-arb' }, - { label: 'Persistent vandalism (ECP)', value: 'pp-30-500-vandalism' }, - { label: 'Disruptive editing (ECP)', value: 'pp-30-500-disruptive' }, - { label: 'BLP policy violations (ECP)', value: 'pp-30-500-blp' }, - { label: 'Sockpuppetry (ECP)', value: 'pp-30-500-sock' } - ] - }, - { - label: 'Semi-protection', - list: [ - { label: 'Generic (semi)', value: 'pp-semi-protected' }, - { label: 'Persistent vandalism (semi)', selected: true, value: 'pp-semi-vandalism' }, - { label: 'Disruptive editing (semi)', value: 'pp-semi-disruptive' }, - { label: 'Adding unsourced content (semi)', value: 'pp-semi-unsourced' }, - { label: 'BLP policy violations (semi)', value: 'pp-semi-blp' }, - { label: 'Sockpuppetry (semi)', value: 'pp-semi-sock' }, - { label: 'User talk of blocked user (semi)', value: 'pp-semi-usertalk' } - ] - }, - { - label: 'Pending changes', - list: [ - { label: 'Generic (PC)', value: 'pp-pc-protected' }, - { label: 'Persistent vandalism (PC)', value: 'pp-pc-vandalism' }, - { label: 'Disruptive editing (PC)', value: 'pp-pc-disruptive' }, - { label: 'Adding unsourced content (PC)', value: 'pp-pc-unsourced' }, - { label: 'BLP policy violations (PC)', value: 'pp-pc-blp' } - ] - }, - { - label: 'Move protection', - list: [ - { label: 'Generic (move)', value: 'pp-move' }, - { label: 'Dispute/move warring (move)', value: 'pp-move-dispute' }, - { label: 'Page-move vandalism (move)', value: 'pp-move-vandalism' }, - { label: 'Highly visible page (move)', value: 'pp-move-indef' } - ] - } -].filter(function(type) { - // Filter for templates and flaggedrevs - return (isTemplate || type.label !== 'Template protection') && (hasFlaggedRevs || type.label !== 'Pending changes'); -}); - -Twinkle.protect.protectionTypesCreate = [ - { label: 'Unprotection', value: 'unprotect' }, - { - label: 'Create protection', - list: [ - { label: 'Offensive name', value: 'pp-create-offensive' }, - { label: 'Repeatedly recreated', selected: true, value: 'pp-create-salt' }, - { label: 'Recently deleted BLP', value: 'pp-create-blp' } - ] - } -]; - -// A page with both regular and PC protection will be assigned its regular -// protection weight plus 2 -Twinkle.protect.protectionWeight = { - sysop: 40, - templateeditor: 30, - extendedconfirmed: 20, - autoconfirmed: 10, - flaggedrevs_autoconfirmed: 5, // Pending Changes protection alone - all: 0, - flaggedrevs_none: 0 // just in case -}; - -// NOTICE: keep this synched with [[MediaWiki:Protect-dropdown]] -// Also note: stabilize = Pending Changes level -// expiry will override any defaults -Twinkle.protect.protectionPresetsInfo = { - 'pp-protected': { - edit: 'sysop', - move: 'sysop', - reason: null - }, - 'pp-dispute': { - edit: 'sysop', - move: 'sysop', - reason: '[[WP:PP#Content disputes|Edit warring / content dispute]]' - }, - 'pp-vandalism': { - edit: 'sysop', - move: 'sysop', - reason: 'Persistent [[WP:Vandalism|vandalism]]' - }, - 'pp-usertalk': { - edit: 'sysop', - move: 'sysop', - expiry: 'infinity', - reason: '[[WP:PP#Talk-page protection|Inappropriate use of user talk page while blocked]]' - }, - 'pp-template': { - edit: 'templateeditor', - move: 'templateeditor', - expiry: 'infinity', - reason: '[[WP:High-risk templates|Highly visible template]]' - }, - 'pp-30-500-arb': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - expiry: 'infinity', - reason: '[[WP:30/500|Arbitration enforcement]]', - template: 'pp-extended' - }, - 'pp-30-500-vandalism': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - reason: 'Persistent [[WP:Vandalism|vandalism]] from (auto)confirmed accounts', - template: 'pp-extended' - }, - 'pp-30-500-disruptive': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - reason: 'Persistent [[WP:Disruptive editing|disruptive editing]] from (auto)confirmed accounts', - template: 'pp-extended' - }, - 'pp-30-500-blp': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - reason: 'Persistent violations of the [[WP:BLP|biographies of living persons policy]] from (auto)confirmed accounts', - template: 'pp-extended' - }, - 'pp-30-500-sock': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - reason: 'Persistent [[WP:Sock puppetry|sock puppetry]]', - template: 'pp-extended' - }, - 'pp-30-500': { - edit: 'extendedconfirmed', - move: 'extendedconfirmed', - reason: null, - template: 'pp-extended' - }, - 'pp-semi-vandalism': { - edit: 'autoconfirmed', - reason: 'Persistent [[WP:Vandalism|vandalism]]', - template: 'pp-vandalism' - }, - 'pp-semi-disruptive': { - edit: 'autoconfirmed', - reason: 'Persistent [[WP:Disruptive editing|disruptive editing]]', - template: 'pp-protected' - }, - 'pp-semi-unsourced': { - edit: 'autoconfirmed', - reason: 'Persistent addition of [[WP:INTREF|unsourced or poorly sourced content]]', - template: 'pp-protected' - }, - 'pp-semi-blp': { - edit: 'autoconfirmed', - reason: 'Violations of the [[WP:BLP|biographies of living persons policy]]', - template: 'pp-blp' - }, - 'pp-semi-usertalk': { - edit: 'autoconfirmed', - move: 'autoconfirmed', - expiry: 'infinity', - reason: '[[WP:PP#Talk-page protection|Inappropriate use of user talk page while blocked]]', - template: 'pp-usertalk' - }, - 'pp-semi-template': { // removed for now - edit: 'autoconfirmed', - move: 'autoconfirmed', - expiry: 'infinity', - reason: '[[WP:High-risk templates|Highly visible template]]', - template: 'pp-template' - }, - 'pp-semi-sock': { - edit: 'autoconfirmed', - reason: 'Persistent [[WP:Sock puppetry|sock puppetry]]', - template: 'pp-sock' - }, - 'pp-semi-protected': { - edit: 'autoconfirmed', - reason: null, - template: 'pp-protected' - }, - 'pp-pc-vandalism': { - stabilize: 'autoconfirmed', // stabilize = Pending Changes - reason: 'Persistent [[WP:Vandalism|vandalism]]', - template: 'pp-pc' - }, - 'pp-pc-disruptive': { - stabilize: 'autoconfirmed', - reason: 'Persistent [[WP:Disruptive editing|disruptive editing]]', - template: 'pp-pc' - }, - 'pp-pc-unsourced': { - stabilize: 'autoconfirmed', - reason: 'Persistent addition of [[WP:INTREF|unsourced or poorly sourced content]]', - template: 'pp-pc' - }, - 'pp-pc-blp': { - stabilize: 'autoconfirmed', - reason: 'Violations of the [[WP:BLP|biographies of living persons policy]]', - template: 'pp-pc' - }, - 'pp-pc-protected': { - stabilize: 'autoconfirmed', - reason: null, - template: 'pp-pc' - }, - 'pp-move': { - move: 'sysop', - reason: null - }, - 'pp-move-dispute': { - move: 'sysop', - reason: '[[WP:MOVP|Move warring]]' - }, - 'pp-move-vandalism': { - move: 'sysop', - reason: '[[WP:MOVP|Page-move vandalism]]' - }, - 'pp-move-indef': { - move: 'sysop', - expiry: 'infinity', - reason: '[[WP:MOVP|Highly visible page]]' - }, - 'unprotect': { - edit: 'all', - move: 'all', - stabilize: 'none', - create: 'all', - reason: null, - template: 'none' - }, - 'pp-create-offensive': { - create: 'sysop', - reason: '[[WP:SALT|Offensive name]]' - }, - 'pp-create-salt': { - create: 'extendedconfirmed', - reason: '[[WP:SALT|Repeatedly recreated]]' - }, - 'pp-create-blp': { - create: 'extendedconfirmed', - reason: '[[WP:BLPDEL|Recently deleted BLP]]' - } -}; - -Twinkle.protect.protectionTags = [ - { - label: 'None (remove existing protection templates)', - value: 'none' - }, - { - label: 'None (do not remove existing protection templates)', - value: 'noop' - }, - { - label: 'Edit protection templates', - list: [ - { label: '{{pp-vandalism}}: vandalism', value: 'pp-vandalism' }, - { label: '{{pp-dispute}}: dispute/edit war', value: 'pp-dispute' }, - { label: '{{pp-blp}}: BLP violations', value: 'pp-blp' }, - { label: '{{pp-sock}}: sockpuppetry', value: 'pp-sock' }, - { label: '{{pp-template}}: high-risk template', value: 'pp-template' }, - { label: '{{pp-usertalk}}: blocked user talk', value: 'pp-usertalk' }, - { label: '{{pp-protected}}: general protection', value: 'pp-protected' }, - { label: '{{pp-semi-indef}}: general long-term semi-protection', value: 'pp-semi-indef' }, - { label: '{{pp-extended}}: extended confirmed protection', value: 'pp-extended' } - ] - }, - { - label: 'Pending changes templates', - list: [ - { label: '{{pp-pc}}: pending changes', value: 'pp-pc' } - ] - }, - { - label: 'Move protection templates', - list: [ - { label: '{{pp-move-dispute}}: dispute/move war', value: 'pp-move-dispute' }, - { label: '{{pp-move-vandalism}}: page-move vandalism', value: 'pp-move-vandalism' }, - { label: '{{pp-move-indef}}: general long-term', value: 'pp-move-indef' }, - { label: '{{pp-move}}: other', value: 'pp-move' } - ] - } -].filter(function(type) { - // Filter FlaggedRevs - return hasFlaggedRevs || type.label !== 'Pending changes templates'; -}); - -Twinkle.protect.callback.changePreset = function twinkleprotectCallbackChangePreset(e) { - var form = e.target.form; - - var actiontypes = form.actiontype; - var actiontype; - for (var i = 0; i < actiontypes.length; i++) { - if (!actiontypes[i].checked) { - continue; - } - actiontype = actiontypes[i].values; - break; - } - - if (actiontype === 'protect') { // actually protecting the page - var item = Twinkle.protect.protectionPresetsInfo[form.category.value]; - - if (mw.config.get('wgArticleId')) { - if (item.edit) { - form.editmodify.checked = true; - Twinkle.protect.formevents.editmodify({ target: form.editmodify }); - form.editlevel.value = item.edit; - Twinkle.protect.formevents.editlevel({ target: form.editlevel }); - } else { - form.editmodify.checked = false; - Twinkle.protect.formevents.editmodify({ target: form.editmodify }); - } - - if (item.move) { - form.movemodify.checked = true; - Twinkle.protect.formevents.movemodify({ target: form.movemodify }); - form.movelevel.value = item.move; - Twinkle.protect.formevents.movelevel({ target: form.movelevel }); - } else { - form.movemodify.checked = false; - Twinkle.protect.formevents.movemodify({ target: form.movemodify }); - } - - form.editexpiry.value = form.moveexpiry.value = item.expiry || '2 days'; - - - if (form.pcmodify) { - if (item.stabilize) { - form.pcmodify.checked = true; - Twinkle.protect.formevents.pcmodify({ target: form.pcmodify }); - form.pclevel.value = item.stabilize; - Twinkle.protect.formevents.pclevel({ target: form.pclevel }); - } else { - form.pcmodify.checked = false; - Twinkle.protect.formevents.pcmodify({ target: form.pcmodify }); - } - form.pcexpiry.value = item.expiry || '1 month'; - } - } else { - if (item.create) { - form.createlevel.value = item.create; - Twinkle.protect.formevents.createlevel({ target: form.createlevel }); - } - form.createexpiry.value = item.expiry || 'infinity'; - } - - var reasonField = actiontype === 'protect' ? form.protectReason : form.reason; - if (item.reason) { - reasonField.value = item.reason; - } else { - reasonField.value = ''; - } - // Add any annotations - Twinkle.protect.callback.annotateProtectReason(e); - - // sort out tagging options, disabled if nonexistent or lua - if (mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto') { - if (form.category.value === 'unprotect') { - form.tagtype.value = 'none'; - } else { - form.tagtype.value = item.template ? item.template : form.category.value; - } - Twinkle.protect.formevents.tagtype({ target: form.tagtype }); - - // Default settings for adding tags to protection templates - var isTemplateEditorProtection = form.category.value === 'pp-template'; - var isAFD = mw.config.get('wgNamespaceNumber') === mw.config.get('wgNamespaceIds').project && mw.config.get('wgTitle').indexOf('Articles for deletion/') === 0; - var isNotTemplateNamespace = mw.config.get('wgNamespaceNumber') !== 10; - if (isTemplateEditorProtection || isAFD) { - form.noinclude.checked = true; - } else if (isNotTemplateNamespace) { - form.noinclude.checked = false; - } - } - - } else { // RPP request - if (form.category.value === 'unprotect') { - form.expiry.value = ''; - form.expiry.disabled = true; - } else { - form.expiry.value = ''; - form.expiry.disabled = false; - } - } -}; - -Twinkle.protect.callback.evaluate = function twinkleprotectCallbackEvaluate(e) { - var form = e.target; - var input = Morebits.quickForm.getInputData(form); - - var tagparams; - if (input.actiontype === 'tag' || (input.actiontype === 'protect' && mw.config.get('wgArticleId') && mw.config.get('wgPageContentModel') !== 'Scribunto')) { - tagparams = { - tag: input.tagtype, - reason: false, - small: input.small, - noinclude: input.noinclude - }; - } - - switch (input.actiontype) { - case 'protect': - // protect the page - Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); - Morebits.wiki.actionCompleted.notice = 'Protection complete'; - - var statusInited = false; - var thispage; - - var allDone = function twinkleprotectCallbackAllDone() { - if (thispage) { - thispage.getStatusElement().info('done'); - } - if (tagparams) { - Twinkle.protect.callbacks.taggingPageInitial(tagparams); - } - }; - - var protectIt = function twinkleprotectCallbackProtectIt(next) { - thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Protecting page'); - if (mw.config.get('wgArticleId')) { - if (input.editmodify) { - thispage.setEditProtection(input.editlevel, input.editexpiry); - } - if (input.movemodify) { - // Ensure a level has actually been chosen - if (input.movelevel) { - thispage.setMoveProtection(input.movelevel, input.moveexpiry); - } else { - alert('You must chose a move protection level!'); - return; - } - } - thispage.setWatchlist(Twinkle.getPref('watchProtectedPages')); - } else { - thispage.setCreateProtection(input.createlevel, input.createexpiry); - thispage.setWatchlist(false); - } - - if (input.protectReason) { - thispage.setEditSummary(input.protectReason); - } else { - alert('You must enter a protect reason, which will be inscribed into the protection log.'); - return; - } - - if (input.protectReason_notes_rfppRevid && !/^\d+$/.test(input.protectReason_notes_rfppRevid)) { - alert('The provided revision ID is malformed. Please see https://en.wikipedia.org/wiki/Help:Permanent_link for information on how to find the correct ID (also called "oldid").'); - return; - } - - if (!statusInited) { - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(form); - statusInited = true; - } - - thispage.setChangeTags(Twinkle.changeTags); - thispage.protect(next); - }; - - var stabilizeIt = function twinkleprotectCallbackStabilizeIt() { - if (thispage) { - thispage.getStatusElement().info('done'); - } - - thispage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Applying pending changes protection'); - thispage.setFlaggedRevs(input.pclevel, input.pcexpiry); - - if (input.protectReason) { - thispage.setEditSummary(input.protectReason + Twinkle.summaryAd); // flaggedrevs tag support: [[phab:T247721]] - } else { - alert('You must enter a protect reason, which will be inscribed into the protection log.'); - return; - } - - if (!statusInited) { - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(form); - statusInited = true; - } - - thispage.setWatchlist(Twinkle.getPref('watchProtectedPages')); - thispage.stabilize(allDone, function(error) { - if (error.errorCode === 'stabilize_denied') { // [[phab:T234743]] - thispage.getStatusElement().error('Failed trying to modify pending changes settings, likely due to a mediawiki bug. Other actions (tagging or regular protection) may have taken place. Please reload the page and try again.'); - } - }); - }; - - if (input.editmodify || input.movemodify || !mw.config.get('wgArticleId')) { - if (input.pcmodify) { - protectIt(stabilizeIt); - } else { - protectIt(allDone); - } - } else if (input.pcmodify) { - stabilizeIt(); - } else { - alert("Please give Twinkle something to do! \nIf you just want to tag the page, you can choose the 'Tag page with protection template' option at the top."); - } - - break; - - case 'tag': - // apply a protection template - - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(form); - - Morebits.wiki.actionCompleted.redirect = mw.config.get('wgPageName'); - Morebits.wiki.actionCompleted.followRedirect = false; - Morebits.wiki.actionCompleted.notice = 'Tagging complete'; - - Twinkle.protect.callbacks.taggingPageInitial(tagparams); - break; - - case 'request': - // file request at RFPP - var typename, typereason; - switch (input.category) { - case 'pp-dispute': - case 'pp-vandalism': - case 'pp-usertalk': - case 'pp-protected': - typename = 'full protection'; - break; - case 'pp-template': - typename = 'template protection'; - break; - case 'pp-30-500-arb': - case 'pp-30-500-vandalism': - case 'pp-30-500-disruptive': - case 'pp-30-500-blp': - case 'pp-30-500-sock': - case 'pp-30-500': - typename = 'extended confirmed protection'; - break; - case 'pp-semi-vandalism': - case 'pp-semi-disruptive': - case 'pp-semi-unsourced': - case 'pp-semi-usertalk': - case 'pp-semi-sock': - case 'pp-semi-blp': - case 'pp-semi-protected': - typename = 'semi-protection'; - break; - case 'pp-pc-vandalism': - case 'pp-pc-blp': - case 'pp-pc-protected': - case 'pp-pc-unsourced': - case 'pp-pc-disruptive': - typename = 'pending changes'; - break; - case 'pp-move': - case 'pp-move-dispute': - case 'pp-move-indef': - case 'pp-move-vandalism': - typename = 'move protection'; - break; - case 'pp-create-offensive': - case 'pp-create-blp': - case 'pp-create-salt': - typename = 'create protection'; - break; - case 'unprotect': - var admins = $.map(Twinkle.protect.currentProtectionLevels, function(pl) { - if (!pl.admin || Twinkle.protect.trustedBots.indexOf(pl.admin) !== -1) { - return null; - } - return 'User:' + pl.admin; - }); - if (admins.length && !confirm('Have you attempted to contact the protecting admins (' + Morebits.array.uniq(admins).join(', ') + ') first?')) { - return false; - } - // otherwise falls through - default: - typename = 'unprotection'; - break; - } - switch (input.category) { - case 'pp-dispute': - typereason = 'Content dispute/edit warring'; - break; - case 'pp-vandalism': - case 'pp-semi-vandalism': - case 'pp-pc-vandalism': - case 'pp-30-500-vandalism': - typereason = 'Persistent [[WP:VAND|vandalism]]'; - break; - case 'pp-semi-disruptive': - case 'pp-pc-disruptive': - case 'pp-30-500-disruptive': - typereason = 'Persistent [[Wikipedia:Disruptive editing|disruptive editing]]'; - break; - case 'pp-semi-unsourced': - case 'pp-pc-unsourced': - typereason = 'Persistent addition of [[WP:INTREF|unsourced or poorly sourced content]]'; - break; - case 'pp-template': - typereason = '[[WP:HIGHRISK|High-risk template]]'; - break; - case 'pp-30-500-arb': - typereason = '[[WP:30/500|Arbitration enforcement]]'; - break; - case 'pp-usertalk': - case 'pp-semi-usertalk': - typereason = 'Inappropriate use of user talk page while blocked'; - break; - case 'pp-semi-sock': - case 'pp-30-500-sock': - typereason = 'Persistent [[WP:SOCK|sockpuppetry]]'; - break; - case 'pp-semi-blp': - case 'pp-pc-blp': - case 'pp-30-500-blp': - typereason = '[[WP:BLP|BLP]] policy violations'; - break; - case 'pp-move-dispute': - typereason = 'Page title dispute/move warring'; - break; - case 'pp-move-vandalism': - typereason = 'Page-move vandalism'; - break; - case 'pp-move-indef': - typereason = 'Highly visible page'; - break; - case 'pp-create-offensive': - typereason = 'Offensive name'; - break; - case 'pp-create-blp': - typereason = 'Recently deleted [[WP:BLP|BLP]]'; - break; - case 'pp-create-salt': - typereason = 'Repeatedly recreated'; - break; - default: - typereason = ''; - break; - } - - var reason = typereason; - if (input.reason !== '') { - if (typereason !== '') { - reason += '\u00A0\u2013 '; // U+00A0 NO-BREAK SPACE; U+2013 EN RULE - } - reason += input.reason; - } - if (reason !== '' && reason.charAt(reason.length - 1) !== '.') { - reason += '.'; - } - - var rppparams = { - reason: reason, - typename: typename, - category: input.category, - expiry: input.expiry - }; - - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(form); - - var rppName = 'Wikipedia:Requests for page protection/Increase'; - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = 'Wikipedia: Requests for page protection'; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, redirecting now to the discussion page'; - - var rppPage = new Morebits.wiki.page(rppName, 'Requesting protection of page'); - rppPage.setFollowRedirect(true); - rppPage.setCallbackParameters(rppparams); - rppPage.load(Twinkle.protect.callbacks.fileRequest); - break; - default: - alert('twinkleprotect: unknown kind of action'); - break; - } -}; - -Twinkle.protect.protectReasonAnnotations = []; -Twinkle.protect.callback.annotateProtectReason = function twinkleprotectCallbackAnnotateProtectReason(e) { - var form = e.target.form; - var protectReason = form.protectReason.value.replace(new RegExp('(?:; )?' + mw.util.escapeRegExp(Twinkle.protect.protectReasonAnnotations.join(': '))), ''); - - if (this.name === 'protectReason_notes_rfpp') { - if (this.checked) { - Twinkle.protect.protectReasonAnnotations.push(this.value); - $(form.protectReason_notes_rfppRevid).parent().show(); - } else { - Twinkle.protect.protectReasonAnnotations = []; - form.protectReason_notes_rfppRevid.value = ''; - $(form.protectReason_notes_rfppRevid).parent().hide(); - } - } else if (this.name === 'protectReason_notes_rfppRevid') { - Twinkle.protect.protectReasonAnnotations = Twinkle.protect.protectReasonAnnotations.filter(function(el) { - return el.indexOf('[[Special:Permalink') === -1; - }); - if (e.target.value.length) { - var permalink = '[[Special:Permalink/' + e.target.value + '#' + Morebits.pageNameNorm + ']]'; - Twinkle.protect.protectReasonAnnotations.push(permalink); - } - } - - if (!Twinkle.protect.protectReasonAnnotations.length) { - form.protectReason.value = protectReason; - } else { - form.protectReason.value = (protectReason ? protectReason + '; ' : '') + Twinkle.protect.protectReasonAnnotations.join(': '); - } -}; - -Twinkle.protect.callbacks = { - taggingPageInitial: function(tagparams) { - if (tagparams.tag === 'noop') { - Morebits.status.info('Applying protection template', 'nothing to do'); - return; - } - - var protectedPage = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page'); - protectedPage.setCallbackParameters(tagparams); - protectedPage.load(Twinkle.protect.callbacks.taggingPage); - }, - taggingPage: function(protectedPage) { - var params = protectedPage.getCallbackParameters(); - var text = protectedPage.getPageText(); - var tag, summary; - - var oldtag_re = /(?:\/\*)?\s*(?:)?\s*\{\{\s*(pp-[^{}]*?|protected|(?:t|v|s|p-|usertalk-v|usertalk-s|sb|move)protected(?:2)?|protected template|privacy protection)\s*?\}\}\s*(?:<\/noinclude>)?\s*(?:\*\/)?\s*/gi; - var re_result = oldtag_re.exec(text); - if (re_result) { - if (params.tag === 'none' || confirm('{{' + re_result[1] + '}} was found on the page. \nClick OK to remove it, or click Cancel to leave it there.')) { - text = text.replace(oldtag_re, ''); - } - } - - if (params.tag === 'none') { - summary = 'Removing protection template'; - } else { - tag = params.tag; - if (params.reason) { - tag += '|reason=' + params.reason; - } - if (params.small) { - tag += '|small=yes'; - } - - if (/^\s*#redirect/i.test(text)) { // redirect page - // Only tag if no {{rcat shell}} is found - if (!text.match(/{{(?:redr|this is a redirect|r(?:edirect)?(?:.?cat.*)?[ _]?sh)/i)) { - text = text.replace(/#REDIRECT ?(\[\[.*?\]\])(.*)/i, '#REDIRECT $1$2\n\n{{' + tag + '}}'); - } else { - Morebits.status.info('Redirect category shell present', 'nothing to do'); - return; - } - } else { - var needsTagToBeCommentedOut = ['javascript', 'css', 'sanitized-css'].indexOf(protectedPage.getContentModel()) !== -1; - if (needsTagToBeCommentedOut) { - if (params.noinclude) { - tag = '/* {{' + tag + '}} */'; - } else { - tag = '/* {{' + tag + '}} */\n'; - } - - // Prepend tag at very top - text = tag + text; - } else { - if (params.noinclude) { - tag = '{{' + tag + '}}'; - } else { - tag = '{{' + tag + '}}\n'; - } - - // Insert tag after short description or any hatnotes - var wikipage = new Morebits.wikitext.page(text); - text = wikipage.insertAfterTemplates(tag, Twinkle.hatnoteRegex).getText(); - } - } - summary = 'Adding {{' + params.tag + '}}'; - } - - protectedPage.setEditSummary(summary); - protectedPage.setChangeTags(Twinkle.changeTags); - protectedPage.setWatchlist(Twinkle.getPref('watchPPTaggedPages')); - protectedPage.setPageText(text); - protectedPage.setCreateOption('nocreate'); - protectedPage.suppressProtectWarning(); // no need to let admins know they are editing through protection - protectedPage.save(); - }, - - fileRequest: function(rppPage) { - - var rppPage2 = new Morebits.wiki.page('Wikipedia:Requests for page protection/Decrease', 'Loading requests pages'); - rppPage2.load(function() { - var params = rppPage.getCallbackParameters(); - var text = rppPage.getPageText(); - var statusElement = rppPage.getStatusElement(); - var text2 = rppPage2.getPageText(); - - var rppRe = new RegExp('===\\s*(\\[\\[)?\\s*:?\\s*' + Morebits.string.escapeRegExp(Morebits.pageNameNorm) + '\\s*(\\]\\])?\\s*===', 'm'); - var tag = rppRe.exec(text) || rppRe.exec(text2); - - var rppLink = document.createElement('a'); - rppLink.setAttribute('href', mw.util.getUrl('Wikipedia:Requests for page protection')); - rppLink.appendChild(document.createTextNode('Wikipedia:Requests for page protection')); - - if (tag) { - statusElement.error([ 'There is already a protection request for this page at ', rppLink, ', aborting.' ]); - return; - } - - var newtag = '=== [[:' + Morebits.pageNameNorm + ']] ===\n'; - if (new RegExp('^' + mw.util.escapeRegExp(newtag).replace(/\s+/g, '\\s*'), 'm').test(text) || new RegExp('^' + mw.util.escapeRegExp(newtag).replace(/\s+/g, '\\s*'), 'm').test(text2)) { - statusElement.error([ 'There is already a protection request for this page at ', rppLink, ', aborting.' ]); - return; - } - newtag += '* {{pagelinks|1=' + Morebits.pageNameNorm + '}}\n\n'; - - var words; - switch (params.expiry) { - case 'temporary': - words = 'Temporary '; - break; - case 'infinity': - words = 'Indefinite '; - break; - default: - words = ''; - break; - } - - words += params.typename; - - newtag += "'''" + Morebits.string.toUpperCaseFirstChar(words) + (params.reason !== '' ? ":''' " + - Morebits.string.formatReasonText(params.reason) : ".'''") + ' ~~~~'; - - // If either protection type results in a increased status, then post it under increase - // else we post it under decrease - var increase = false; - var protInfo = Twinkle.protect.protectionPresetsInfo[params.category]; - - // function to compute protection weights (see comment at Twinkle.protect.protectionWeight) - var computeWeight = function(mainLevel, stabilizeLevel) { - var result = Twinkle.protect.protectionWeight[mainLevel || 'all']; - if (stabilizeLevel) { - if (result) { - if (stabilizeLevel.level === 'autoconfirmed') { - result += 2; - } - } else { - result = Twinkle.protect.protectionWeight['flaggedrevs_' + stabilizeLevel]; - } - } - return result; - }; - - // compare the page's current protection weights with the protection we are requesting - var editWeight = computeWeight(Twinkle.protect.currentProtectionLevels.edit && - Twinkle.protect.currentProtectionLevels.edit.level, - Twinkle.protect.currentProtectionLevels.stabilize && - Twinkle.protect.currentProtectionLevels.stabilize.level); - if (computeWeight(protInfo.edit, protInfo.stabilize) > editWeight || - computeWeight(protInfo.move) > computeWeight(Twinkle.protect.currentProtectionLevels.move && - Twinkle.protect.currentProtectionLevels.move.level) || - computeWeight(protInfo.create) > computeWeight(Twinkle.protect.currentProtectionLevels.create && - Twinkle.protect.currentProtectionLevels.create.level)) { - increase = true; - } - - if (increase) { - var originalTextLength = text.length; - text += '\n' + newtag; - if (text.length === originalTextLength) { - var linknode = document.createElement('a'); - linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing RPP')); - linknode.appendChild(document.createTextNode('How to fix RPP')); - statusElement.error([ 'Could not find relevant heading on WP:RPP. To fix this problem, please see ', linknode, '.' ]); - return; - } - statusElement.status('Adding new request...'); - rppPage.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Requesting ' + params.typename + (params.typename === 'pending changes' ? ' on [[:' : ' of [[:') + - Morebits.pageNameNorm + ']].'); - rppPage.setChangeTags(Twinkle.changeTags); - rppPage.setPageText(text); - rppPage.setCreateOption('recreate'); - rppPage.save(function() { - // Watch the page being requested - var watchPref = Twinkle.getPref('watchRequestedPages'); - // action=watch has no way to rely on user preferences (T262912), so we do it manually. - // The watchdefault pref appears to reliably return '1' (string), - // but that's not consistent among prefs so might as well be "correct" - var watch = watchPref !== 'no' && (watchPref !== 'default' || !!parseInt(mw.user.options.get('watchdefault'), 10)); - if (watch) { - var watch_query = { - action: 'watch', - titles: mw.config.get('wgPageName'), - token: mw.user.tokens.get('watchToken') - }; - // Only add the expiry if page is unwatched or already temporarily watched - if (Twinkle.protect.watched !== true && watchPref !== 'default' && watchPref !== 'yes') { - watch_query.expiry = watchPref; - } - new Morebits.wiki.api('Adding requested page to watchlist', watch_query).post(); - } - }); - } else { - var originalTextLength2 = text2.length; - text2 += '\n' + newtag; - if (text2.length === originalTextLength2) { - var linknode2 = document.createElement('a'); - linknode2.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing RPP')); - linknode2.appendChild(document.createTextNode('How to fix RPP')); - statusElement.error([ 'Could not find relevant heading on WP:RPP. To fix this problem, please see ', linknode2, '.' ]); - return; - } - statusElement.status('Adding new request...'); - rppPage2.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Requesting ' + params.typename + (params.typename === 'pending changes' ? ' on [[:' : ' of [[:') + - Morebits.pageNameNorm + ']].'); - rppPage2.setChangeTags(Twinkle.changeTags); - rppPage2.setPageText(text2); - rppPage2.setCreateOption('recreate'); - rppPage2.save(function() { - // Watch the page being requested - var watchPref = Twinkle.getPref('watchRequestedPages'); - // action=watch has no way to rely on user preferences (T262912), so we do it manually. - // The watchdefault pref appears to reliably return '1' (string), - // but that's not consistent among prefs so might as well be "correct" - var watch = watchPref !== 'no' && (watchPref !== 'default' || !!parseInt(mw.user.options.get('watchdefault'), 10)); - if (watch) { - var watch_query = { - action: 'watch', - titles: mw.config.get('wgPageName'), - token: mw.user.tokens.get('watchToken') - }; - // Only add the expiry if page is unwatched or already temporarily watched - if (Twinkle.protect.watched !== true && watchPref !== 'default' && watchPref !== 'yes') { - watch_query.expiry = watchPref; - } - new Morebits.wiki.api('Adding requested page to watchlist', watch_query).post(); - } - }); - } - }); - } -}; - -Twinkle.addInitCallback(Twinkle.protect, 'protect'); -})(jQuery); - - -// diff --git a/modules/twinklewarn-disabled.js b/modules/twinklewarn-disabled.js deleted file mode 100644 index e7af258ad..000000000 --- a/modules/twinklewarn-disabled.js +++ /dev/null @@ -1,1946 +0,0 @@ -// - - -(function($) { - - -/* - **************************************** - *** twinklewarn.js: Warn module - **************************************** - * Mode of invocation: Tab ("Warn") - * Active on: Any page with relevant user name (userspace, contribs, - * etc.) (not IP ranges), as well as the rollback success page - */ - -Twinkle.warn = function twinklewarn() { - - // Users and IPs but not IP ranges - if (mw.config.exists('wgRelevantUserName') && !Morebits.ip.isRange(mw.config.get('wgRelevantUserName'))) { - Twinkle.addPortletLink(Twinkle.warn.callback, 'Warn', 'tw-warn', 'Warn/notify user'); - if (Twinkle.getPref('autoMenuAfterRollback') && - mw.config.get('wgNamespaceNumber') === 3 && - mw.util.getParamValue('vanarticle') && - !mw.util.getParamValue('friendlywelcome') && - !mw.util.getParamValue('noautowarn')) { - Twinkle.warn.callback(); - } - } - - // Modify URL of talk page on rollback success pages, makes use of a - // custom message box in [[MediaWiki:Rollback-success]] - if (mw.config.get('wgAction') === 'rollback') { - var $vandalTalkLink = $('#mw-rollback-success').find('.mw-usertoollinks a').first(); - if ($vandalTalkLink.length) { - $vandalTalkLink.css('font-weight', 'bold'); - $vandalTalkLink.wrapInner($('').attr('title', 'If appropriate, you can use Twinkle to warn the user about their edits to this page.')); - - // Can't provide vanarticlerevid as only wgCurRevisionId is provided - var extraParam = 'vanarticle=' + mw.util.rawurlencode(Morebits.pageNameNorm); - var href = $vandalTalkLink.attr('href'); - if (href.indexOf('?') === -1) { - $vandalTalkLink.attr('href', href + '?' + extraParam); - } else { - $vandalTalkLink.attr('href', href + '&' + extraParam); - } - } - } -}; - -// Used to close window when switching to ARV in autolevel -Twinkle.warn.dialog = null; - -Twinkle.warn.callback = function twinklewarnCallback() { - if (mw.config.get('wgRelevantUserName') === mw.config.get('wgUserName') && - !confirm('You are about to warn yourself! Are you sure you want to proceed?')) { - return; - } - - var dialog; - Twinkle.warn.dialog = new Morebits.simpleWindow(600, 440); - dialog = Twinkle.warn.dialog; - dialog.setTitle('Warn/notify user'); - dialog.setScriptName('Twinkle'); - dialog.addFooterLink('Choosing a warning level', 'WP:UWUL#Levels'); - dialog.addFooterLink('Warn prefs', 'WP:TW/PREF#warn'); - dialog.addFooterLink('Twinkle help', 'WP:TW/DOC#warn'); - dialog.addFooterLink('Give feedback', 'WT:TW'); - - var form = new Morebits.quickForm(Twinkle.warn.callback.evaluate); - var main_select = form.append({ - type: 'field', - label: 'Choose type of warning/notice to issue', - tooltip: 'First choose a main warning group, then the specific warning to issue.' - }); - - var main_group = main_select.append({ - type: 'select', - name: 'main_group', - tooltip: 'You can customize the default selection in your Twinkle preferences', - event: Twinkle.warn.callback.change_category - }); - - var defaultGroup = parseInt(Twinkle.getPref('defaultWarningGroup'), 10); - main_group.append({ type: 'option', label: 'Auto-select level (1-4)', value: 'autolevel', selected: defaultGroup === 11 }); - main_group.append({ type: 'option', label: '1: General note', value: 'level1', selected: defaultGroup === 1 }); - main_group.append({ type: 'option', label: '2: Caution', value: 'level2', selected: defaultGroup === 2 }); - main_group.append({ type: 'option', label: '3: Warning', value: 'level3', selected: defaultGroup === 3 }); - main_group.append({ type: 'option', label: '4: Final warning', value: 'level4', selected: defaultGroup === 4 }); - main_group.append({ type: 'option', label: '4im: Only warning', value: 'level4im', selected: defaultGroup === 5 }); - if (Twinkle.getPref('combinedSingletMenus')) { - main_group.append({ type: 'option', label: 'Single-issue messages', value: 'singlecombined', selected: defaultGroup === 6 || defaultGroup === 7 }); - } else { - main_group.append({ type: 'option', label: 'Single-issue notices', value: 'singlenotice', selected: defaultGroup === 6 }); - main_group.append({ type: 'option', label: 'Single-issue warnings', value: 'singlewarn', selected: defaultGroup === 7 }); - } - if (Twinkle.getPref('customWarningList').length) { - main_group.append({ type: 'option', label: 'Custom warnings', value: 'custom', selected: defaultGroup === 9 }); - } - main_group.append({ type: 'option', label: 'All warning templates', value: 'kitchensink', selected: defaultGroup === 10 }); - - main_select.append({ type: 'select', name: 'sub_group', event: Twinkle.warn.callback.change_subcategory }); // Will be empty to begin with. - - form.append({ - type: 'input', - name: 'article', - label: 'Linked page', - value: mw.util.getParamValue('vanarticle') || '', - tooltip: 'A page can be linked within the notice, perhaps because it was a revert to said page that dispatched this notice. Leave empty for no page to be linked.' - }); - - form.append({ - type: 'div', - label: '', - style: 'color: red', - id: 'twinkle-warn-warning-messages' - }); - - - var more = form.append({ type: 'field', name: 'reasonGroup', label: 'Warning information' }); - more.append({ type: 'textarea', label: 'Optional message:', name: 'reason', tooltip: 'Perhaps a reason, or that a more detailed notice must be appended' }); - - var previewlink = document.createElement('a'); - $(previewlink).click(function() { - Twinkle.warn.callbacks.preview(result); // |result| is defined below - }); - previewlink.style.cursor = 'pointer'; - previewlink.textContent = 'Preview'; - more.append({ type: 'div', id: 'warningpreview', label: [ previewlink ] }); - more.append({ type: 'div', id: 'twinklewarn-previewbox', style: 'display: none' }); - - more.append({ type: 'submit', label: 'Submit' }); - - var result = form.render(); - dialog.setContent(result); - dialog.display(); - result.main_group.root = result; - result.previewer = new Morebits.wiki.preview($(result).find('div#twinklewarn-previewbox').last()[0]); - - // Potential notices for staleness and missed reverts - var vanrevid = mw.util.getParamValue('vanarticlerevid'); - if (vanrevid) { - var message = ''; - var query = {}; - - // If you tried reverting, check if *you* actually reverted - if (!mw.util.getParamValue('noautowarn') && mw.util.getParamValue('vanarticle')) { // Via fluff link - query = { - action: 'query', - titles: mw.util.getParamValue('vanarticle'), - prop: 'revisions', - rvstartid: vanrevid, - rvlimit: 2, - rvdir: 'newer', - rvprop: 'user', - format: 'json' - }; - - new Morebits.wiki.api('Checking if you successfully reverted the page', query, function(apiobj) { - var rev = apiobj.getResponse().query.pages[0].revisions; - var revertUser = rev && rev[1].user; - if (revertUser && revertUser !== mw.config.get('wgUserName')) { - message += ' Someone else reverted the page and may have already warned the user.'; - $('#twinkle-warn-warning-messages').text('Note:' + message); - } - }).post(); - } - - // Confirm edit wasn't too old for a warning - var checkStale = function(vantimestamp) { - var revDate = new Morebits.date(vantimestamp); - if (vantimestamp && revDate.isValid()) { - if (revDate.add(24, 'hours').isBefore(new Date())) { - message += ' This edit was made more than 24 hours ago so a warning may be stale.'; - $('#twinkle-warn-warning-messages').text('Note:' + message); - } - } - }; - - var vantimestamp = mw.util.getParamValue('vantimestamp'); - // Provided from a fluff module-based revert, no API lookup necessary - if (vantimestamp) { - checkStale(vantimestamp); - } else { - query = { - action: 'query', - prop: 'revisions', - rvprop: 'timestamp', - revids: vanrevid, - format: 'json' - }; - new Morebits.wiki.api('Grabbing the revision timestamps', query, function(apiobj) { - var rev = apiobj.getResponse().query.pages[0].revisions; - vantimestamp = rev && rev[0].timestamp; - checkStale(vantimestamp); - }).post(); - } - } - - - // We must init the first choice (General Note); - var evt = document.createEvent('Event'); - evt.initEvent('change', true, true); - result.main_group.dispatchEvent(evt); -}; - -// This is all the messages that might be dispatched by the code -// Each of the individual templates require the following information: -// label (required): A short description displayed in the dialog -// summary (required): The edit summary used. If an article name is entered, the summary is postfixed with "on [[article]]", and it is always postfixed with "." -// suppressArticleInSummary (optional): Set to true to suppress showing the article name in the edit summary. Useful if the warning relates to attack pages, or some such. -// hideLinkedPage (optional): Set to true to hide the "Linked article" text box. Some warning templates do not have a linked article parameter. -// hideReason (optional): Set to true to hide the "Reason" text box. Some warning templates do not have a reason parameter. -Twinkle.warn.messages = { - levels: { - 'Common warnings': { - 'uw-vandalism': { - level1: { - label: 'Vandalism', - summary: 'General note: Unconstructive editing' - }, - level2: { - label: 'Vandalism', - summary: 'Caution: Unconstructive editing' - }, - level3: { - label: 'Vandalism', - summary: 'Warning: Vandalism' - }, - level4: { - label: 'Vandalism', - summary: 'Final warning: Vandalism' - }, - level4im: { - label: 'Vandalism', - summary: 'Only warning: Vandalism' - } - }, - 'uw-disruptive': { - level1: { - label: 'Disruptive editing', - summary: 'General note: Unconstructive editing' - }, - level2: { - label: 'Disruptive editing', - summary: 'Caution: Unconstructive editing' - }, - level3: { - label: 'Disruptive editing', - summary: 'Warning: Disruptive editing' - } - }, - 'uw-test': { - level1: { - label: 'Editing tests', - summary: 'General note: Editing tests' - }, - level2: { - label: 'Editing tests', - summary: 'Caution: Editing tests' - }, - level3: { - label: 'Editing tests', - summary: 'Warning: Editing tests' - } - }, - 'uw-delete': { - level1: { - label: 'Removal of content, blanking', - summary: 'General note: Removal of content, blanking' - }, - level2: { - label: 'Removal of content, blanking', - summary: 'Caution: Removal of content, blanking' - }, - level3: { - label: 'Removal of content, blanking', - summary: 'Warning: Removal of content, blanking' - }, - level4: { - label: 'Removal of content, blanking', - summary: 'Final warning: Removal of content, blanking' - }, - level4im: { - label: 'Removal of content, blanking', - summary: 'Only warning: Removal of content, blanking' - } - }, - 'uw-generic': { - level4: { - label: 'Generic warning (for template series missing level 4)', - summary: 'Final warning notice' - } - } - }, - 'Behavior in articles': { - 'uw-biog': { - level1: { - label: 'Adding unreferenced controversial information about living persons', - summary: 'General note: Adding unreferenced controversial information about living persons' - }, - level2: { - label: 'Adding unreferenced controversial information about living persons', - summary: 'Caution: Adding unreferenced controversial information about living persons' - }, - level3: { - label: 'Adding unreferenced controversial/defamatory information about living persons', - summary: 'Warning: Adding unreferenced controversial information about living persons' - }, - level4: { - label: 'Adding unreferenced defamatory information about living persons', - summary: 'Final warning: Adding unreferenced controversial information about living persons' - }, - level4im: { - label: 'Adding unreferenced defamatory information about living persons', - summary: 'Only warning: Adding unreferenced controversial information about living persons' - } - }, - 'uw-defamatory': { - level1: { - label: 'Addition of defamatory content', - summary: 'General note: Addition of defamatory content' - }, - level2: { - label: 'Addition of defamatory content', - summary: 'Caution: Addition of defamatory content' - }, - level3: { - label: 'Addition of defamatory content', - summary: 'Warning: Addition of defamatory content' - }, - level4: { - label: 'Addition of defamatory content', - summary: 'Final warning: Addition of defamatory content' - }, - level4im: { - label: 'Addition of defamatory content', - summary: 'Only warning: Addition of defamatory content' - } - }, - 'uw-error': { - level1: { - label: 'Introducing deliberate factual errors', - summary: 'General note: Introducing factual errors' - }, - level2: { - label: 'Introducing deliberate factual errors', - summary: 'Caution: Introducing factual errors' - }, - level3: { - label: 'Introducing deliberate factual errors', - summary: 'Warning: Introducing deliberate factual errors' - }, - level4: { - label: 'Introducing deliberate factual errors', - summary: 'Final warning: Introducing deliberate factual errors' - } - }, - 'uw-fringe': { - level1: { - label: 'Introducing fringe theories', - summary: 'General note: Introducing fringe theories' - }, - level2: { - label: 'Introducing fringe theories', - summary: 'Caution: Introducing fringe theories' - }, - level3: { - label: 'Introducing fringe theories', - summary: 'Warning: Introducing fringe theories' - } - }, - 'uw-genre': { - level1: { - label: 'Frequent or mass changes to genres without consensus or references', - summary: 'General note: Frequent or mass changes to genres without consensus or references' - }, - level2: { - label: 'Frequent or mass changes to genres without consensus or references', - summary: 'Caution: Frequent or mass changes to genres without consensus or references' - }, - level3: { - label: 'Frequent or mass changes to genres without consensus or reference', - summary: 'Warning: Frequent or mass changes to genres without consensus or reference' - }, - level4: { - label: 'Frequent or mass changes to genres without consensus or reference', - summary: 'Final warning: Frequent or mass changes to genres without consensus or reference' - } - }, - 'uw-image': { - level1: { - label: 'Image-related vandalism in articles', - summary: 'General note: Image-related vandalism in articles' - }, - level2: { - label: 'Image-related vandalism in articles', - summary: 'Caution: Image-related vandalism in articles' - }, - level3: { - label: 'Image-related vandalism in articles', - summary: 'Warning: Image-related vandalism in articles' - }, - level4: { - label: 'Image-related vandalism in articles', - summary: 'Final warning: Image-related vandalism in articles' - }, - level4im: { - label: 'Image-related vandalism', - summary: 'Only warning: Image-related vandalism' - } - }, - 'uw-joke': { - level1: { - label: 'Using improper humor in articles', - summary: 'General note: Using improper humor in articles' - }, - level2: { - label: 'Using improper humor in articles', - summary: 'Caution: Using improper humor in articles' - }, - level3: { - label: 'Using improper humor in articles', - summary: 'Warning: Using improper humor in articles' - }, - level4: { - label: 'Using improper humor in articles', - summary: 'Final warning: Using improper humor in articles' - }, - level4im: { - label: 'Using improper humor', - summary: 'Only warning: Using improper humor' - } - }, - 'uw-nor': { - level1: { - label: 'Adding original research', - summary: 'General note: Adding original research' - }, - level2: { - label: 'Adding original research', - summary: 'Caution: Adding original research' - }, - level3: { - label: 'Adding original research', - summary: 'Warning: Adding original research' - }, - level4: { - label: 'Adding original research', - summary: 'Final warning: Adding original research' - } - }, - 'uw-notcensored': { - level1: { - label: 'Censorship of material', - summary: 'General note: Censorship of material' - }, - level2: { - label: 'Censorship of material', - summary: 'Caution: Censorship of material' - }, - level3: { - label: 'Censorship of material', - summary: 'Warning: Censorship of material' - } - }, - 'uw-own': { - level1: { - label: 'Ownership of articles', - summary: 'General note: Ownership of articles' - }, - level2: { - label: 'Ownership of articles', - summary: 'Caution: Ownership of articles' - }, - level3: { - label: 'Ownership of articles', - summary: 'Warning: Ownership of articles' - }, - level4: { - label: 'Ownership of articles', - summary: 'Final warning: Ownership of articles' - }, - level4im: { - label: 'Ownership of articles', - summary: 'Only warning: Ownership of articles' - } - }, - 'uw-subtle': { - level1: { - label: 'Subtle vandalism', - summary: 'General note: Possible unconstructive editing' - }, - level2: { - label: 'Subtle vandalism', - summary: 'Caution: Likely unconstructive editing' - }, - level3: { - label: 'Subtle vandalism', - summary: 'Warning: Subtle vandalism' - }, - level4: { - label: 'Subtle vandalism', - summary: 'Final warning: Subtle vandalism' - } - }, - 'uw-tdel': { - level1: { - label: 'Removal of maintenance templates', - summary: 'General note: Removal of maintenance templates' - }, - level2: { - label: 'Removal of maintenance templates', - summary: 'Caution: Removal of maintenance templates' - }, - level3: { - label: 'Removal of maintenance templates', - summary: 'Warning: Removal of maintenance templates' - }, - level4: { - label: 'Removal of maintenance templates', - summary: 'Final warning: Removal of maintenance templates' - } - }, - 'uw-unsourced': { - level1: { - label: 'Addition of unsourced or improperly cited material', - summary: 'General note: Addition of unsourced or improperly cited material' - }, - level2: { - label: 'Addition of unsourced or improperly cited material', - summary: 'Caution: Addition of unsourced or improperly cited material' - }, - level3: { - label: 'Addition of unsourced or improperly cited material', - summary: 'Warning: Addition of unsourced or improperly cited material' - }, - level4: { - label: 'Addition of unsourced or improperly cited material', - summary: 'Final warning: Addition of unsourced or improperly cited material' - } - } - }, - 'Promotions and spam': { - 'uw-advert': { - level1: { - label: 'Using Wikipedia for advertising or promotion', - summary: 'General note: Using Wikipedia for advertising or promotion' - }, - level2: { - label: 'Using Wikipedia for advertising or promotion', - summary: 'Caution: Using Wikipedia for advertising or promotion' - }, - level3: { - label: 'Using Wikipedia for advertising or promotion', - summary: 'Warning: Using Wikipedia for advertising or promotion' - }, - level4: { - label: 'Using Wikipedia for advertising or promotion', - summary: 'Final warning: Using Wikipedia for advertising or promotion' - }, - level4im: { - label: 'Using Wikipedia for advertising or promotion', - summary: 'Only warning: Using Wikipedia for advertising or promotion' - } - }, - 'uw-npov': { - level1: { - label: 'Not adhering to neutral point of view', - summary: 'General note: Not adhering to neutral point of view' - }, - level2: { - label: 'Not adhering to neutral point of view', - summary: 'Caution: Not adhering to neutral point of view' - }, - level3: { - label: 'Not adhering to neutral point of view', - summary: 'Warning: Not adhering to neutral point of view' - }, - level4: { - label: 'Not adhering to neutral point of view', - summary: 'Final warning: Not adhering to neutral point of view' - } - }, - 'uw-paid': { - level1: { - label: 'Paid editing without disclosure under the Wikimedia Terms of Use', - summary: 'General note: Disclosure requirements for paid editing under the Wikimedia Terms of Use' - }, - level2: { - label: 'Paid editing without disclosure under the Wikimedia Terms of Use', - summary: 'Caution: Disclosure requirements for paid editing under the Wikimedia Terms of Use' - }, - level3: { - label: 'Paid editing without disclosure under the Wikimedia Terms of Use', - summary: 'Warning: Disclosure requirements for paid editing under the Wikimedia Terms of Use' - }, - level4: { - label: 'Paid editing without disclosure under the Wikimedia Terms of Use', - summary: 'Final warning: Disclosure requirements for paid editing under the Wikimedia Terms of Use' - } - }, - 'uw-spam': { - level1: { - label: 'Adding inappropriate external links', - summary: 'General note: Adding inappropriate external links' - }, - level2: { - label: 'Adding spam links', - summary: 'Caution: Adding spam links' - }, - level3: { - label: 'Adding spam links', - summary: 'Warning: Adding spam links' - }, - level4: { - label: 'Adding spam links', - summary: 'Final warning: Adding spam links' - }, - level4im: { - label: 'Adding spam links', - summary: 'Only warning: Adding spam links' - } - } - }, - 'Behavior towards other editors': { - 'uw-agf': { - level1: { - label: 'Not assuming good faith', - summary: 'General note: Not assuming good faith' - }, - level2: { - label: 'Not assuming good faith', - summary: 'Caution: Not assuming good faith' - }, - level3: { - label: 'Not assuming good faith', - summary: 'Warning: Not assuming good faith' - } - }, - 'uw-harass': { - level1: { - label: 'Harassment of other users', - summary: 'General note: Harassment of other users' - }, - level2: { - label: 'Harassment of other users', - summary: 'Caution: Harassment of other users' - }, - level3: { - label: 'Harassment of other users', - summary: 'Warning: Harassment of other users' - }, - level4: { - label: 'Harassment of other users', - summary: 'Final warning: Harassment of other users' - }, - level4im: { - label: 'Harassment of other users', - summary: 'Only warning: Harassment of other users' - } - }, - 'uw-npa': { - level1: { - label: 'Personal attack directed at a specific editor', - summary: 'General note: Personal attack directed at a specific editor' - }, - level2: { - label: 'Personal attack directed at a specific editor', - summary: 'Caution: Personal attack directed at a specific editor' - }, - level3: { - label: 'Personal attack directed at a specific editor', - summary: 'Warning: Personal attack directed at a specific editor' - }, - level4: { - label: 'Personal attack directed at a specific editor', - summary: 'Final warning: Personal attack directed at a specific editor' - }, - level4im: { - label: 'Personal attack directed at a specific editor', - summary: 'Only warning: Personal attack directed at a specific editor' - } - }, - 'uw-tempabuse': { - level1: { - label: 'Improper use of warning or blocking template', - summary: 'General note: Improper use of warning or blocking template' - }, - level2: { - label: 'Improper use of warning or blocking template', - summary: 'Caution: Improper use of warning or blocking template' - } - } - }, - 'Removal of deletion tags': { - 'uw-afd': { - level1: { - label: 'Removing {{afd}} templates', - summary: 'General note: Removing {{afd}} templates' - }, - level2: { - label: 'Removing {{afd}} templates', - summary: 'Caution: Removing {{afd}} templates' - }, - level3: { - label: 'Removing {{afd}} templates', - summary: 'Warning: Removing {{afd}} templates' - }, - level4: { - label: 'Removing {{afd}} templates', - summary: 'Final warning: Removing {{afd}} templates' - } - }, - 'uw-blpprod': { - level1: { - label: 'Removing {{blp prod}} templates', - summary: 'General note: Removing {{blp prod}} templates' - }, - level2: { - label: 'Removing {{blp prod}} templates', - summary: 'Caution: Removing {{blp prod}} templates' - }, - level3: { - label: 'Removing {{blp prod}} templates', - summary: 'Warning: Removing {{blp prod}} templates' - }, - level4: { - label: 'Removing {{blp prod}} templates', - summary: 'Final warning: Removing {{blp prod}} templates' - } - }, - 'uw-idt': { - level1: { - label: 'Removing file deletion tags', - summary: 'General note: Removing file deletion tags' - }, - level2: { - label: 'Removing file deletion tags', - summary: 'Caution: Removing file deletion tags' - }, - level3: { - label: 'Removing file deletion tags', - summary: 'Warning: Removing file deletion tags' - }, - level4: { - label: 'Removing file deletion tags', - summary: 'Final warning: Removing file deletion tags' - } - }, - 'uw-tfd': { - level1: { - label: 'Removing {{tfd}} templates', - summary: 'General note: Removing {{tfd}} templates' - }, - level2: { - label: 'Removing {{tfd}} templates', - summary: 'Caution: Removing {{tfd}} templates' - }, - level3: { - label: 'Removing {{tfd}} templates', - summary: 'Warning: Removing {{tfd}} templates' - }, - level4: { - label: 'Removing {{tfd}} templates', - summary: 'Final warning: Removing {{tfd}} templates' - } - }, - 'uw-speedy': { - level1: { - label: 'Removing speedy deletion tags', - summary: 'General note: Removing speedy deletion tags' - }, - level2: { - label: 'Removing speedy deletion tags', - summary: 'Caution: Removing speedy deletion tags' - }, - level3: { - label: 'Removing speedy deletion tags', - summary: 'Warning: Removing speedy deletion tags' - }, - level4: { - label: 'Removing speedy deletion tags', - summary: 'Final warning: Removing speedy deletion tags' - } - } - }, - 'Other': { - 'uw-attempt': { - level1: { - label: 'Triggering the edit filter', - summary: 'General note: Triggering the edit filter' - }, - level2: { - label: 'Triggering the edit filter', - summary: 'Caution: Triggering the edit filter' - }, - level3: { - label: 'Triggering the edit filter', - summary: 'Warning: Triggering the edit filter' - }, - level4: { - label: 'Triggering the edit filter', - summary: 'Final warning: Triggering the edit filter' - }, - level4im: { - label: 'Triggering the edit filter', - summary: 'Only warning: Triggering the edit filter' - } - }, - 'uw-chat': { - level1: { - label: 'Using talk page as forum', - summary: 'General note: Using talk page as forum' - }, - level2: { - label: 'Using talk page as forum', - summary: 'Caution: Using talk page as forum' - }, - level3: { - label: 'Using talk page as forum', - summary: 'Warning: Using talk page as forum' - }, - level4: { - label: 'Using talk page as forum', - summary: 'Final warning: Using talk page as forum' - } - }, - 'uw-create': { - level1: { - label: 'Creating inappropriate pages', - summary: 'General note: Creating inappropriate pages' - }, - level2: { - label: 'Creating inappropriate pages', - summary: 'Caution: Creating inappropriate pages' - }, - level3: { - label: 'Creating inappropriate pages', - summary: 'Warning: Creating inappropriate pages' - }, - level4: { - label: 'Creating inappropriate pages', - summary: 'Final warning: Creating inappropriate pages' - }, - level4im: { - label: 'Creating inappropriate pages', - summary: 'Only warning: Creating inappropriate pages' - } - }, - 'uw-mos': { - level1: { - label: 'Manual of style', - summary: 'General note: Formatting, date, language, etc (Manual of style)' - }, - level2: { - label: 'Manual of style', - summary: 'Caution: Formatting, date, language, etc (Manual of style)' - }, - level3: { - label: 'Manual of style', - summary: 'Warning: Formatting, date, language, etc (Manual of style)' - }, - level4: { - label: 'Manual of style', - summary: 'Final warning: Formatting, date, language, etc (Manual of style)' - } - }, - 'uw-move': { - level1: { - label: 'Page moves against naming conventions or consensus', - summary: 'General note: Page moves against naming conventions or consensus' - }, - level2: { - label: 'Page moves against naming conventions or consensus', - summary: 'Caution: Page moves against naming conventions or consensus' - }, - level3: { - label: 'Page moves against naming conventions or consensus', - summary: 'Warning: Page moves against naming conventions or consensus' - }, - level4: { - label: 'Page moves against naming conventions or consensus', - summary: 'Final warning: Page moves against naming conventions or consensus' - }, - level4im: { - label: 'Page moves against naming conventions or consensus', - summary: 'Only warning: Page moves against naming conventions or consensus' - } - }, - 'uw-tpv': { - level1: { - label: "Refactoring others' talk page comments", - summary: "General note: Refactoring others' talk page comments" - }, - level2: { - label: "Refactoring others' talk page comments", - summary: "Caution: Refactoring others' talk page comments" - }, - level3: { - label: "Refactoring others' talk page comments", - summary: "Warning: Refactoring others' talk page comments" - }, - level4: { - label: "Refactoring others' talk page comments", - summary: "Final warning: Refactoring others' talk page comments" - }, - level4im: { - label: "Refactoring others' talk page comments", - summary: "Only warning: Refactoring others' talk page comments" - } - }, - 'uw-upload': { - level1: { - label: 'Uploading unencyclopedic images', - summary: 'General note: Uploading unencyclopedic images' - }, - level2: { - label: 'Uploading unencyclopedic images', - summary: 'Caution: Uploading unencyclopedic images' - }, - level3: { - label: 'Uploading unencyclopedic images', - summary: 'Warning: Uploading unencyclopedic images' - }, - level4: { - label: 'Uploading unencyclopedic images', - summary: 'Final warning: Uploading unencyclopedic images' - }, - level4im: { - label: 'Uploading unencyclopedic images', - summary: 'Only warning: Uploading unencyclopedic images' - } - } - } - }, - - singlenotice: { - 'uw-agf-sock': { - label: 'Use of multiple accounts (assuming good faith)', - summary: 'Notice: Using multiple accounts' - }, - 'uw-aiv': { - label: 'Bad AIV report', - summary: 'Notice: Bad AIV report' - }, - 'uw-autobiography': { - label: 'Creating autobiographies', - summary: 'Notice: Creating autobiographies' - }, - 'uw-badcat': { - label: 'Adding incorrect categories', - summary: 'Notice: Adding incorrect categories' - }, - 'uw-badlistentry': { - label: 'Adding inappropriate entries to lists', - summary: 'Notice: Adding inappropriate entries to lists' - }, - 'uw-bite': { - label: '"Biting" newcomers', - summary: 'Notice: "Biting" newcomers', - suppressArticleInSummary: true // non-standard (user name, not article), and not necessary - }, - 'uw-coi': { - label: 'Conflict of interest', - summary: 'Notice: Conflict of interest', - heading: 'Managing a conflict of interest' - }, - 'uw-controversial': { - label: 'Introducing controversial material', - summary: 'Notice: Introducing controversial material' - }, - 'uw-copying': { - label: 'Copying text to another page', - summary: 'Notice: Copying text to another page' - }, - 'uw-crystal': { - label: 'Adding speculative or unconfirmed information', - summary: 'Notice: Adding speculative or unconfirmed information' - }, - 'uw-c&pmove': { - label: 'Cut and paste moves', - summary: 'Notice: Cut and paste moves' - }, - 'uw-dab': { - label: 'Incorrect edit to a disambiguation page', - summary: 'Notice: Incorrect edit to a disambiguation page' - }, - 'uw-date': { - label: 'Unnecessarily changing date formats', - summary: 'Notice: Unnecessarily changing date formats' - }, - 'uw-deadlink': { - label: 'Removing proper sources containing dead links', - summary: 'Notice: Removing proper sources containing dead links' - }, - 'uw-displaytitle': { - label: 'Incorrect use of DISPLAYTITLE', - summary: 'Notice: Incorrect use of DISPLAYTITLE' - }, - 'uw-draftfirst': { - label: 'User should draft in userspace without the risk of speedy deletion', - summary: 'Notice: Consider drafting your article in [[Help:Userspace draft|userspace]]' - }, - 'uw-editsummary': { - label: 'New user not using edit summary', - summary: 'Notice: Not using edit summary' - }, - 'uw-editsummary2': { - label: 'Experienced user not using edit summary', - summary: 'Notice: Not using edit summary', - hideLinkedPage: true, - hideReason: true - }, - 'uw-elinbody': { - label: 'Adding external links to the body of an article', - summary: 'Notice: Keep external links to External links sections at the bottom of an article' - }, - 'uw-english': { - label: 'Not communicating in English', - summary: 'Notice: Not communicating in English' - }, - 'uw-hasty': { - label: 'Hasty addition of speedy deletion tags', - summary: 'Notice: Allow creators time to improve their articles before tagging them for deletion' - }, - 'uw-islamhon': { - label: 'Use of Islamic honorifics', - summary: 'Notice: Use of Islamic honorifics' - }, - 'uw-italicize': { - label: 'Italicize books, films, albums, magazines, TV series, etc within articles', - summary: 'Notice: Italicize books, films, albums, magazines, TV series, etc within articles' - }, - 'uw-lang': { - label: 'Unnecessarily changing between British and American English', - summary: 'Notice: Unnecessarily changing between British and American English', - heading: 'National varieties of English' - }, - 'uw-linking': { - label: 'Excessive addition of redlinks or repeated blue links', - summary: 'Notice: Excessive addition of redlinks or repeated blue links' - }, - 'uw-minor': { - label: 'Incorrect use of minor edits check box', - summary: 'Notice: Incorrect use of minor edits check box' - }, - 'uw-notenglish': { - label: 'Creating non-English articles', - summary: 'Notice: Creating non-English articles' - }, - 'uw-notenglishedit': { - label: 'Adding non-English content to articles', - summary: 'Notice: Adding non-English content to articles' - }, - 'uw-notvote': { - label: 'We use consensus, not voting', - summary: 'Notice: We use consensus, not voting' - }, - 'uw-plagiarism': { - label: 'Copying from public domain sources without attribution', - summary: 'Notice: Copying from public domain sources without attribution' - }, - 'uw-preview': { - label: 'Use preview button to avoid mistakes', - summary: 'Notice: Use preview button to avoid mistakes' - }, - 'uw-redlink': { - label: 'Indiscriminate removal of redlinks', - summary: 'Notice: Be careful when removing redlinks' - }, - 'uw-selfrevert': { - label: 'Self-reverted editing tests', - summary: 'Notice: Self-reverted editing tests' - }, - 'uw-socialnetwork': { - label: 'Wikipedia is not a social network', - summary: 'Notice: Wikipedia is not a social network' - }, - 'uw-sofixit': { - label: 'Be bold and fix things yourself', - summary: 'Notice: You can be bold and fix things yourself' - }, - 'uw-spoiler': { - label: 'Adding spoiler alerts or removing spoilers from appropriate sections', - summary: "Notice: Don't delete or flag potential 'spoilers' in Wikipedia articles" - }, - 'uw-talkinarticle': { - label: 'Talk in article', - summary: 'Notice: Talk in article' - }, - 'uw-tilde': { - label: 'Not signing posts', - summary: 'Notice: Not signing posts' - }, - 'uw-toppost': { - label: 'Posting at the top of talk pages', - summary: 'Notice: Posting at the top of talk pages' - }, - 'uw-translation': { - label: 'Adding translations without proper attribution', - summary: 'Notice: Attribution required when translating articles' - }, - 'uw-unattribcc': { - label: 'Copying from compatibly-licensed sources without attribution', - summary: 'Notice: Copying from compatibly-licensed sources without attribution' - }, - 'uw-userspace draft finish': { - label: 'Stale userspace draft', - summary: 'Notice: Stale userspace draft' - }, - 'uw-vgscope': { - label: 'Adding video game walkthroughs, cheats or instructions', - summary: 'Notice: Adding video game walkthroughs, cheats or instructions' - }, - 'uw-warn': { - label: 'Place user warning templates when reverting vandalism', - summary: 'Notice: You can use user warning templates when reverting vandalism' - }, - 'uw-wrongsummary': { - label: 'Using inaccurate or inappropriate edit summaries', - summary: 'Notice: Using inaccurate or inappropriate edit summaries' - } - }, - - singlewarn: { - 'uw-3rr': { - label: 'Potential three-revert rule violation; see also uw-ew', - summary: 'Warning: Three-revert rule' - }, - 'uw-affiliate': { - label: 'Affiliate marketing', - summary: 'Warning: Affiliate marketing' - }, - 'uw-attack': { - label: 'Creating attack pages', - summary: 'Warning: Creating attack pages', - suppressArticleInSummary: true - }, - 'uw-botun': { - label: 'Bot username', - summary: 'Warning: Bot username' - }, - 'uw-canvass': { - label: 'Canvassing', - summary: 'Warning: Canvassing' - }, - 'uw-copyright': { - label: 'Copyright violation', - summary: 'Warning: Copyright violation' - }, - 'uw-copyright-link': { - label: 'Linking to copyrighted works violation', - summary: 'Warning: Linking to copyrighted works violation' - }, - 'uw-copyright-new': { - label: 'Copyright violation (with explanation for new users)', - summary: 'Notice: Avoiding copyright problems', - heading: 'Wikipedia and copyright' - }, - 'uw-copyright-remove': { - label: 'Removing {{copyvio}} template from articles', - summary: 'Warning: Removing {{copyvio}} templates' - }, - 'uw-efsummary': { - label: 'Edit summary triggering the edit filter', - summary: 'Warning: Edit summary triggering the edit filter' - }, - 'uw-ew': { - label: 'Edit warring (stronger wording)', - summary: 'Warning: Edit warring' - }, - 'uw-ewsoft': { - label: 'Edit warring (softer wording for newcomers)', - summary: 'Warning: Edit warring' - }, - 'uw-hijacking': { - label: 'Hijacking articles', - summary: 'Warning: Hijacking articles' - }, - 'uw-hoax': { - label: 'Creating hoaxes', - summary: 'Warning: Creating hoaxes' - }, - 'uw-legal': { - label: 'Making legal threats', - summary: 'Warning: Making legal threats' - }, - 'uw-login': { - label: 'Editing while logged out', - summary: 'Warning: Editing while logged out' - }, - 'uw-multipleIPs': { - label: 'Usage of multiple IPs', - summary: 'Warning: Vandalism using multiple IPs' - }, - 'uw-paraphrase': { - label: 'Close paraphrasing', - summary: 'Warning: Close paraphrasing' - }, - 'uw-pinfo': { - label: 'Personal info (outing)', - summary: 'Warning: Personal info' - }, - 'uw-salt': { - label: 'Recreating salted articles under a different title', - summary: 'Notice: Recreating creation-protected articles under a different title' - }, - 'uw-socksuspect': { - label: 'Sockpuppetry', - summary: 'Warning: You are a suspected [[WP:SOCK|sockpuppet]]' // of User:... - }, - 'uw-upv': { - label: 'Userpage vandalism', - summary: 'Warning: Userpage vandalism' - }, - 'uw-username': { - label: 'Username is against policy', - summary: 'Warning: Your username might be against policy', - suppressArticleInSummary: true // not relevant for this template - }, - 'uw-coi-username': { - label: 'Username is against policy, and conflict of interest', - summary: 'Warning: Username and conflict of interest', - heading: 'Your username' - }, - 'uw-userpage': { - label: 'Userpage or subpage is against policy', - summary: 'Warning: Userpage or subpage is against policy' - } - } -}; - -/** - * Reads Twinkle.warn.messages and returns a specified template's property (such as label, summary, - * suppressArticleInSummary, hideLinkedPage, or hideReason) - */ -Twinkle.warn.getTemplateProperty = function(templates, templateName, propertyName) { - var result; - var isNumberedTemplate = templateName.match(/(1|2|3|4|4im)$/); - if (isNumberedTemplate) { - var unNumberedTemplateName = templateName.replace(/(?:1|2|3|4|4im)$/, ''); - var level = isNumberedTemplate[0]; - var numberedWarnings = {}; - $.each(templates.levels, function(key, val) { - $.extend(numberedWarnings, val); - }); - $.each(numberedWarnings, function(key) { - if (key === unNumberedTemplateName) { - result = numberedWarnings[key]['level' + level][propertyName]; - } - }); - } - - // Non-level templates can also end in a number. So check this for all templates. - var otherWarnings = {}; - $.each(templates, function(key, val) { - if (key !== 'levels') { - $.extend(otherWarnings, val); - } - }); - $.each(otherWarnings, function(key) { - if (key === templateName) { - result = otherWarnings[key][propertyName]; - } - }); - - return result; -}; - -// Used repeatedly below across menu rebuilds -Twinkle.warn.prev_article = null; -Twinkle.warn.prev_reason = null; -Twinkle.warn.talkpageObj = null; - -Twinkle.warn.callback.change_category = function twinklewarnCallbackChangeCategory(e) { - var value = e.target.value; - var sub_group = e.target.root.sub_group; - sub_group.main_group = value; - var old_subvalue = sub_group.value; - var old_subvalue_re; - if (old_subvalue) { - if (value === 'kitchensink') { // Exact match possible in kitchensink menu - old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue)); - } else { - old_subvalue = old_subvalue.replace(/\d*(im)?$/, ''); - old_subvalue_re = new RegExp(mw.util.escapeRegExp(old_subvalue) + '(\\d*(?:im)?)$'); - } - } - - while (sub_group.hasChildNodes()) { - sub_group.removeChild(sub_group.firstChild); - } - - var selected = false; - // worker function to create the combo box entries - var createEntries = function(contents, container, wrapInOptgroup, val) { - val = typeof val !== 'undefined' ? val : value; // IE doesn't support default parameters - // level2->2, singlewarn->''; also used to distinguish the - // scaled levels from singlenotice, singlewarn, and custom - var level = val.replace(/^\D+/g, ''); - // due to an apparent iOS bug, we have to add an option-group to prevent truncation of text - // (search WT:TW archives for "Problem selecting warnings on an iPhone") - if (wrapInOptgroup && $.client.profile().platform === 'iphone') { - var wrapperOptgroup = new Morebits.quickForm.element({ - type: 'optgroup', - label: 'Available templates' - }); - wrapperOptgroup = wrapperOptgroup.render(); - container.appendChild(wrapperOptgroup); - container = wrapperOptgroup; - } - - $.each(contents, function(itemKey, itemProperties) { - // Skip if the current template doesn't have a version for the current level - if (!!level && !itemProperties[val]) { - return; - } - var key = typeof itemKey === 'string' ? itemKey : itemProperties.value; - var template = key + level; - - var elem = new Morebits.quickForm.element({ - type: 'option', - label: '{{' + template + '}}: ' + (level ? itemProperties[val].label : itemProperties.label), - value: template - }); - - // Select item best corresponding to previous selection - if (!selected && old_subvalue && old_subvalue_re.test(template)) { - elem.data.selected = selected = true; - } - var elemRendered = container.appendChild(elem.render()); - $(elemRendered).data('messageData', itemProperties); - }); - }; - var createGroup = function(warnGroup, label, wrapInOptgroup, val) { - wrapInOptgroup = typeof wrapInOptgroup !== 'undefined' ? wrapInOptgroup : true; - var optgroup = new Morebits.quickForm.element({ - type: 'optgroup', - label: label - }); - optgroup = optgroup.render(); - sub_group.appendChild(optgroup); - createEntries(warnGroup, optgroup, wrapInOptgroup, val); - }; - - switch (value) { - case 'singlenotice': - case 'singlewarn': - createEntries(Twinkle.warn.messages[value], sub_group, true); - break; - case 'singlecombined': - var unSortedSinglets = $.extend({}, Twinkle.warn.messages.singlenotice, Twinkle.warn.messages.singlewarn); - var sortedSingletMessages = {}; - Object.keys(unSortedSinglets).sort().forEach(function(key) { - sortedSingletMessages[key] = unSortedSinglets[key]; - }); - createEntries(sortedSingletMessages, sub_group, true); - break; - case 'custom': - createEntries(Twinkle.getPref('customWarningList'), sub_group, true); - break; - case 'kitchensink': - ['level1', 'level2', 'level3', 'level4', 'level4im'].forEach(function(lvl) { - $.each(Twinkle.warn.messages.levels, function(levelGroupLabel, levelGroup) { - createGroup(levelGroup, 'Level ' + lvl.slice(5) + ': ' + levelGroupLabel, true, lvl); - }); - }); - createGroup(Twinkle.warn.messages.singlenotice, 'Single-issue notices'); - createGroup(Twinkle.warn.messages.singlewarn, 'Single-issue warnings'); - createGroup(Twinkle.getPref('customWarningList'), 'Custom warnings'); - break; - case 'level1': - case 'level2': - case 'level3': - case 'level4': - case 'level4im': - // Creates subgroup regardless of whether there is anything to place in it; - // leaves "Removal of deletion tags" empty for 4im - $.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) { - createGroup(groupContents, groupLabel, false); - }); - break; - case 'autolevel': - // Check user page to determine appropriate level - var autolevelProc = function() { - var wikitext = Twinkle.warn.talkpageObj.getPageText(); - // history not needed for autolevel - var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0]; - // Pseudo-params with only what's needed to parse the level i.e. no messageData - var params = { - sub_group: old_subvalue, - article: e.target.root.article.value - }; - var lvl = 'level' + Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[1]; - - // Identical to level1, etc. above but explicitly provides the level - $.each(Twinkle.warn.messages.levels, function(groupLabel, groupContents) { - createGroup(groupContents, groupLabel, false, lvl); - }); - - // Trigger subcategory change, add select menu, etc. - Twinkle.warn.callback.postCategoryCleanup(e); - }; - - - if (Twinkle.warn.talkpageObj) { - autolevelProc(); - } else { - var usertalk_page = new Morebits.wiki.page('User_talk:' + mw.config.get('wgRelevantUserName'), 'Loading previous warnings'); - usertalk_page.setFollowRedirect(true, false); - usertalk_page.load(function(pageobj) { - Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj - autolevelProc(); - }, function() { - // Catch and warn if the talkpage can't load, - // most likely because it's a cross-namespace redirect - // Supersedes the typical $autolevelMessage added in autolevelParseWikitext - var $noTalkPageNode = $('', { - text: 'Unable to load user talk page; it might be a cross-namespace redirect. Autolevel detection will not work.', - id: 'twinkle-warn-autolevel-message', - css: {color: 'red' } - }); - $noTalkPageNode.insertBefore($('#twinkle-warn-warning-messages')); - // If a preview was opened while in a different mode, close it - // Should nullify the need to catch the error in preview callback - e.target.root.previewer.closePreview(); - }); - } - break; - default: - alert('Unknown warning group in twinklewarn'); - break; - } - - // Trigger subcategory change, add select menu, etc. - // Here because of the async load for autolevel - if (value !== 'autolevel') { - // reset any autolevel-specific messages while we're here - $('#twinkle-warn-autolevel-message').remove(); - - Twinkle.warn.callback.postCategoryCleanup(e); - } -}; - -Twinkle.warn.callback.postCategoryCleanup = function twinklewarnCallbackPostCategoryCleanup(e) { - // clear overridden label on article textbox - Morebits.quickForm.setElementTooltipVisibility(e.target.root.article, true); - Morebits.quickForm.resetElementLabel(e.target.root.article); - // Trigger custom label/change on main category change - Twinkle.warn.callback.change_subcategory(e); - - // Use select2 to make the select menu searchable - if (!Twinkle.getPref('oldSelect')) { - $('select[name=sub_group]') - .select2({ - width: '100%', - matcher: Morebits.select2.matchers.optgroupFull, - templateResult: Morebits.select2.highlightSearchMatches, - language: { - searching: Morebits.select2.queryInterceptor - } - }) - .change(Twinkle.warn.callback.change_subcategory); - - $('.select2-selection').keydown(Morebits.select2.autoStart).focus(); - - mw.util.addCSS( - // Increase height - '.select2-container .select2-dropdown .select2-results > .select2-results__options { max-height: 350px; }' + - - // 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; }' - ); - } -}; - -Twinkle.warn.callback.change_subcategory = function twinklewarnCallbackChangeSubcategory(e) { - var selected_main_group = e.target.form.main_group.value; - var selected_template = e.target.form.sub_group.value; - - // If template shouldn't have a linked article, hide the linked article label and text box - var hideLinkedPage = Twinkle.warn.getTemplateProperty(Twinkle.warn.messages, selected_template, 'hideLinkedPage'); - if (hideLinkedPage) { - e.target.form.article.value = ''; - Morebits.quickForm.setElementVisibility(e.target.form.article.parentElement, false); - } else { - Morebits.quickForm.setElementVisibility(e.target.form.article.parentElement, true); - } - - // If template shouldn't have an optional message, hide the optional message label and text box - var hideReason = Twinkle.warn.getTemplateProperty(Twinkle.warn.messages, selected_template, 'hideLinkedPage'); - if (hideReason) { - e.target.form.reason.value = ''; - Morebits.quickForm.setElementVisibility(e.target.form.reason.parentElement, false); - } else { - Morebits.quickForm.setElementVisibility(e.target.form.reason.parentElement, true); - } - - // Tags that don't take a linked article, but something else (often a username). - // The value of each tag is the label next to the input field - var notLinkedArticle = { - 'uw-agf-sock': 'Optional username of other account (without User:) ', - 'uw-bite': "Username of 'bitten' user (without User:) ", - 'uw-socksuspect': 'Username of sock master, if known (without User:) ', - 'uw-username': 'Username violates policy because... ', - 'uw-aiv': 'Optional username that was reported (without User:) ' - }; - - var hasLevel = ['singlenotice', 'singlewarn', 'singlecombined', 'kitchensink'].indexOf(selected_main_group) !== -1; - if (hasLevel) { - if (notLinkedArticle[selected_template]) { - if (Twinkle.warn.prev_article === null) { - Twinkle.warn.prev_article = e.target.form.article.value; - } - e.target.form.article.notArticle = true; - e.target.form.article.value = ''; - - // change form labels according to the warning selected - Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, false); - Morebits.quickForm.overrideElementLabel(e.target.form.article, notLinkedArticle[selected_template]); - } else if (e.target.form.article.notArticle) { - if (Twinkle.warn.prev_article !== null) { - e.target.form.article.value = Twinkle.warn.prev_article; - Twinkle.warn.prev_article = null; - } - e.target.form.article.notArticle = false; - Morebits.quickForm.setElementTooltipVisibility(e.target.form.article, true); - Morebits.quickForm.resetElementLabel(e.target.form.article); - } - } - - // add big red notice, warning users about how to use {{uw-[coi-]username}} appropriately - $('#tw-warn-red-notice').remove(); - var $redWarning; - if (selected_template === 'uw-username') { - $redWarning = $("
{{uw-username}} should not be used for blatant username policy violations. " + - "Blatant violations should be reported directly to UAA (via Twinkle's ARV tab). " + - '{{uw-username}} should only be used in edge cases in order to engage in discussion with the user.
'); - $redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup)); - } else if (selected_template === 'uw-coi-username') { - $redWarning = $("
{{uw-coi-username}} should not be used for blatant username policy violations. " + - "Blatant violations should be reported directly to UAA (via Twinkle's ARV tab). " + - '{{uw-coi-username}} should only be used in edge cases in order to engage in discussion with the user.
'); - $redWarning.insertAfter(Morebits.quickForm.getElementLabelObject(e.target.form.reasonGroup)); - } -}; - -Twinkle.warn.callbacks = { - getWarningWikitext: function(templateName, article, reason, isCustom) { - var text = '{{subst:' + templateName; - - // add linked article for user warnings - if (article) { - // c&pmove has the source as the first parameter - if (templateName === 'uw-c&pmove') { - text += '|to=' + article; - } else { - text += '|1=' + article; - } - } - if (reason && !isCustom) { - // add extra message - if (templateName === 'uw-csd' || templateName === 'uw-probation' || - templateName === 'uw-userspacenoindex' || templateName === 'uw-userpage') { - text += "|3=''" + reason + "''"; - } else { - text += "|2=''" + reason + "''"; - } - } - text += '}}'; - - if (reason && isCustom) { - // we assume that custom warnings lack a {{{2}}} parameter - text += " ''" + reason + "''"; - } - - return text + ' ~~~~'; - }, - showPreview: function(form, templatename) { - var input = Morebits.quickForm.getInputData(form); - // Provided on autolevel, not otherwise - templatename = templatename || input.sub_group; - var linkedarticle = input.article; - var templatetext; - - templatetext = Twinkle.warn.callbacks.getWarningWikitext(templatename, linkedarticle, - input.reason, input.main_group === 'custom'); - - form.previewer.beginRender(templatetext, 'User_talk:' + mw.config.get('wgRelevantUserName')); // Force wikitext/correct username - }, - // Just a pass-through unless the autolevel option was selected - preview: function(form) { - if (form.main_group.value === 'autolevel') { - // Always get a new, updated talkpage for autolevel processing - var usertalk_page = new Morebits.wiki.page('User_talk:' + mw.config.get('wgRelevantUserName'), 'Loading previous warnings'); - usertalk_page.setFollowRedirect(true, false); - // Will fail silently if the talk page is a cross-ns redirect, - // removal of the preview box handled when loading the menu - usertalk_page.load(function(pageobj) { - Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj - - var wikitext = pageobj.getPageText(); - // history not needed for autolevel - var latest = Twinkle.warn.callbacks.dateProcessing(wikitext)[0]; - var params = { - sub_group: form.sub_group.value, - article: form.article.value, - messageData: $(form.sub_group).find('option[value="' + $(form.sub_group).val() + '"]').data('messageData') - }; - var template = Twinkle.warn.callbacks.autolevelParseWikitext(wikitext, params, latest)[0]; - Twinkle.warn.callbacks.showPreview(form, template); - - // If the templates have diverged, fake a change event - // to reload the menu with the updated pageobj - if (form.sub_group.value !== template) { - var evt = document.createEvent('Event'); - evt.initEvent('change', true, true); - form.main_group.dispatchEvent(evt); - } - }); - } else { - Twinkle.warn.callbacks.showPreview(form); - } - }, - /** - * Used in the main and autolevel loops to determine when to warn - * about excessively recent, stale, or identical warnings. - * @param {string} wikitext The text of a user's talk page, from getPageText() - * @returns {Object[]} - Array of objects: latest contains most recent - * warning and date; history lists all prior warnings - */ - dateProcessing: function(wikitext) { - var history_re = /.*?(\d{1,2}:\d{1,2}, \d{1,2} \w+ \d{4} \(UTC\))/g; - var history = {}; - var latest = { date: new Morebits.date(0), type: '' }; - var current; - - while ((current = history_re.exec(wikitext)) !== null) { - var template = current[1], current_date = new Morebits.date(current[2]); - if (!(template in history) || history[template].isBefore(current_date)) { - history[template] = current_date; - } - if (!latest.date.isAfter(current_date)) { - latest.date = current_date; - latest.type = template; - } - } - return [latest, history]; - }, - /** - * Main loop for deciding what the level should increment to. Most of - * this is really just error catching and updating the subsequent data. - * May produce up to two notices in a twinkle-warn-autolevel-messages div - * - * @param {string} wikitext The text of a user's talk page, from getPageText() (required) - * @param {Object} params Params object: sub_group is the template (required); - * article is the user-provided article (form.article) used to link ARV on recent level4 warnings; - * messageData is only necessary if getting the full template, as it's - * used to ensure a valid template of that level exists - * @param {Object} latest First element of the array returned from - * dateProcessing. Provided here rather than processed within to avoid - * repeated call to dateProcessing - * @param {(Date|Morebits.date)} date Date from which staleness is determined - * @param {Morebits.status} statelem Status element, only used for handling error in final execution - * - * @returns {Array} - Array that contains the full template and just the warning level - */ - autolevelParseWikitext: function(wikitext, params, latest, date, statelem) { - var level; // undefined rather than '' means the isNaN below will return true - if (/\d(?:im)?$/.test(latest.type)) { // level1-4im - level = parseInt(latest.type.replace(/.*(\d)(?:im)?$/, '$1'), 10); - } else if (latest.type) { // Non-numbered warning - // Try to leverage existing categorization of - // warnings, all but one are universally lowercased - var loweredType = /uw-multipleIPs/i.test(latest.type) ? 'uw-multipleIPs' : latest.type.toLowerCase(); - // It would be nice to account for blocks, but in most - // cases the hidden message is terminal, not the sig - if (Twinkle.warn.messages.singlewarn[loweredType]) { - level = 3; - } else { - level = 1; // singlenotice or not found - } - } - - var $autolevelMessage = $('
', {id: 'twinkle-warn-autolevel-message'}); - - if (isNaN(level)) { // No prior warnings found, this is the first - level = 1; - } else if (level > 4 || level < 1) { // Shouldn't happen - var message = 'Unable to parse previous warning level, please manually select a warning level.'; - if (statelem) { - statelem.error(message); - } else { - alert(message); - } - return; - } else { - date = date || new Date(); - var autoTimeout = new Morebits.date(latest.date.getTime()).add(parseInt(Twinkle.getPref('autolevelStaleDays'), 10), 'days'); - if (autoTimeout.isAfter(date)) { - if (level === 4) { - level = 4; - // Basically indicates whether we're in the final Main evaluation or not, - // and thus whether we can continue or need to display the warning and link - if (!statelem) { - var $link = $('', { - href: '#', - text: 'click here to open the ARV tool.', - css: { fontWeight: 'bold' }, - click: function() { - Morebits.wiki.actionCompleted.redirect = null; - Twinkle.warn.dialog.close(); - Twinkle.arv.callback(mw.config.get('wgRelevantUserName')); - $('input[name=page]').val(params.article); // Target page - $('input[value=final]').prop('checked', true); // Vandalism after final - } - }); - var statusNode = $('
', { - text: mw.config.get('wgRelevantUserName') + ' recently received a level 4 warning (' + latest.type + ') so it might be better to report them instead; ', - css: {color: 'red' } - }); - statusNode.append($link[0]); - $autolevelMessage.append(statusNode); - } - } else { // Automatically increase severity - level += 1; - } - } else { // Reset warning level if most-recent warning is too old - level = 1; - } - } - - $autolevelMessage.prepend($('
Will issue a level ' + level + ' template.
')); - // Place after the stale and other-user-reverted (text-only) messages - $('#twinkle-warn-autolevel-message').remove(); // clean slate - $autolevelMessage.insertAfter($('#twinkle-warn-warning-messages')); - - var template = params.sub_group.replace(/(.*)\d$/, '$1'); - // Validate warning level, falling back to the uw-generic series. - // Only a few items are missing a level, and in all but a handful - // of cases, the uw-generic series is explicitly used elsewhere per WP:UTM. - if (params.messageData && !params.messageData['level' + level]) { - template = 'uw-generic'; - } - template += level; - - return [template, level]; - }, - main: function(pageobj) { - var text = pageobj.getPageText(); - var statelem = pageobj.getStatusElement(); - var params = pageobj.getCallbackParameters(); - var messageData = params.messageData; - - // JS somehow didn't get destructured assignment until ES6 so of course IE doesn't support it - var warningHistory = Twinkle.warn.callbacks.dateProcessing(text); - var latest = warningHistory[0]; - var history = warningHistory[1]; - - var now = new Morebits.date(pageobj.getLoadTime()); - - Twinkle.warn.talkpageObj = pageobj; // Update talkpageObj, just in case - if (params.main_group === 'autolevel') { - // [template, level] - var templateAndLevel = Twinkle.warn.callbacks.autolevelParseWikitext(text, params, latest, now, statelem); - - // Only if there's a change from the prior display/load - if (params.sub_group !== templateAndLevel[0] && !confirm('Will issue a {{' + templateAndLevel[0] + '}} template to the user, okay?')) { - statelem.error('aborted per user request'); - return; - } - // Update params now that we've selected a warning - params.sub_group = templateAndLevel[0]; - messageData = params.messageData['level' + templateAndLevel[1]]; - } else if (params.sub_group in history) { - if (new Morebits.date(history[params.sub_group]).add(1, 'day').isAfter(now)) { - if (!confirm('An identical ' + params.sub_group + ' has been issued in the last 24 hours. \nWould you still like to add this warning/notice?')) { - statelem.error('aborted per user request'); - return; - } - } - } - - latest.date.add(1, 'minute'); // after long debate, one minute is max - - if (latest.date.isAfter(now)) { - if (!confirm('A ' + latest.type + ' has been issued in the last minute. \nWould you still like to add this warning/notice?')) { - statelem.error('aborted per user request'); - return; - } - } - - // build the edit summary - // Function to handle generation of summary prefix for custom templates - var customProcess = function(template) { - template = template.split('|')[0]; - var prefix; - switch (template.substr(-1)) { - case '1': - prefix = 'General note'; - break; - case '2': - prefix = 'Caution'; - break; - case '3': - prefix = 'Warning'; - break; - case '4': - prefix = 'Final warning'; - break; - case 'm': - if (template.substr(-3) === '4im') { - prefix = 'Only warning'; - break; - } - // falls through - default: - prefix = 'Notice'; - break; - } - return prefix + ': ' + Morebits.string.toUpperCaseFirstChar(messageData.label); - }; - - var summary; - if (params.main_group === 'custom') { - summary = customProcess(params.sub_group); - } else { - // Normalize kitchensink to the 1-4im style - if (params.main_group === 'kitchensink' && !/^D+$/.test(params.sub_group)) { - var sub = params.sub_group.substr(-1); - if (sub === 'm') { - sub = params.sub_group.substr(-3); - } - // Don't overwrite uw-3rr, technically unnecessary - if (/\d/.test(sub)) { - params.main_group = 'level' + sub; - } - } - // singlet || level1-4im, no need to /^\D+$/.test(params.main_group) - summary = messageData.summary || (messageData[params.main_group] && messageData[params.main_group].summary); - // Not in Twinkle.warn.messages, assume custom template - if (!summary) { - summary = customProcess(params.sub_group); - } - if (messageData.suppressArticleInSummary !== true && params.article) { - if (params.sub_group === 'uw-agf-sock' || - params.sub_group === 'uw-socksuspect' || - params.sub_group === 'uw-aiv') { // these templates require a username - summary += ' of [[:User:' + params.article + ']]'; - } else { - summary += ' on [[:' + params.article + ']]'; - } - } - } - - pageobj.setEditSummary(summary + '.'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('watchWarnings')); - - - // Get actual warning text - var warningText = Twinkle.warn.callbacks.getWarningWikitext(params.sub_group, params.article, - params.reason, params.main_group === 'custom'); - if (Twinkle.getPref('showSharedIPNotice') && mw.util.isIPAddress(mw.config.get('wgTitle'))) { - Morebits.status.info('Info', 'Adding a shared IP notice'); - warningText += '\n{{subst:Shared IP advice}}'; - } - - var sectionExists = false, sectionNumber = 0; - // Only check sections if there are sections or there's a chance we won't create our own - if (!messageData.heading && text.length) { - // Get all sections - var sections = text.match(/^(==*).+\1/gm); - if (sections && sections.length !== 0) { - // Find the index of the section header in question - var dateHeaderRegex = now.monthHeaderRegex(); - sectionNumber = 0; - // Find this month's section among L2 sections, preferring the bottom-most - sectionExists = sections.reverse().some(function(sec, idx) { - return /^(==)[^=].+\1/m.test(sec) && dateHeaderRegex.test(sec) && typeof (sectionNumber = sections.length - 1 - idx) === 'number'; - }); - } - } - - if (sectionExists) { // append to existing section - pageobj.setPageSection(sectionNumber + 1); - pageobj.setAppendText('\n\n' + warningText); - pageobj.append(); - } else { - if (messageData.heading) { // create new section - pageobj.setNewSectionTitle(messageData.heading); - } else { - Morebits.status.info('Info', 'Will create a new talk page section for this month, as none was found'); - pageobj.setNewSectionTitle(now.monthHeader(0)); - } - pageobj.setNewSectionText(warningText); - pageobj.newSection(); - } - } -}; - -Twinkle.warn.callback.evaluate = function twinklewarnCallbackEvaluate(e) { - var userTalkPage = 'User_talk:' + mw.config.get('wgRelevantUserName'); - - // reason, main_group, sub_group, article - var params = Morebits.quickForm.getInputData(e.target); - - // Check that a reason was filled in if uw-username was selected - if (params.sub_group === 'uw-username' && !params.article) { - alert('You must supply a reason for the {{uw-username}} template.'); - return; - } - - // The autolevel option will already know by now if a user talk page - // is a cross-namespace redirect (via !!Twinkle.warn.talkpageObj), so - // technically we could alert an error here, but the user will have - // already ignored the bold red error above. Moreover, they probably - // *don't* want to actually issue a warning, so the error handling - // after the form is submitted is probably preferable - - // Find the selected
').text(choice.text).attr({ - href: mw.util.getUrl('Wikipedia:WikiProject_Deletion_sorting/' + choice.text), - target: '_blank' - }); - } - }); - - mw.util.addCSS( - // Remove black border - '.select2-container--default.select2-container--focus .select2-selection--multiple { border: 1px solid #aaa; }' + - - // 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; }' + - - // Make the tiny cross larger - '.select2-selection__choice__remove { font-size: 130%; }' - ); - break; - - case 'tfd': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Templates for discussion', - name: 'work_area' - }); - - var templateOrModule = mw.config.get('wgPageContentModel') === 'Scribunto' ? 'module' : 'template'; - work_area.append({ - type: 'select', - label: 'Choose type of action wanted:', - name: 'xfdcat', - event: function(e) { - var target = e.target, - tfdtarget = target.form.tfdtarget; - // add/remove extra input box - if (target.value === 'tfm' && !tfdtarget) { - tfdtarget = new Morebits.quickForm.element({ - name: 'tfdtarget', - type: 'input', - label: 'Other ' + templateOrModule + ' to be merged:', - tooltip: 'Required. Should not include the ' + Morebits.string.toUpperCaseFirstChar(templateOrModule) + ': namespace prefix.', - required: true - }); - target.parentNode.appendChild(tfdtarget.render()); - } else { - $(Morebits.quickForm.getElementContainer(tfdtarget)).remove(); - tfdtarget = null; - } - }, - list: [ - { type: 'option', label: 'Deletion', value: 'tfd', selected: true }, - { type: 'option', label: 'Merge', value: 'tfm' } - ] - }); - work_area.append({ - type: 'select', - name: 'templatetype', - label: 'Deletion tag display style:', - tooltip: 'Which type= parameter to pass to the TfD tag template.', - list: templateOrModule === 'module' ? [ - { type: 'option', value: 'module', label: 'Module', selected: true } - ] : [ - { type: 'option', value: 'standard', label: 'Standard', selected: true }, - { type: 'option', value: 'sidebar', label: 'Sidebar/infobox', selected: $('.infobox').length }, - { type: 'option', value: 'inline', label: 'Inline template', selected: $('.mw-parser-output > p .Inline-Template').length }, - { type: 'option', value: 'tiny', label: 'Tiny inline' }, - { type: 'option', value: 'disabled', label: 'Disabled' } - ] - }); - - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Wrap deletion tag with <noinclude> (for substituted templates only)', - value: 'noinclude', - name: 'noinclude', - tooltip: 'Will wrap the deletion tag in <noinclude> tags, so that it won\'t get substituted along with the template.', - disabled: templateOrModule === 'module', - checked: !!$('.box-Subst_only').length // Default to checked if page carries {{subst only}} - } - ] - }); - - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Notify talk pages of affected user scripts', - value: 'devpages', - name: 'devpages', - tooltip: 'A notification will be sent to Twinkle, AWB, and Ultraviolet\'s talk pages if those user scripts are marked as using this template.', - checked: true - } - ] - }); - - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - case 'mfd': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Miscellany for deletion', - name: 'work_area' - }); - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Wrap deletion tag with <noinclude>', - value: 'noinclude', - name: 'noinclude', - tooltip: 'Will wrap the deletion tag in <noinclude> tags, so that it won\'t transclude. Select this option for userboxes.' - } - ] - }); - if ((mw.config.get('wgNamespaceNumber') === 2 /* User: */ || mw.config.get('wgNamespaceNumber') === 3 /* User talk: */) && mw.config.exists('wgRelevantUserName')) { - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Notify owner of userspace (if they are not the page creator)', - value: 'notifyuserspace', - name: 'notifyuserspace', - tooltip: 'If the user in whose userspace this page is located is not the page creator (for example, the page is a rescued article stored as a userspace draft), notify the userspace owner as well.', - checked: true - } - ] - }); - } - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - case 'ffd': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Discussion venues for files', - name: 'work_area' - }); - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - case 'cfd': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Categories for discussion', - name: 'work_area' - }); - var isCategory = mw.config.get('wgNamespaceNumber') === 14; - work_area.append({ - type: 'select', - label: 'Choose type of action wanted:', - name: 'xfdcat', - event: function(e) { - var value = e.target.value, - cfdtarget = e.target.form.cfdtarget, - cfdtarget2 = e.target.form.cfdtarget2; - - // update enabled status - cfdtarget.disabled = value === 'cfd' || value === 'sfd-t'; - - if (isCategory) { - // update label - if (value === 'cfs') { - Morebits.quickForm.setElementLabel(cfdtarget, 'Target categories: '); - } else if (value === 'cfc') { - Morebits.quickForm.setElementLabel(cfdtarget, 'Target article: '); - } else { - Morebits.quickForm.setElementLabel(cfdtarget, 'Target category: '); - } - // add/remove extra input box - if (value === 'cfs') { - if (cfdtarget2) { - cfdtarget2.disabled = false; - $(cfdtarget2).show(); - } else { - cfdtarget2 = document.createElement('input'); - cfdtarget2.setAttribute('name', 'cfdtarget2'); - cfdtarget2.setAttribute('type', 'text'); - cfdtarget2.setAttribute('required', 'true'); - cfdtarget.parentNode.appendChild(cfdtarget2); - } - } else { - $(cfdtarget2).prop('disabled', true); - $(cfdtarget2).hide(); - } - } else { // Update stub template label - Morebits.quickForm.setElementLabel(cfdtarget, 'Target stub template: '); - } - }, - list: isCategory ? [ - { type: 'option', label: 'Deletion', value: 'cfd', selected: true }, - { type: 'option', label: 'Merge', value: 'cfm' }, - { type: 'option', label: 'Renaming', value: 'cfr' }, - { type: 'option', label: 'Split', value: 'cfs' }, - { type: 'option', label: 'Convert into article', value: 'cfc' } - ] : [ - { type: 'option', label: 'Stub Deletion', value: 'sfd-t', selected: true }, - { type: 'option', label: 'Stub Renaming', value: 'sfr-t' } - ] - }); - - work_area.append({ - type: 'input', - name: 'cfdtarget', - label: 'Target category:', // default, changed above - disabled: true, - required: true, // only when enabled - value: '' - }); - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - case 'cfds': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Categories for speedy renaming', - name: 'work_area' - }); - work_area.append({ - type: 'select', - label: 'C2 sub-criterion:', - name: 'xfdcat', - tooltip: 'See WP:CFDS for full explanations.', - list: [ - { type: 'option', label: 'C2A: Typographic and spelling fixes', value: 'C2A', selected: true }, - { type: 'option', label: 'C2B: Naming conventions and disambiguation', value: 'C2B' }, - { type: 'option', label: 'C2C: Consistency with names of similar categories', value: 'C2C' }, - { type: 'option', label: 'C2D: Rename to match article name', value: 'C2D' }, - { type: 'option', label: 'C2E: Author request', value: 'C2E' }, - { type: 'option', label: 'C2F: One eponymous article', value: 'C2F' } - ] - }); - - work_area.append({ - type: 'input', - name: 'cfdstarget', - label: 'New name:', - value: '', - required: true - }); - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - case 'rfd': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Redirects for discussion', - name: 'work_area' - }); - - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Notify target page if possible', - value: 'relatedpage', - name: 'relatedpage', - tooltip: "A notification template will be placed on the talk page of this redirect's target if this is true.", - checked: true - } - ] - }); - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - case 'rm': - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Requested moves', - name: 'work_area' - }); - work_area.append({ - type: 'checkbox', - list: [ - { - label: 'Uncontroversial technical request', - value: 'rmtr', - name: 'rmtr', - tooltip: 'Use this option when you are unable to perform this uncontroversial move yourself because of a technical reason (e.g. a page already exists at the new title, or the page is protected)', - checked: false, - event: function() { - form.newname.required = this.checked; - }, - subgroup: { - type: 'checkbox', - list: [ - { - label: 'Opt out of discussion if the request is contested', - value: 'rmtr-discuss', - name: 'rmtr-discuss', - tooltip: 'Use this option if you prefer to withdraw the request if contested, rather than discuss it. This suppresses the "discuss" link, which may be used to convert your request to a discussion on the talk page.', - checked: false - } - ] - } - } - ] - }); - work_area.append({ - type: 'input', - name: 'newname', - label: 'New title:', - tooltip: 'Required for technical requests. Otherwise, if unsure of the appropriate title, you may leave it blank.' - }); - - appendReasonBox(); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - - default: - work_area = new Morebits.quickForm.element({ - type: 'field', - label: 'Nothing for anything', - name: 'work_area' - }); - work_area = work_area.render(); - old_area.parentNode.replaceChild(work_area, old_area); - break; - } - - // Return to checked state when switching, but no creator notification for CFDS or RM - form.notifycreator.disabled = value === 'cfds' || value === 'rm'; - form.notifycreator.checked = !form.notifycreator.disabled; -}; - - -Twinkle.xfd.callbacks = { - // Requires having the tag text (params.tagText) set ahead of time - autoEditRequest: function(pageobj, params) { - var talkName = new mw.Title(pageobj.getPageName()).getTalkPage().toText(); - if (talkName === pageobj.getPageName()) { - pageobj.getStatusElement().error('Page protected and nowhere to add an edit request, aborting'); - } else { - pageobj.getStatusElement().warn('Page protected, requesting edit'); - - var editRequest = '{{subst:Xfd edit protected|page=' + pageobj.getPageName() + - '|discussion=' + params.discussionpage + (params.venue === 'rfd' ? '|rfd=yes' : '') + - '|tag=' + params.tagText + '\u003C/nowiki>}}'; // U+003C: < - - var talk_page = new Morebits.wiki.page(talkName, 'Automatically posting edit request on talk page'); - talk_page.setNewSectionTitle('Edit request to complete ' + utils.toTLACase(params.venue) + ' nomination'); - talk_page.setNewSectionText(editRequest); - talk_page.setCreateOption('recreate'); - talk_page.setWatchlist(Twinkle.getPref('xfdWatchPage')); - talk_page.setFollowRedirect(true); // should never be needed, but if the article is moved, we would want to follow the redirect - talk_page.setChangeTags(Twinkle.changeTags); - talk_page.setCallbackParameters(params); - talk_page.newSection(null, function() { - talk_page.getStatusElement().warn('Unable to add edit request, the talk page may be protected'); - }); - } - }, - getDiscussionWikitext: function(venue, params) { - if (venue === 'cfds') { // CfD/S takes a completely different style - return '* [[:' + Morebits.pageNameNorm + ']] to [[:' + params.cfdstarget + ']]\u00A0\u2013 ' + - params.xfdcat + (params.reason ? ': ' + Morebits.string.formatReasonText(params.reason) : '.') + ' ~~~~'; - // U+00A0 NO-BREAK SPACE; U+2013 EN RULE - } - if (venue === 'rm') { - // even if invoked from talk page, propose the subject page for move - var pageName = new mw.Title(Morebits.pageNameNorm).getSubjectPage().toText(); - var rmtrDiscuss = params['rmtr-discuss'] ? '|discuss=no' : ''; - var rmtr = '{{subst:RMassist|1=' + pageName + '|2=' + params.newname + rmtrDiscuss + '|reason=' + params.reason + '}}'; - var requestedMove = '{{subst:Requested move|current1=' + pageName + '|new1=' + params.newname + '|reason=' + params.reason + '}}'; - return params.rmtr ? rmtr : requestedMove; - } - - var text = '{{subst:' + venue + '2'; - var reasonKey = venue === 'ffd' ? 'Reason' : 'text'; - // Add a reason unconditionally, so that at least a signature is added - text += '|' + reasonKey + '=' + Morebits.string.formatReasonText(params.reason, true); - - if (venue === 'afd' || venue === 'mfd') { - text += '|pg=' + Morebits.pageNameNorm; - if (venue === 'afd') { - text += '|cat=' + params.xfdcat; - } - } else if (venue === 'rfd') { - text += '|redirect=' + Morebits.pageNameNorm; - } else { - text += '|1=' + mw.config.get('wgTitle'); - if (mw.config.get('wgPageContentModel') === 'Scribunto') { - text += '|module=Module:'; - } - } - - if (params.rfdtarget) { - text += '|target=' + params.rfdtarget + (params.section ? '#' + params.section : ''); - } else if (params.tfdtarget) { - text += '|2=' + params.tfdtarget; - } else if (params.cfdtarget) { - text += '|2=' + params.cfdtarget; - if (params.cfdtarget2) { - text += '|3=' + params.cfdtarget2; - } - } else if (params.uploader) { - text += '|Uploader=' + params.uploader; - } - - text += '}}'; - - // Don't delsort if delsortCats is undefined (TFD, FFD, etc.) - // Don't delsort if delsortCats is an empty array (AFD where user chose no categories) - if (Array.isArray(params.delsortCats) && params.delsortCats.length) { - text += '\n{{subst:Deletion sorting/multi|' + params.delsortCats.join('|') + '|sig=~~~~}}'; - } - - return text; - }, - showPreview: function(form, venue, params) { - var templatetext = Twinkle.xfd.callbacks.getDiscussionWikitext(venue, params); - if (venue === 'rm') { // RM templates are sensitive to page title - form.previewer.beginRender(templatetext, params.rmtr ? 'Wikipedia:Requested moves/Technical requests' : new mw.Title(Morebits.pageNameNorm).getTalkPage().toText()); - } else { - form.previewer.beginRender(templatetext, 'WP:TW'); // Force wikitext - } - }, - preview: function(form) { - // venue, reason, xfdcat, tfdtarget, cfdtarget, cfdtarget2, cfdstarget, delsortCats, newname - var params = Morebits.quickForm.getInputData(form); - - var venue = params.venue; - - // Remove CfD or TfD namespace prefixes if given - if (params.tfdtarget) { - params.tfdtarget = utils.stripNs(params.tfdtarget); - } else if (params.cfdtarget) { - params.cfdtarget = utils.stripNs(params.cfdtarget); - if (params.cfdtarget2) { - params.cfdtarget2 = utils.stripNs(params.cfdtarget2); - } - } else if (params.cfdstarget) { // Add namespace if not given (CFDS) - params.cfdstarget = utils.addNs(params.cfdstarget, 14); - } - - if (venue === 'ffd') { - // Fetch the uploader - var page = new Morebits.wiki.page(mw.config.get('wgPageName')); - page.lookupCreation(function() { - params.uploader = page.getCreator(); - Twinkle.xfd.callbacks.showPreview(form, venue, params); - }); - } else if (venue === 'rfd') { // Find the target - Twinkle.xfd.callbacks.rfd.findTarget(params, function(params) { - Twinkle.xfd.callbacks.showPreview(form, venue, params); - }); - } else if (venue === 'cfd') { // Swap in CfD subactions - Twinkle.xfd.callbacks.showPreview(form, params.xfdcat, params); - } else { - Twinkle.xfd.callbacks.showPreview(form, venue, params); - } - }, - /** - * Unified handler for sending {{Xfd notice}} notifications - * Also handles userspace logging - * @param {object} params - * @param {string} notifyTarget The user or page being notified - * @param {boolean} [noLog=false] Whether to skip logging to userspace - * XfD log, especially useful in cases in where multiple notifications - * may be sent out (MfD, TfM, RfD) - * @param {string} [actionName] Alternative description of the action - * being undertaken. Required if not notifying a user talk page. - */ - notifyUser: function(params, notifyTarget, noLog, actionName) { - // Ensure items with User talk or no namespace prefix both end - // up at user talkspace as expected, but retain the - // prefix-less username for addToLog - notifyTarget = mw.Title.newFromText(notifyTarget, 3); - var targetNS = notifyTarget.getNamespaceId(); - var usernameOrTarget = notifyTarget.getRelativeText(3); - notifyTarget = notifyTarget.toText(); - if (targetNS === 3) { - // Disallow warning yourself - if (usernameOrTarget === mw.config.get('wgUserName')) { - Morebits.status.warn('You (' + usernameOrTarget + ') created this page; skipping user notification'); - - // if we thought we would notify someone but didn't, - // then jump to logging. - Twinkle.xfd.callbacks.addToLog(params, null); - return; - } - // Default is notifying the initial contributor, but MfD also - // notifies userspace page owner - actionName = actionName || 'Notifying initial contributor (' + usernameOrTarget + ')'; - } - - var notifytext = '\n{{subst:' + params.venue + ' notice'; - // Venue-specific parameters - switch (params.venue) { - case 'afd': - case 'mfd': - notifytext += params.numbering !== '' ? '|order= ' + params.numbering : ''; - break; - case 'tfd': - if (params.xfdcat === 'tfm') { - notifytext = '\n{{subst:Tfm notice|2=' + params.tfdtarget; - } - break; - case 'cfd': - notifytext += '|action=' + params.action + (mw.config.get('wgNamespaceNumber') === 10 ? '|stub=yes' : ''); - break; - default: // ffd, rfd - break; - } - notifytext += '|1=' + Morebits.pageNameNorm + '}} ~~~~'; - - // Link to the venue; object used here rather than repetitive items in switch - var venueNames = { - afd: 'Articles for deletion', - tfd: 'Templates for discussion', - mfd: 'Miscellany for deletion', - cfd: 'Categories for discussion', - ffd: 'Files for discussion', - rfd: 'Redirects for discussion' - }; - var editSummary = 'Notification: [[' + params.discussionpage + '|listing]] of [[:' + - Morebits.pageNameNorm + ']] at [[WP:' + venueNames[params.venue] + ']].'; - - var usertalkpage = new Morebits.wiki.page(notifyTarget, actionName); - usertalkpage.setAppendText(notifytext); - usertalkpage.setEditSummary(editSummary); - usertalkpage.setChangeTags(Twinkle.changeTags); - usertalkpage.setCreateOption('recreate'); - // Different pref for RfD target notifications - if (params.venue === 'rfd' && targetNS !== 3) { - usertalkpage.setWatchlist(Twinkle.getPref('xfdWatchRelated')); - } else { - usertalkpage.setWatchlist(Twinkle.getPref('xfdWatchUser')); - } - usertalkpage.setFollowRedirect(true, false); - - if (noLog) { - usertalkpage.append(); - } else { - usertalkpage.append(function onNotifySuccess() { - // Don't treat RfD target or MfD userspace owner as initialContrib in log - if (!params.notifycreator) { - notifyTarget = null; - } - // add this nomination to the user's userspace log - Twinkle.xfd.callbacks.addToLog(params, usernameOrTarget); - }, function onNotifyError() { - // if user could not be notified, log nomination without mentioning that notification was sent - Twinkle.xfd.callbacks.addToLog(params, null); - }); - } - }, - addToLog: function(params, initialContrib) { - if (!Twinkle.getPref('logXfdNominations') || Twinkle.getPref('noLogOnXfdNomination').indexOf(params.venue) !== -1) { - return; - } - - var usl = new Morebits.userspaceLogger(Twinkle.getPref('xfdLogPageName'));// , 'Adding entry to userspace log'); - - usl.initialText = - "This is a log of all [[WP:XFD|deletion discussion]] nominations made by this user using [[WP:TW|Twinkle]]'s XfD module.\n\n" + - 'If you no longer wish to keep this log, you can turn it off using the [[Wikipedia:Twinkle/Preferences|preferences panel]], and ' + - 'nominate this page for speedy deletion under [[WP:CSD#U1|CSD U1]].' + - (Morebits.userIsSysop ? '\n\nThis log does not track XfD-related deletions made using Twinkle.' : ''); - - var editsummary; - if (params.discussionpage) { - editsummary = 'Logging [[' + params.discussionpage + '|' + utils.toTLACase(params.venue) + ' nomination]] of [[:' + Morebits.pageNameNorm + ']].'; - } else { - editsummary = 'Logging ' + utils.toTLACase(params.venue) + ' nomination of [[:' + Morebits.pageNameNorm + ']].'; - } - // If a logged file is deleted but exists on commons, the wikilink will be blue, so provide a link to the log - var fileLogLink = mw.config.get('wgNamespaceNumber') === 6 ? ' ([{{fullurl:Special:Log|page=' + mw.util.wikiUrlencode(mw.config.get('wgPageName')) + '}} log])' : ''; - // CFD/S and RM don't have canonical links - var nominatedLink = params.discussionpage ? '[[' + params.discussionpage + '|nominated]]' : 'nominated'; - - var appendText = '# [[:' + Morebits.pageNameNorm + ']]:' + fileLogLink + ' ' + nominatedLink + ' at [[WP:' + params.venue.toUpperCase() + '|' + utils.toTLACase(params.venue) + ']]'; - - switch (params.venue) { - case 'tfd': - if (params.xfdcat === 'tfm') { - appendText += ' (merge)'; - if (params.tfdtarget) { - var contentModel = mw.config.get('wgPageContentModel') === 'Scribunto' ? 'Module:' : 'Template:'; - appendText += '; Other ' + contentModel.toLowerCase() + ' [['; - if (!new RegExp('^:?' + Morebits.namespaceRegex([10, 828]) + ':', 'i').test(params.tfdtarget)) { - appendText += contentModel; - } - appendText += params.tfdtarget + ']]'; - } - } - break; - case 'mfd': - if (params.notifyuserspace && params.userspaceOwner && params.userspaceOwner !== initialContrib) { - appendText += '; notified {{user|1=' + params.userspaceOwner + '}}'; - } - break; - case 'cfd': - appendText += ' (' + utils.toTLACase(params.xfdcat) + ')'; - if (params.cfdtarget) { - var categoryOrTemplate = params.xfdcat.charAt(0) === 's' ? 'Template:' : ':Category:'; - appendText += '; ' + params.action + ' to [[' + categoryOrTemplate + params.cfdtarget + ']]'; - if (params.xfdcat === 'cfs' && params.cfdtarget2) { - appendText += ', [[' + categoryOrTemplate + params.cfdtarget2 + ']]'; - } - } - break; - case 'cfds': - appendText += ' (' + utils.toTLACase(params.xfdcat) + ')'; - // Ensure there's more than just 'Category:' - if (params.cfdstarget && params.cfdstarget.length > 9) { - appendText += '; New name: [[:' + params.cfdstarget + ']]'; - } - break; - case 'rfd': - if (params.rfdtarget) { - appendText += '; Target: [[:' + params.rfdtarget + ']]'; - if (params.relatedpage) { - appendText += ' (notified)'; - } - } - break; - case 'rm': - if (params.rmtr) { - appendText += ' (technical)'; - } - if (params.newname) { - appendText += '; New name: [[:' + params.newname + ']]'; - } - break; - - default: // afd or ffd - break; - } - - if (initialContrib && params.notifycreator) { - appendText += '; notified {{user|1=' + initialContrib + '}}'; - } - appendText += ' ~~~~~'; - if (params.reason) { - appendText += "\n#* '''Reason''': " + Morebits.string.formatReasonForLog(params.reason); - } - - usl.changeTags = Twinkle.changeTags; - usl.log(appendText, editsummary); - }, - - afd: { - main: function(apiobj) { - var response = apiobj.getResponse(); - var titles = response.query.allpages; - - // There has been no earlier entries with this prefix, just go on. - if (titles.length <= 0) { - apiobj.params.numbering = apiobj.params.number = ''; - } else { - var number = 0; - for (var i = 0; i < titles.length; ++i) { - var title = titles[i].title; - - // First, simple test, is there an instance with this exact name? - if (title === 'Wikipedia:Articles for deletion/' + Morebits.pageNameNorm) { - number = Math.max(number, 1); - continue; - } - - var order_re = new RegExp('^' + - Morebits.string.escapeRegExp('Wikipedia:Articles for deletion/' + Morebits.pageNameNorm) + - '\\s*\\(\\s*(\\d+)(?:(?:th|nd|rd|st) nom(?:ination)?)?\\s*\\)\\s*$'); - var match = order_re.exec(title); - - // No match; A non-good value - // Or the match is an unrealistically high number. Avoid false positives such as Wikipedia:Articles for deletion/The Basement (2014), by ignoring matches greater than 100 - if (!match || match[1] > 100) { - continue; - } - - // A match, set number to the max of current - number = Math.max(number, Number(match[1])); - } - apiobj.params.number = utils.num2order(parseInt(number, 10) + 1); - apiobj.params.numbering = number > 0 ? ' (' + apiobj.params.number + ' nomination)' : ''; - } - apiobj.params.discussionpage = 'Wikipedia:Articles for deletion/' + Morebits.pageNameNorm + apiobj.params.numbering; - - Morebits.status.info('Next discussion page', '[[' + apiobj.params.discussionpage + ']]'); - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = apiobj.params.discussionpage; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, now redirecting to the discussion page'; - - // Tagging article - var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Adding deletion tag to article'); - wikipedia_page.setFollowRedirect(true); // should never be needed, but if the article is moved, we would want to follow the redirect - wikipedia_page.setChangeTags(Twinkle.changeTags); // Here to apply to triage - wikipedia_page.setCallbackParameters(apiobj.params); - wikipedia_page.load(Twinkle.xfd.callbacks.afd.taggingArticle); - }, - // Tagging needs to happen before everything else: this means we can check if there is an AfD tag already on the page - taggingArticle: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - if (!pageobj.exists()) { - statelem.error("It seems that the page doesn't exist; perhaps it has already been deleted"); - return; - } - - // Check for existing AfD tag, for the benefit of new page patrollers - var textNoAfd = text.replace(/\n+)/, '$1' + added_data); - if (text === old_text) { - var linknode = document.createElement('a'); - linknode.setAttribute('href', mw.util.getUrl('Wikipedia:Twinkle/Fixing AFD') + '?action=purge'); - linknode.appendChild(document.createTextNode('How to fix AFD')); - statelem.error([ 'Could not find the target spot for the discussion. To fix this problem, please see ', linknode, '.' ]); - return; - } - } - - pageobj.setPageText(text); - pageobj.setEditSummary('Adding [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchList')); - pageobj.setCreateOption('recreate'); - pageobj.save(); - }, - delsortListing: function(pageobj) { - var discussionPage = pageobj.getCallbackParameters().discussionPage; - var text = pageobj.getPageText().replace('directly below this line -->', 'directly below this line -->\n{{' + discussionPage + '}}'); - pageobj.setPageText(text); - pageobj.setEditSummary('Listing [[:' + discussionPage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setCreateOption('nocreate'); - pageobj.save(); - } - }, - - - tfd: { - main: function(pageobj) { - var params = pageobj.getCallbackParameters(); - - var date = new Morebits.date(pageobj.getLoadTime()); - params.logpage = 'Wikipedia:Templates for discussion/Log/' + date.format('YYYY MMMM D', 'utc'), - params.discussionpage = params.logpage + '#' + Morebits.pageNameNorm; - // Add log/discussion page params to the already-loaded page object - pageobj.setCallbackParameters(params); - - // Defined here rather than below to reduce duplication - var watchModule, watch_query; - if (params.scribunto) { - var watchPref = Twinkle.getPref('xfdWatchPage'); - // action=watch has no way to rely on user - // preferences (T262912), so we do it manually. - // The watchdefault pref appears to reliably return '1' (string), - // but that's not consistent among prefs so might as well be "correct" - watchModule = watchPref !== 'no' && (watchPref !== 'default' || !!parseInt(mw.user.options.get('watchdefault'), 10)); - if (watchModule) { - watch_query = { - action: 'watch', - titles: [ mw.config.get('wgPageName') ], - token: mw.user.tokens.get('watchToken') - }; - // Only add the expiry if page is unwatched or already temporarily watched - if (pageobj.getWatched() !== true && watchPref !== 'default' && watchPref !== 'yes') { - watch_query.expiry = watchPref; - } - } - } - - // Tagging template(s)/module(s) - if (params.xfdcat === 'tfm') { // Merge - var wikipedia_otherpage; - if (params.scribunto) { - wikipedia_otherpage = new Morebits.wiki.page(params.otherTemplateName + '/doc', 'Tagging other module documentation with merge tag'); - - // Watch tagged module pages as well - if (watchModule) { - watch_query.titles.push(params.otherTemplateName); - new Morebits.wiki.api('Adding Modules to watchlist', watch_query).post(); - } - } else { - wikipedia_otherpage = new Morebits.wiki.page(params.otherTemplateName, 'Tagging other template with merge tag'); - } - // Tag this template/module - Twinkle.xfd.callbacks.tfd.taggingTemplateForMerge(pageobj); - - // Tag other template/module - wikipedia_otherpage.setFollowRedirect(true); - var otherParams = $.extend({}, params); - otherParams.otherTemplateName = Morebits.pageNameNorm; - wikipedia_otherpage.setCallbackParameters(otherParams); - wikipedia_otherpage.load(Twinkle.xfd.callbacks.tfd.taggingTemplateForMerge); - } else { // delete - if (params.scribunto && Twinkle.getPref('xfdWatchPage') !== 'no') { - // Watch tagged module page as well - if (watchModule) { - new Morebits.wiki.api('Adding Module to watchlist', watch_query).post(); - } - } - Twinkle.xfd.callbacks.tfd.taggingTemplate(pageobj); - } - - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = params.logpage; - Morebits.wiki.actionCompleted.notice = "Nomination completed, now redirecting to today's log"; - - // Adding discussion - var wikipedia_page = new Morebits.wiki.page(params.logpage, "Adding discussion to today's log"); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.tfd.todaysList); - - // Notification to first contributors - if (params.notifycreator) { - var involvedpages = []; - var seenusers = []; - involvedpages.push(new Morebits.wiki.page(mw.config.get('wgPageName'))); - if (params.xfdcat === 'tfm') { - if (params.scribunto) { - involvedpages.push(new Morebits.wiki.page('Module:' + params.tfdtarget)); - } else { - involvedpages.push(new Morebits.wiki.page('Template:' + params.tfdtarget)); - } - } - involvedpages.forEach(function(page) { - page.setCallbackParameters(params); - page.lookupCreation(function(innerpage) { - var username = innerpage.getCreator(); - if (seenusers.indexOf(username) === -1) { - seenusers.push(username); - // Only log once on merge nominations, for the initial template - Twinkle.xfd.callbacks.notifyUser(innerpage.getCallbackParameters(), username, - params.xfdcat === 'tfm' && innerpage.getPageName() !== Morebits.pageNameNorm); - } - }); - }); - // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name - } else { - Twinkle.xfd.callbacks.addToLog(params, null); - } - - // Notify developer(s) of script(s) that use(s) the nominated template - if (params.devpages) { - var inCategories = mw.config.get('wgCategories'); - var categoryNotificationPageMap = { - 'Templates used by Twinkle': 'Wikipedia talk:Twinkle', - 'Templates used by AutoWikiBrowser': 'Wikipedia talk:AutoWikiBrowser', - 'Templates used by Ultraviolet': 'Wikipedia talk:Ultraviolet' - }; - $.each(categoryNotificationPageMap, function(category, page) { - if (inCategories.indexOf(category) !== -1) { - Twinkle.xfd.callbacks.notifyUser(params, page, true, 'Notifying ' + page + ' of template nomination'); - } - }); - } - - }, - taggingTemplate: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - params.tagText = '{{subst:template for discussion|help=off' + (params.templatetype !== 'standard' ? '|type=' + params.templatetype : '') + '}}'; - - if (pageobj.getContentModel() === 'sanitized-css') { - params.tagText = '/* ' + params.tagText + ' */'; - } else { - if (params.noinclude) { - params.tagText = '' + params.tagText + ''; - } - params.tagText += params.templatetype === 'standard' || params.templatetype === 'sidebar' || params.templatetype === 'disabled' ? '\n' : ''; // No newline for inline - } - - if (pageobj.canEdit() && ['wikitext', 'sanitized-css'].indexOf(pageobj.getContentModel()) !== -1) { - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary('Nominated for deletion; see [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - if (params.scribunto) { - pageobj.setCreateOption('recreate'); // Module /doc might not exist - } - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - }, - taggingTemplateForMerge: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - params.tagText = '{{subst:tfm|help=off|' + (params.templatetype !== 'standard' ? 'type=' + params.templatetype + '|' : '') + - '1=' + params.otherTemplateName.replace(new RegExp('^' + Morebits.namespaceRegex([10, 828]) + ':'), '') + '}}'; - - if (pageobj.getContentModel() === 'sanitized-css') { - params.tagText = '/* ' + params.tagText + ' */'; - } else { - if (params.noinclude) { - params.tagText = '' + params.tagText + ''; - } - params.tagText += params.templatetype === 'standard' || params.templatetype === 'sidebar' || params.templatetype === 'disabled' ? '\n' : ''; // No newline for inline - } - - if (pageobj.canEdit() && ['wikitext', 'sanitized-css'].indexOf(pageobj.getContentModel()) !== -1) { - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary('Listed for merging with [[:' + params.otherTemplateName + ']]; see [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - if (params.scribunto) { - pageobj.setCreateOption('recreate'); // Module /doc might not exist - } - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - }, - todaysList: function(pageobj) { - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var added_data = Twinkle.xfd.callbacks.getDiscussionWikitext(params.xfdcat, params); - var text; - - // add date header if the log is found to be empty (a bot should do this automatically) - if (!pageobj.exists()) { - text = '{{subst:TfD log}}\n' + added_data; - } else { - var old_text = pageobj.getPageText(); - - text = old_text.replace('-->', '-->\n' + added_data); - if (text === old_text) { - statelem.error('failed to find target spot for the discussion'); - return; - } - } - - pageobj.setPageText(text); - pageobj.setEditSummary('/* ' + Morebits.pageNameNorm + ' */ Adding ' + (params.xfdcat === 'tfd' ? 'deletion nomination' : 'merge listing') + ' of [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('recreate'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - } - }, - - - mfd: { - main: function(apiobj) { - var response = apiobj.getResponse(); - var titles = response.query.allpages; - - // There has been no earlier entries with this prefix, just go on. - if (titles.length <= 0) { - apiobj.params.numbering = apiobj.params.number = ''; - } else { - var number = 0; - for (var i = 0; i < titles.length; ++i) { - var title = titles[i].title; - - // First, simple test, is there an instance with this exact name? - if (title === 'Wikipedia:Miscellany for deletion/' + Morebits.pageNameNorm) { - number = Math.max(number, 1); - continue; - } - - var order_re = new RegExp('^' + - Morebits.string.escapeRegExp('Wikipedia:Miscellany for deletion/' + Morebits.pageNameNorm) + - '\\s*\\(\\s*(\\d+)(?:(?:th|nd|rd|st) nom(?:ination)?)?\\s*\\)\\s*$'); - var match = order_re.exec(title); - - // No match; A non-good value - if (!match) { - continue; - } - - // A match, set number to the max of current - number = Math.max(number, Number(match[1])); - } - apiobj.params.number = utils.num2order(parseInt(number, 10) + 1); - apiobj.params.numbering = number > 0 ? ' (' + apiobj.params.number + ' nomination)' : ''; - } - apiobj.params.discussionpage = 'Wikipedia:Miscellany for deletion/' + Morebits.pageNameNorm + apiobj.params.numbering; - - apiobj.statelem.info('next in order is [[' + apiobj.params.discussionpage + ']]'); - - // Tagging page - var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging page with deletion tag'); - wikipedia_page.setFollowRedirect(true); // should never be needed, but if the page is moved, we would want to follow the redirect - wikipedia_page.setCallbackParameters(apiobj.params); - wikipedia_page.load(Twinkle.xfd.callbacks.mfd.taggingPage); - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = apiobj.params.discussionpage; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, now redirecting to the discussion page'; - - // Discussion page - wikipedia_page = new Morebits.wiki.page(apiobj.params.discussionpage, 'Creating deletion discussion page'); - wikipedia_page.setCallbackParameters(apiobj.params); - wikipedia_page.load(Twinkle.xfd.callbacks.mfd.discussionPage); - - // Today's list - wikipedia_page = new Morebits.wiki.page('Wikipedia:Miscellany for deletion', "Adding discussion to today's list"); - wikipedia_page.setPageSection(2); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(apiobj.params); - wikipedia_page.load(Twinkle.xfd.callbacks.mfd.todaysList); - - // Notification to first contributor and/or notification to owner of userspace - if (apiobj.params.notifycreator || apiobj.params.notifyuserspace) { - var thispage = new Morebits.wiki.page(mw.config.get('wgPageName')); - thispage.setCallbackParameters(apiobj.params); - thispage.lookupCreation(Twinkle.xfd.callbacks.mfd.sendNotifications); - // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name - } else { - Twinkle.xfd.callbacks.addToLog(apiobj.params, null); - } - }, - taggingPage: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - params.tagText = '{{' + (params.number === '' ? 'mfd' : 'mfdx|' + params.number) + '|help=off}}'; - - if (['javascript', 'css', 'sanitized-css'].indexOf(mw.config.get('wgPageContentModel')) !== -1) { - params.tagText = '/* ' + params.tagText + ' */\n'; - } else { - params.tagText += '\n'; - if (params.noinclude) { - params.tagText = '' + params.tagText + ''; - } - } - - if (pageobj.canEdit() && ['wikitext', 'javascript', 'css', 'sanitized-css'].indexOf(pageobj.getContentModel()) !== -1) { - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary('Nominated for deletion; see [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - pageobj.setCreateOption('nocreate'); - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - }, - discussionPage: function(pageobj) { - var params = pageobj.getCallbackParameters(); - - pageobj.setPageText(Twinkle.xfd.callbacks.getDiscussionWikitext('mfd', params)); - pageobj.setEditSummary('Creating deletion discussion page for [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('createonly'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - }, - todaysList: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var date = new Morebits.date(pageobj.getLoadTime()); - var date_header = date.format('===MMMM D, YYYY===\n', 'utc'); - var date_header_regex = new RegExp(date.format('(===[\\s]*MMMM[\\s]+D,[\\s]+YYYY[\\s]*===)', 'utc')); - var added_data = '{{subst:mfd3|pg=' + Morebits.pageNameNorm + params.numbering + '}}'; - - if (date_header_regex.test(text)) { // we have a section already - statelem.info('Found today\'s section, proceeding to add new entry'); - text = text.replace(date_header_regex, '$1\n' + added_data); - } else { // we need to create a new section - statelem.info('No section for today found, proceeding to create one'); - text = text.replace('===', date_header + added_data + '\n\n==='); - } - - pageobj.setPageText(text); - pageobj.setEditSummary('Adding [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchList')); - pageobj.setCreateOption('recreate'); - pageobj.save(); - }, - sendNotifications: function(pageobj) { - var initialContrib = pageobj.getCreator(); - var params = pageobj.getCallbackParameters(); - - // Notify the creator - if (params.notifycreator) { - Twinkle.xfd.callbacks.notifyUser(params, initialContrib); - } - - // Notify the user who owns the subpage if they are not the creator - params.userspaceOwner = mw.config.get('wgRelevantUserName'); - if (params.notifyuserspace) { - if (params.userspaceOwner !== initialContrib) { - // Don't log if notifying creator above, will log then - Twinkle.xfd.callbacks.notifyUser(params, params.userspaceOwner, params.notifycreator, 'Notifying owner of userspace (' + params.userspaceOwner + ')'); - } else if (!params.notifycreator) { - // If we thought we would notify the owner but didn't, - // then we need to log if we didn't notify the creator - // Twinkle.xfd.callbacks.addToLog(params, null); - Twinkle.xfd.callbacks.addToLog(params, initialContrib); - } - } - } - }, - - - ffd: { - taggingImage: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - var date = new Morebits.date(pageobj.getLoadTime()).format('YYYY MMMM D', 'utc'); - params.logpage = 'Wikipedia:Files for discussion/' + date; - params.discussionpage = params.logpage + '#' + Morebits.pageNameNorm; - - params.tagText = '{{ffd|log=' + date + '|help=off}}\n'; - if (pageobj.canEdit()) { - text = text.replace(/\{\{(mtc|(copy |move )?to ?commons|move to wikimedia commons|copy to wikimedia commons)[^}]*\}\}/gi, ''); - - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary('Listed for discussion at [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - pageobj.setCreateOption('recreate'); // it might be possible for a file to exist without a description page - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = params.logpage; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, now redirecting to the discussion page'; - - // Contributor specific edits - var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName')); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.lookupCreation(Twinkle.xfd.callbacks.ffd.main); - }, - main: function(pageobj) { - // this is coming in from lookupCreation...! - var params = pageobj.getCallbackParameters(); - var initialContrib = pageobj.getCreator(); - params.uploader = initialContrib; - - // Adding discussion - var wikipedia_page = new Morebits.wiki.page(params.logpage, "Adding discussion to today's list"); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.ffd.todaysList); - - // Notification to first contributor - if (params.notifycreator) { - Twinkle.xfd.callbacks.notifyUser(params, initialContrib); - // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name - } else { - Twinkle.xfd.callbacks.addToLog(params, null); - } - }, - todaysList: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - // add date header if the log is found to be empty (a bot should do this automatically) - if (!pageobj.exists()) { - text = '{{subst:FfD log}}'; - } - - pageobj.setPageText(text + '\n\n' + Twinkle.xfd.callbacks.getDiscussionWikitext('ffd', params)); - pageobj.setEditSummary('Adding [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('recreate'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - } - }, - - - cfd: { - main: function(pageobj) { - var params = pageobj.getCallbackParameters(); - - var date = new Morebits.date(pageobj.getLoadTime()); - params.logpage = 'Wikipedia:Categories for discussion/Log/' + date.format('YYYY MMMM D', 'utc'); - params.discussionpage = params.logpage + '#' + Morebits.pageNameNorm; - // Add log/discussion page params to the already-loaded page object - pageobj.setCallbackParameters(params); - - // Tagging category - Twinkle.xfd.callbacks.cfd.taggingCategory(pageobj); - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = params.logpage; - Morebits.wiki.actionCompleted.notice = "Nomination completed, now redirecting to today's log"; - - // Adding discussion to list - var wikipedia_page = new Morebits.wiki.page(params.logpage, "Adding discussion to today's list"); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.cfd.todaysList); - - // Notification to first contributor - if (params.notifycreator) { - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName')); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.lookupCreation(function(pageobj) { - Twinkle.xfd.callbacks.notifyUser(pageobj.getCallbackParameters(), pageobj.getCreator()); - }); - // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name - } else { - Twinkle.xfd.callbacks.addToLog(params, null); - } - }, - taggingCategory: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - params.tagText = '{{subst:' + params.xfdcat; - var editsummary = (mw.config.get('wgNamespaceNumber') === 14 ? 'Category' : 'Stub template') + - ' being considered for ' + params.action; - switch (params.xfdcat) { - case 'cfd': - case 'sfd-t': - break; - case 'cfc': - editsummary += ' to an article'; - // falls through - case 'cfm': - case 'cfr': - case 'sfr-t': - params.tagText += '|' + params.cfdtarget; - break; - case 'cfs': - params.tagText += '|' + params.cfdtarget + '|' + params.cfdtarget2; - break; - default: - alert('twinklexfd in taggingCategory(): unknown CFD action'); - break; - } - params.tagText += '}}\n'; - editsummary += '; see [[:' + params.discussionpage + ']].'; - - if (pageobj.canEdit()) { - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary(editsummary); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - pageobj.setCreateOption('recreate'); // since categories can be populated without an actual page at that title - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - }, - todaysList: function(pageobj) { - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var added_data = Twinkle.xfd.callbacks.getDiscussionWikitext(params.xfdcat, params); - var text; - - // add date header if the log is found to be empty (a bot should do this automatically) - if (!pageobj.exists()) { - text = '{{subst:CfD log}}\n' + added_data; - } else { - var old_text = pageobj.getPageText(); - - text = old_text.replace('below this line -->', 'below this line -->\n' + added_data); - if (text === old_text) { - statelem.error('failed to find target spot for the discussion'); - return; - } - } - - pageobj.setPageText(text); - pageobj.setEditSummary('Adding ' + params.action + ' nomination of [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('recreate'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - } - }, - - - cfds: { - taggingCategory: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - - params.tagText = '{{subst:cfr-speedy|1=' + params.cfdstarget.replace(/^:?Category:/, '') + '}}\n'; - params.discussionpage = ''; // CFDS is just a bullet in a bulleted list. There's no section to link to, so we set this to blank. Blank will be recognized by both the generate userspace log code and the generate userspace log edit summary code as "don't wikilink to a section". - if (pageobj.canEdit()) { - pageobj.setPageText(params.tagText + text); - pageobj.setEditSummary('Listed for speedy renaming; see [[WP:CFDS|Categories for discussion/Speedy]].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - pageobj.setCreateOption('recreate'); // since categories can be populated without an actual page at that title - pageobj.save(function() { - // No user notification for CfDS, so just add this nomination to the user's userspace log - Twinkle.xfd.callbacks.addToLog(params, null); - }); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - // No user notification for CfDS, so just add this nomination to the user's userspace log - Twinkle.xfd.callbacks.addToLog(params, null); - } - }, - addToList: function(pageobj) { - var old_text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var text = old_text.replace('BELOW THIS LINE -->', 'BELOW THIS LINE -->\n' + Twinkle.xfd.callbacks.getDiscussionWikitext('cfds', params)); - if (text === old_text) { - statelem.error('failed to find target spot for the discussion'); - return; - } - - pageobj.setPageText(text); - pageobj.setEditSummary('Adding [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('recreate'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - } - }, - - - rfd: { - // This gets called both on submit and preview to determine the redirect target - findTarget: function(params, callback) { - // Used by regular redirects to find the target, but for all redirects, - // avoid relying on the client clock to build the log page - var query = { - action: 'query', - curtimestamp: true, - format: 'json' - }; - if (document.getElementById('softredirect')) { - // For soft redirects, define the target early - // to skip target checks in findTargetCallback - params.rfdtarget = document.getElementById('softredirect').textContent.replace(/^:+/, ''); - } else { - // Find current target of redirect - query.titles = mw.config.get('wgPageName'); - query.redirects = true; - } - var wikipedia_api = new Morebits.wiki.api('Finding target of redirect', query, Twinkle.xfd.callbacks.rfd.findTargetCallback(callback)); - wikipedia_api.params = params; - wikipedia_api.post(); - }, - // This is a closure for the callback from the above API request, which gets the target of the redirect - findTargetCallback: function(callback) { - return function(apiobj) { - var response = apiobj.getResponse(); - apiobj.params.curtimestamp = response.curtimestamp; - - if (!apiobj.params.rfdtarget) { // Not a softredirect - var target = response.query.redirects && response.query.redirects[0].to; - if (!target) { - var message = 'No target found. this page does not appear to be a redirect, aborting'; - if (mw.config.get('wgAction') === 'history') { - message += '. If this is a soft redirect, try again from the content page, not the page history.'; - } - apiobj.statelem.error(message); - return; - } - apiobj.params.rfdtarget = target; - var section = response.query.redirects[0].tofragment; - apiobj.params.section = section; - } - callback(apiobj.params); - }; - }, - main: function(params) { - var date = new Morebits.date(params.curtimestamp); - params.logpage = 'Wikipedia:Redirects for discussion/Log/' + date.format('YYYY MMMM D', 'utc'); - params.discussionpage = params.logpage + '#' + Morebits.pageNameNorm; - - // Tagging redirect - var wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Adding deletion tag to redirect'); - wikipedia_page.setFollowRedirect(false); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.rfd.taggingRedirect); - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = params.logpage; - Morebits.wiki.actionCompleted.notice = "Nomination completed, now redirecting to today's log"; - - // Adding discussion - wikipedia_page = new Morebits.wiki.page(params.logpage, "Adding discussion to today's log"); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.rfd.todaysList); - - // Notifications - if (params.notifycreator || params.relatedpage) { - var thispage = new Morebits.wiki.page(mw.config.get('wgPageName')); - thispage.setCallbackParameters(params); - thispage.lookupCreation(Twinkle.xfd.callbacks.rfd.sendNotifications); - // or, if not notifying, add this nomination to the user's userspace log without the initial contributor's name - } else { - Twinkle.xfd.callbacks.addToLog(params, null); - } - }, - taggingRedirect: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - // Imperfect for edit request but so be it - params.tagText = '{{subst:rfd|' + (mw.config.get('wgNamespaceNumber') === 10 ? 'showontransclusion=1|' : '') + 'content=\n'; - - if (pageobj.canEdit()) { - pageobj.setPageText(params.tagText + text + '\n}}'); - pageobj.setEditSummary('Listed for discussion at [[:' + params.discussionpage + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchPage')); - pageobj.setCreateOption('nocreate'); - pageobj.save(); - } else { - Twinkle.xfd.callbacks.autoEditRequest(pageobj, params); - } - }, - todaysList: function(pageobj) { - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var added_data = Twinkle.xfd.callbacks.getDiscussionWikitext('rfd', params); - var text; - - // add date header if the log is found to be empty (a bot should do this automatically) - if (!pageobj.exists()) { - text = '{{subst:RfD log}}' + added_data; - } else { - var old_text = pageobj.getPageText(); - text = old_text.replace(/()/, '$1\n' + added_data); - if (text === old_text) { - statelem.error('failed to find target spot for the discussion'); - return; - } - } - - pageobj.setPageText(text); - pageobj.setEditSummary('Adding [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.setCreateOption('recreate'); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - }); - }, - sendNotifications: function(pageobj) { - var initialContrib = pageobj.getCreator(); - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - // Notifying initial contributor - if (params.notifycreator) { - Twinkle.xfd.callbacks.notifyUser(params, initialContrib); - } - - // Notifying target page's watchers, if not a soft redirect - if (params.relatedpage) { - var targetTalk = new mw.Title(params.rfdtarget).getTalkPage(); - - // On the offchance it's a circular redirect - if (params.rfdtarget === mw.config.get('wgPageName')) { - statelem.warn('Circular redirect; skipping target page notification'); - } else if (document.getElementById('softredirect')) { - statelem.warn('Soft redirect; skipping target page notification'); - // Don't issue if target talk is the initial contributor's talk or your own - } else if (targetTalk.getNamespaceId() === 3 && targetTalk.getNameText() === initialContrib) { - statelem.warn('Target is initial contributor; skipping target page notification'); - } else if (targetTalk.getNamespaceId() === 3 && targetTalk.getNameText() === mw.config.get('wgUserName')) { - statelem.warn('You (' + mw.config.get('wgUserName') + ') are the target; skipping target page notification'); - } else { - // Don't log if notifying creator above, will log then - Twinkle.xfd.callbacks.notifyUser(params, targetTalk.toText(), params.notifycreator, 'Notifying redirect target of the discussion'); - return; - } - // If we thought we would notify the target but didn't, - // we need to log if we didn't notify the creator - if (!params.notifycreator) { - Twinkle.xfd.callbacks.addToLog(params, null); - } - } - } - }, - - rm: { - listAtTalk: function(pageobj) { - var params = pageobj.getCallbackParameters(); - - pageobj.setAppendText('\n\n' + Twinkle.xfd.callbacks.getDiscussionWikitext('rm', params)); - pageobj.setEditSummary('Proposing move' + (params.newname ? ' to [[:' + params.newname + ']]' : '')); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.setCreateOption('recreate'); // since the talk page need not exist - pageobj.setWatchlist(Twinkle.getPref('xfdWatchDiscussion')); - pageobj.append(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - // add this nomination to the user's userspace log - Twinkle.xfd.callbacks.addToLog(params, null); - }); - }, - - listAtRMTR: function(pageobj) { - var text = pageobj.getPageText(); - var params = pageobj.getCallbackParameters(); - var statelem = pageobj.getStatusElement(); - - var hiddenCommentRE = /==== ?Uncontroversial technical requests ?====(?:.|\n)*? -->/i; - var newtext = text.replace(hiddenCommentRE, '$&\n' + Twinkle.xfd.callbacks.getDiscussionWikitext('rm', params)); - if (text === newtext) { - statelem.error('failed to find target spot for the entry'); - return; - } - pageobj.setPageText(newtext); - pageobj.setEditSummary('Adding [[:' + Morebits.pageNameNorm + ']].'); - pageobj.setChangeTags(Twinkle.changeTags); - pageobj.save(function() { - Twinkle.xfd.currentRationale = null; // any errors from now on do not need to print the rationale, as it is safely saved on-wiki - // add this nomination to the user's userspace log - Twinkle.xfd.callbacks.addToLog(params, null); - }); - } - } -}; - - - -Twinkle.xfd.callback.evaluate = function(e) { - var form = e.target; - - var params = Morebits.quickForm.getInputData(form); - - Morebits.simpleWindow.setButtonsEnabled(false); - Morebits.status.init(form); - - Twinkle.xfd.currentRationale = params.reason; - Morebits.status.onError(Twinkle.xfd.printRationale); - - var query, wikipedia_page, wikipedia_api; - switch (params.venue) { - - case 'afd': // AFD - query = { - action: 'query', - list: 'allpages', - apprefix: 'Articles for deletion/' + Morebits.pageNameNorm, - apnamespace: 4, - apfilterredir: 'nonredirects', - aplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops - format: 'json' - }; - wikipedia_api = new Morebits.wiki.api('Tagging article with deletion tag', query, Twinkle.xfd.callbacks.afd.main); - wikipedia_api.params = params; - wikipedia_api.post(); - break; - - case 'tfd': // TFD - if (params.tfdtarget) { // remove namespace name - params.tfdtarget = utils.stripNs(params.tfdtarget); - } - - // Modules can't be tagged, TfD instructions are to place on /doc subpage - params.scribunto = mw.config.get('wgPageContentModel') === 'Scribunto'; - if (params.xfdcat === 'tfm') { // Merge - // Tag this template/module - if (params.scribunto) { - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging this module documentation with merge tag'); - params.otherTemplateName = 'Module:' + params.tfdtarget; - } else { - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging this template with merge tag'); - params.otherTemplateName = 'Template:' + params.tfdtarget; - } - } else { // delete - if (params.scribunto) { - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName') + '/doc', 'Tagging module documentation with deletion tag'); - } else { - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging template with deletion tag'); - } - } - wikipedia_page.setFollowRedirect(true); // should never be needed, but if the page is moved, we would want to follow the redirect - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.tfd.main); - break; - - case 'mfd': // MFD - query = { - action: 'query', - list: 'allpages', - apprefix: 'Miscellany for deletion/' + Morebits.pageNameNorm, - apnamespace: 4, - apfilterredir: 'nonredirects', - aplimit: 'max', // 500 is max for normal users, 5000 for bots and sysops - format: 'json' - }; - wikipedia_api = new Morebits.wiki.api('Looking for prior nominations of this page', query, Twinkle.xfd.callbacks.mfd.main); - wikipedia_api.params = params; - wikipedia_api.post(); - break; - - case 'ffd': // FFD - // Tagging file - // A little out of order with this coming before 'main', - // but tagging doesn't need the uploader parameter, - // while everything else does, so tag then get the uploader - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Adding deletion tag to file page'); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.ffd.taggingImage); - break; - - case 'cfd': - if (params.cfdtarget) { - params.cfdtarget = utils.stripNs(params.cfdtarget); - } else { - params.cfdtarget = ''; // delete - } - if (params.cfdtarget2) { // split - params.cfdtarget2 = utils.stripNs(params.cfdtarget2); - } - - // Used for customized actions in edit summaries and the notification template - var summaryActions = { - 'cfd': 'deletion', - 'sfd-t': 'deletion', - 'cfm': 'merging', - 'cfr': 'renaming', - 'sfr-t': 'renaming', - 'cfs': 'splitting', - 'cfc': 'conversion' - }; - params.action = summaryActions[params.xfdcat]; - - // Tagging category - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging category with ' + params.action + ' tag'); - wikipedia_page.setFollowRedirect(true); // should never be needed, but if the page is moved, we would want to follow the redirect - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.cfd.main); - break; - - case 'cfds': - // add namespace name if missing - params.cfdstarget = utils.addNs(params.cfdstarget, 14); - - var logpage = 'Wikipedia:Categories for discussion/Speedy'; - - // Updating data for the action completed event - Morebits.wiki.actionCompleted.redirect = logpage; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, now redirecting to the discussion page'; - - // Tagging category - wikipedia_page = new Morebits.wiki.page(mw.config.get('wgPageName'), 'Tagging category with rename tag'); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.cfds.taggingCategory); - - // Adding discussion to list - wikipedia_page = new Morebits.wiki.page(logpage, 'Adding discussion to the list'); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - wikipedia_page.load(Twinkle.xfd.callbacks.cfds.addToList); - - break; - - case 'rfd': - // find target and pass main as the callback - Twinkle.xfd.callbacks.rfd.findTarget(params, Twinkle.xfd.callbacks.rfd.main); - break; - - case 'rm': - var nomPageName = params.rmtr ? - 'Wikipedia:Requested moves/Technical requests' : - new mw.Title(Morebits.pageNameNorm).getTalkPage().toText(); - - Morebits.wiki.actionCompleted.redirect = nomPageName; - Morebits.wiki.actionCompleted.notice = 'Nomination completed, now redirecting to the discussion page'; - - wikipedia_page = new Morebits.wiki.page(nomPageName, params.rmtr ? 'Adding entry at WP:RM/TR' : 'Adding entry on talk page'); - wikipedia_page.setFollowRedirect(true); - wikipedia_page.setCallbackParameters(params); - - if (params.rmtr) { - wikipedia_page.setPageSection(2); - wikipedia_page.load(Twinkle.xfd.callbacks.rm.listAtRMTR); - } else { - // listAtTalk uses .append(), so no need to load the page - Twinkle.xfd.callbacks.rm.listAtTalk(wikipedia_page); - } - break; - - default: - alert('twinklexfd: unknown XFD discussion venue'); - break; - } -}; - -Twinkle.addInitCallback(Twinkle.xfd, 'xfd'); -})(jQuery); - - -//