User:BrandonXLF/ListSorter.js
Note: After saving, you have to bypass your browser's cache to see the changes. Google Chrome, Firefox, Microsoft Edge and Safari: Hold down the ⇧ Shift key and click the Reload toolbar button. For details and instructions about other browsers, see Wikipedia:Bypass your cache.
/*** 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 [[en:w:User:BrandonXLF/ListSorter|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 = $('<ul>'),
cnt = $('<div>'),
preview = $('<div>');
items.slice(0, 2).each(function() {
return $('<li>').text(unattachedInnerText(this)).appendTo(list);
});
if (items.length >= 3) {
$('<li>').text('…').appendTo(list);
}
preview.css({
overflow: 'hidden',
pointerEvents: 'none',
whiteSpace: 'nowrap'
}).appendTo(cnt).append(list);
if (sortable.listSortChildren.length) {
$('<div>').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 +
'<div class="listsorter-start" data-bullets="' + bullets + '" data-index="' + (afters.push(after) - 1) + '">' +
after +
'</div><div class="listsorter-end"></div>';
});
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')];
});
$('<form method="post" action="' + mw.config.get('wgScript') + '" style="display:none;">')
.append($('<textarea name="wpTextbox1">').val(text))
.append($('<input name="title">').val(title))
.append('<input name="wpDiff" value="wpDiff">')
.append('<input name="wpUltimateParam" value="1">')
.append('<input name="wpIgnoreBlankSummary" value="1">')
.append('<input name="wpSummary" value="' + summary + '">')
.append('<input name="action" value="submit">')
.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; }'
);
});