User:BrandonXLF/ListSorter.js

/*** List Sorter ***/

// Tool to sort bullet-point lists on a page // Documentation at en:w:User:BrandonXLF/ListSorter // By en:w:User:BrandonXLF

$(function {	var lang = mw.config.get('wgPageContentLanguage'),		title = mw.config.get('wgPageName'),		summary = 'Sorted bullet lists using ListSorter',		regex = /(\n|^)(\*+)(.*)/g;

function unattachedInnerText(el) { var parent = el.parentNode, tmp = parent ? document.createElement('i') : null;

parent && parent.replaceChild(tmp, el); document.body.appendChild(el);

var text = el.innerText;

document.body.removeChild(el); parent && parent.replaceChild(el, tmp);

return text; }

function sortList(list) { var children = Array.prototype.slice.call(list.children);

while (list.firstChild) list.removeChild(list.firstChild);

children.sort(function(a, b) {			return unattachedInnerText(a).trim.localeCompare( unattachedInnerText(b).trim, lang, {sensitivity: 'accent'} );		});

for (var i = 0; i < children.length; i++) list.appendChild(children[i]); }

function recursiveShow(sortables) { var options = [], values = [], widget = new OO.ui.CheckboxMultiselectInputWidget, data = 0;

for (var i = 0; i < sortables.length; i++) { var sortable = sortables[i], items = $(sortables[i]).children, list = $(''), cnt = $(' '), preview = $(' ');

items.slice(0, 2).each(function {				return $('').text(unattachedInnerText(this)).appendTo(list);			});

if (items.length >= 3) { $('').text('…').appendTo(list); }

preview.css({				overflow: 'hidden',				pointerEvents: 'none',				whiteSpace: 'nowrap'			}).appendTo(cnt).append(list);

if (sortable.listSortChildren.length) { $(' ').css({paddingTop: '12px'}).appendTo(cnt).append(recursiveShow(sortable.listSortChildren).$element); }

sortable.listSortInputIndex = '' + data++; options.push({				data: sortable.listSortInputIndex,				label: cnt			}); values.push(sortable.listSortInputIndex); }

widget.setOptions(options); widget.setValue(values);

for (i = 0; i < sortables.length; i++) { sortables[i].listSortWidget = widget.checkboxMultiselectWidget.findItemFromData(sortables[i].listSortInputIndex); }

return widget; }

function recursiveDo(sortables, action) { for (var i = 0; i < sortables.length; i++) { action(sortables[i]); recursiveDo(sortables[i].listSortChildren, action); }	}

function sort { var params = { action: 'query', format: 'json', prop: 'revisions', titles: title, formatversion: 2, rvprop: 'content', rvslots: 'main' };

new mw.Api.get(params).then(function(res) {			var text = res.query.pages[0].revisions[0].slots.main.content,				afters = [],				marked = text.replace(regex, function(_, before, bullets, after) { return before + bullets + '' + after + ' ';				});

new mw.Api.parse(marked).then(function(parsed) {				var container = document.createElement('div'),					topLevel = [];

container.innerHTML = parsed;

var nodes = container.querySelectorAll('.listsorter-start');

for (var i = 0; i < nodes.length; i++) { var list = nodes[i].parentNode.parentNode;

if (nodes[i].parentNode.previousElementSibling) continue;

list.listSortChildren = [];

if (list.children.length < 2) continue;

var parent = $(list).parents('ul')[0];

(parent ? parent.listSortChildren : topLevel).push(list); }

var sort = new OO.ui.ButtonInputWidget({						label: 'Sort and review',						flags: ['primary', 'progressive']					}), select = new OO.ui.ButtonInputWidget({						label: 'Select all'					}), deselect = new OO.ui.ButtonInputWidget({						label: 'Deselect all'					}), cancel = new OO.ui.ButtonInputWidget({						label: 'Cancel',						framed: false,						flags: ['destructive']					}), inputs = recursiveShow(topLevel), buttons = new OO.ui.HorizontalLayout({						items: [sort, select, deselect, cancel]					}), fieldset = new OO.ui.FieldsetLayout({						label: 'Select lists to sort',						items: [inputs, buttons],						id: 'listsorterui'					});

inputs.$element.css({					border: '1px solid #888',					borderBottom: '0',					padding: '1em'				});

buttons.$element.css({					paddingTop: '12px',					position: 'sticky',					bottom: '0',					background: '#fff',					borderTop: '1px solid #888',					marginRight: '8px',					boxShadow: '0 -4px 4px -4px #888'				});

select.on('click', function {					recursiveDo(topLevel, function(sortable) { sortable.listSortWidget.setSelected(true); });				});

deselect.on('click', function {					recursiveDo(topLevel, function(sortable) { sortable.listSortWidget.setSelected(false); });				});

sort.on('click', function {					recursiveDo(topLevel, function(sortable) { if (sortable.listSortWidget.isSelected) sortList(sortable); });

var nodes = container.querySelectorAll('.listsorter-start'), i = -1;

text = text.replace(regex, function(_, before) {						i++;						return before + nodes[i].getAttribute('data-bullets') + afters[+nodes[i].getAttribute('data-index')];					});

$('') .append($('').val(text)) .append($(' ').val(title)) .append('') .append('') .append('') .append('') .append('') .appendTo(document.body) .submit; });

cancel.on('click', function {					fieldset.$element.remove;				});

$('#listsorterui').remove; $('#mw-content-text').prepend(fieldset.$element); });		});	}

$(mw.util.addPortletLink('p-cactions', '#', 'Sort lists')).click(function(e) {		e.preventDefault;		sort;	});

mw.loader.addStyleTag(		'#listsorterui .oo-ui-multiselectWidget-group { overflow: hidden; }' +		'#listsorterui .oo-ui-checkboxMultioptionWidget.oo-ui-labelElement { width: 100%; margin-top: 0.5em; }' +		'#listsorterui li { white-space: nowrap; }'	); });