Created
October 6, 2020 19:43
-
-
Save siddharthvp/d8e514016c1e3b3c9497866c9f8bb0a9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class TwinkleModule { | |
attachMenu() { | |
Twinkle.addPortletLink(this.makeWindow, this.portletName, this.portletId, this.portletTooltip) | |
} | |
} | |
// Init: Twinkle.addInitCallback(function() { new Twinkle(); }, 'Tag'); | |
class Tag extends TwinkleModule { | |
// Override to change modes available, | |
// each mode is a class extending TagMode | |
static modeList = [ | |
ArticleMode, | |
RedirectMode, | |
FileMode | |
]; | |
constructor() { | |
for (let mode of Tag.modeList) { | |
if (mode.isActive()) { | |
this.mode = new mode(); | |
break; | |
} | |
} | |
} | |
get portletName() { | |
return 'Tag'; | |
} | |
get portletId() { | |
return 'friendly-tag'; | |
} | |
get portletTooltip() { | |
return this.mode.getMenuTooltip(); | |
} | |
attachMenu() { | |
Twinkle.addPortletLink(this.makeWindow, 'Tag', 'friendly-tag', this.mode.getMenuTooltip()); | |
} | |
makeWindow() { | |
var Window = new Morebits.simpleWindow(630, 500); | |
Window.setScriptName('Twinkle'); | |
// anyone got a good policy/guideline/info page/instructional page link?? | |
Window.addFooterLink('Twinkle help', 'WP:TW/DOC#tag'); | |
this.mode.makeForm(Window); | |
this.mode.formRender(); | |
this.mode.postRender(); | |
} | |
evaluate() { | |
this.mode.evaluate(); | |
} | |
} | |
// Abstract class | |
class TagMode { | |
static isActive() { // must be overridden | |
return false; | |
} | |
get canRemove() { | |
return false; | |
} | |
getMenuTooltip() { | |
return 'Add maintenance tags to the page'; | |
} | |
get windowTitle() { | |
return 'Add maintenance tags'; | |
} | |
makeForm(Window) { | |
this.Window = Window; | |
this.Window.setTitle(this.mode.windowTitle); | |
this.form = new Morebits.quickForm(this.evaluate); | |
this.formAppendQuickFilter(); | |
} | |
formAppendQuickFilter() { | |
this.form.append({ | |
type: 'input', | |
label: 'Filter tag list: ', | |
name: 'quickfilter', | |
size: '30px', | |
event: QuickFilter.onInputChange | |
}); | |
} | |
formAppendPatrolLink() { | |
if (!document.getElementsByClassName('patrollink').length) { | |
return; | |
} | |
this.form.append({ | |
type: 'checkbox', | |
list: [{ | |
label: 'Mark the page as patrolled/reviewed', | |
value: 'patrol', | |
name: 'patrol', | |
checked: Twinkle.getPref('markTaggedPagesAsPatrolled') | |
}] | |
}); | |
} | |
formAppendSubmitButton() { | |
this.form.append({ | |
type: 'submit', | |
className: 'tw-tag-submit' | |
}); | |
} | |
formRender() { | |
this.result = this.form.render(); | |
this.Window.setContent(this.result); | |
this.Window.display(); | |
QuickFilter.init(this.result); | |
} | |
postRender() { | |
Morebits.quickForm.getElements(this.result, 'tags').forEach(generateLinks); | |
} | |
evaluate() { | |
} | |
} | |
/** | |
* Adds a link to each template's description page | |
* @param {Morebits.quickForm.element} checkbox associated with the template | |
*/ | |
function generateLinks(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]); | |
}; | |
class QuickFilter { | |
/** | |
* @param {HTMLFormElement} result | |
*/ | |
static init(result) { | |
QuickFilter.$allCheckboxDivs = $(result).find('[name$=tags]').parent(); | |
QuickFilter.$allHeaders = $(result).find('h5'); | |
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; | |
} | |
}); | |
} | |
static onInputChange() { | |
// flush the DOM of all existing underline spans | |
QuickFilter.$allCheckboxDivs.find('.search-hit').each(function (i, e) { | |
var label_element = e.parentElement; | |
// This would convert <label>Hello <span class=search-hit>wo</span>rld</label> | |
// to <label>Hello world</label> | |
label_element.innerHTML = label_element.textContent; | |
}); | |
if (this.value) { | |
QuickFilter.$allCheckboxDivs.hide(); | |
QuickFilter.$allHeaders.hide(); | |
var searchString = this.value; | |
var searchRegex = new RegExp(mw.util.escapeRegExp(searchString), 'i'); | |
QuickFilter.$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 = $('<span>').addClass('search-hit').css('text-decoration', 'underline')[0]; | |
range.surroundContents(underline_span); | |
this.parentElement.style.display = 'block'; // show | |
} | |
}); | |
} else { | |
QuickFilter.$allCheckboxDivs.show(); | |
QuickFilter.$allHeaders.show(); | |
} | |
} | |
} | |
class ArticleMode extends TagMode { | |
get name() { | |
return 'article'; | |
} | |
static isActive() { | |
return [0, 118].indexOf(mw.config.get('wgNamespaceNumber')) !== -1 && mw.config.get('wgCurRevisionId'); | |
} | |
get canRemove() { | |
return true; | |
} | |
getMenuTooltip() { | |
return 'Add or remove article maintenance tags'; | |
} | |
get windowTitle() { | |
return 'Article maintenance tagging'; | |
} | |
makeFlatObject() { | |
// Build sorting and lookup object flatObject, which is always | |
// needed but also used to generate the alphabetical list | |
this.flatObject = {}; | |
Object.keys(this.tagList).forEach(group => { | |
Object.keys(this.tagList[group]).forEach(subgroup => { | |
if (Array.isArray(this.tagList[group][subgroup])) { | |
this.tagList[group][subgroup].forEach(item => { | |
this.flatObject[item.tag] = { | |
description: item.description, | |
excludeMI: !!item.excludeMI | |
}; | |
}); | |
} else { | |
this.flatObject[this.tagList[group][subgroup].tag] = { | |
description: this.tagList[group][subgroup].description, | |
excludeMI: !!this.tagList[group][subgroup].excludeMI | |
}; | |
} | |
}); | |
}); | |
} | |
makeForm() { | |
super.makeForm(); | |
this.makeFlatObject(); | |
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: this.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: '60px' | |
}); | |
this.formAppendPatrolLink(); | |
this.formAppendSubmitButton(); | |
} | |
/** | |
* @override | |
*/ | |
postRender() { | |
this.alreadyPresentTags = []; | |
if (this.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((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) { | |
var tag = e.classList[0].slice(4).replace(/_/g, ' '); | |
this.alreadyPresentTags.push(tag); | |
}); | |
return true; // continue | |
} | |
var tag = e.classList[0].slice(4).replace(/_/g, ' '); | |
this.alreadyPresentTags.push(tag); | |
} | |
}); | |
// {{Uncategorized}} and {{Improve categories}} are usually placed at the end | |
if ($('.box-Uncategorized').length) { | |
this.alreadyPresentTags.push('Uncategorized'); | |
} | |
if ($('.box-Improve_categories').length) { | |
this.alreadyPresentTags.push('Improve categories'); | |
} | |
} | |
// Add status text node after Submit button | |
var statusNode = document.createElement('small'); | |
statusNode.id = 'tw-tag-status'; | |
this.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); | |
this.result.sortorder.dispatchEvent(evt); | |
} | |
updateSortOrder(e) { | |
} | |
evaluate() { | |
} | |
} | |
class RedirectMode extends TagMode { | |
get name() { | |
return 'redirect'; | |
} | |
static isActive() { | |
return Morebits.wiki.isPageRedirect(); | |
} | |
getMenuTooltip() { | |
return 'Tag redirect'; | |
} | |
get windowTitle() { | |
return 'Redirect tagging'; | |
} | |
makeForm() { | |
super.makeForm(); | |
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') | |
}); | |
} | |
this.formAppendPatrolLink(); | |
this.formAppendSubmitButton(); | |
} | |
evaluate() { | |
} | |
} | |
class FileMode extends TagMode { | |
get name() { | |
return 'file'; | |
} | |
static isActive() { | |
return mw.config.get('wgNamespaceNumber') === 6 && !document.getElementById('mw-sharedupload') && document.getElementById('mw-imagepage-section-filehistory') | |
} | |
getMenuTooltip() { | |
return 'Add maintenance tags to file'; | |
} | |
get windowTitle() { | |
return 'File maintenance tagging'; | |
} | |
makeForm() { | |
super.makeForm(); | |
$.each(this.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') | |
}); | |
} | |
this.formAppendPatrolLink(); | |
this.formAppendSubmitButton(); | |
} | |
evaluate() { | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment