Skip to content

Instantly share code, notes, and snippets.

@siddharthvp
Created October 6, 2020 19:43
Show Gist options
  • Save siddharthvp/d8e514016c1e3b3c9497866c9f8bb0a9 to your computer and use it in GitHub Desktop.
Save siddharthvp/d8e514016c1e3b3c9497866c9f8bb0a9 to your computer and use it in GitHub Desktop.
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