(function() {
function loadScripts() {
var requests = [null, null], // Force $.when to return arrays
enabledScripts = [],
js = '',
scripts = JSON.parse(mw.user.options.get('userjs-scriptmanager-scripts') || '[]');
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i];
if (script.enabled && (script.conds ? eval(script.conds) : true) && script.name !== 'User:BrandonXLF/sandbox.js') {
enabledScripts.push(script.name);
js += script.vars + '\n';
}
}
for (var i = 0; i < enabledScripts.length; i += 50) {
requests.push(new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: enabledScripts.slice(i, i + 50).join('|'),
rvprop: 'content',
rvslots: 'main',
formatversion: 2
}));
}
$.when.apply(null, requests).then(function() {
for (var i = 2; i < arguments.length; i++) {
arguments[i][0].query.pages.forEach(function(page) {
if (page.revisions) {
js += '/* ' + page.title + ' */\n' + page.revisions[0].slots.main.content;
} else {
console.warn('Failed to load revision for script ' + page.title);
}
});
}
var script = document.createElement('script');
script.text = js;
document.head.appendChild(script);
});
}
function manageScripts(e) {
e.preventDefault();
function ScriptGroup(items) {
ScriptGroup.super.call(this);
OO.ui.mixin.DraggableGroupElement.call(this, {
$group: this.$element,
items: items
});
}
OO.inheritClass(ScriptGroup, OO.ui.Widget);
OO.mixinClass(ScriptGroup, OO.ui.mixin.DraggableGroupElement);
function ScriptWidget(data, dialog) {
var widget = this;
ScriptWidget.super.call(this, {data: data});
OO.ui.mixin.IconElement.call(this, {icon: 'draggable'});
OO.ui.mixin.LabelElement.call(this, {label: data.name});
OO.ui.mixin.DraggableElement.call(this, {$handle: this.$icon});
this.toggle = new OO.ui.ButtonWidget({
framed: false,
label: widget.data.enabled ? 'Disable' : 'Enable'
});
this.toggle.on('click', function() {
widget.data.enabled = !widget.data.enabled;
widget.toggle.setLabel(widget.data.enabled ? 'Disable' : 'Enable');
widget.$label.toggleClass('userscript-disabled', !widget.data.enabled);
});
this.remove = new OO.ui.ButtonWidget({
framed: false,
flags: 'destructive',
label: 'Remove'
});
this.remove.on('click', function() {
widget.getElementGroup().removeItems([widget]);
dialog.updateSize();
});
this.$label.toggleClass('userscript-disabled', !this.data.enabled);
this.$element.append(
this.$icon,
this.$label,
new OO.ui.ButtonGroupWidget({
items: [this.toggle, this.remove]
}).$element
);
this.$element.addClass('userscript-listing');
this.$label.addClass('userscript-label');
}
OO.inheritClass(ScriptWidget, OO.ui.Widget);
OO.mixinClass(ScriptWidget, OO.ui.mixin.IconElement);
OO.mixinClass(ScriptWidget, OO.ui.mixin.LabelElement);
OO.mixinClass(ScriptWidget, OO.ui.mixin.DraggableElement);
function ManagerDialog(config) {
ManagerDialog.super.call(this, config);
}
OO.inheritClass(ManagerDialog, OO.ui.ProcessDialog);
ManagerDialog.static.name = 'manageuserscripts';
ManagerDialog.static.title = 'Manage user scripts';
ManagerDialog.static.actions = [
{
label: 'Close',
flags: ['safe', 'close'],
modes: ['list']
},
{
label: 'Save',
flags: ['primary', 'progressive'],
modes: ['list'],
action: 'save'
},
{
label: 'Add new script',
flags: ['safe'],
modes: ['list'],
action: 'add'
},
{
label: 'Import scripts',
flags: ['safe'],
modes: ['list'],
action: 'import'
},
{
label: 'Back',
flags: ['safe', 'back'],
modes: ['add'],
action: 'add-back'
},
{
label: 'Continue',
flags: ['primary', 'progressive'],
modes: ['add'],
action: 'add-continue'
},
];
ManagerDialog.prototype.initialize = function() {
ManagerDialog.super.prototype.initialize.apply(this, arguments);
var widgets = [],
scripts = JSON.parse(mw.user.options.get('userjs-scriptmanager-scripts') || '[]');
for (var i = 0; i < scripts.length; i++) {
widgets.push(new ScriptWidget(scripts[i], this));
}
this.listPanel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.addNewPanel = new OO.ui.PanelLayout({
padded: true,
expanded: false
});
this.group = new ScriptGroup(widgets);
this.panels = new OO.ui.StackLayout();
this.addNewInput = new OO.ui.TextInputWidget();
this.addNewLayout = new OO.ui.FieldLayout(this.addNewInput, {
align: 'top',
label: 'Enter script title'
});
this.listPanel.$element.append(this.group.$element);
this.addNewPanel.$element.append(this.addNewLayout.$element);
this.panels.addItems([this.listPanel, this.addNewPanel]);
this.$body.append(this.panels.$element);
};
ManagerDialog.prototype.getSetupProcess = function(data) {
return ManagerDialog.super.prototype.getSetupProcess.call(this, data).next(function() {
this.panels.setItem(this.listPanel);
this.actions.setMode('list');
}, this);
};
ManagerDialog.prototype.showErrors = function(errors) {
ManagerDialog.super.prototype.showErrors.call(this, errors);
this.actions.setAbilities({
'add-continue': true
});
this.updateSize();
};
ManagerDialog.prototype.showAddNewError = function(error) {
this.addNewLayout.setErrors(error ? [error] : []);
this.updateSize();
};
ManagerDialog.prototype.getActionProcess = function(action) {
var dialog = this;
return new OO.ui.Process(function() {
if (!action) return dialog.close();
if (action == 'add') {
dialog.panels.setItem(dialog.addNewPanel);
dialog.actions.setMode('add');
dialog.addNewInput.setValue('');
dialog.showAddNewError();
} else if (action == 'add-back') {
dialog.panels.setItem(dialog.listPanel);
dialog.actions.setMode('list');
} else if (action == 'add-continue') {
var name = dialog.addNewInput.getValue();
if (!name) return dialog.showAddNewError('Name is required.');
return new mw.Api().get({
action: 'query',
titles: name,
prop: 'info',
formatversion: 2
}).then(function(res) {
var info = res.query.pages[0];
if (info.missing) return dialog.showAddNewError('Script does not exist.');
if (info.ns !== 2 && info.ns !== 8) return dialog.showAddNewError('Script must be in the User or MediaWiki namespaces.');
if (info.contentmodel !== 'javascript') return dialog.showAddNewError('Script must have the JavaScript content model.');
var items = dialog.group.getItems();
for (var i = 0; i < items.length; i++) {
if (items[i].getData().name == info.title) return dialog.showAddNewError('A script with the name is already added.');
}
dialog.showAddNewError();
dialog.group.addItems([
new ScriptWidget({name: info.title, enabled: true}, dialog)
]);
dialog.panels.setItem(dialog.listPanel);
dialog.actions.setMode('list');
});
} else if (action == 'save') {
var items = dialog.group.getItems(),
scripts = [];
for (var i = 0; i < items.length; i++) {
scripts.push(items[i].getData());
}
return new mw.Api().saveOption('userjs-scriptmanager-scripts', JSON.stringify(scripts)).then(function() {
mw.user.options.set('userjs-scriptmanager-scripts', JSON.stringify(scripts));
dialog.close();
});
} else if (action == 'import') {
var skins = ['common', 'monobook', 'minerva', 'vector', 'cologneblue', 'timeless'],
skinScripts = [];
for (var i = 0; i < skins.length; i++) {
skinScripts.push('User:' + mw.config.get('wgUserName') + '/' + skins[i] + '.js');
}
return new mw.Api().get({
action: 'query',
prop: 'revisions',
titles: skinScripts.join('|'),
rvprop: 'content',
rvslots: 'main',
formatversion: 2
}).then(function(res) {
for (var i = 0; i < res.query.pages.length; i++) {
var re = /(\/\/|)importScript\('(.*)'\)/g,
match,
page = res.query.pages[i];
if (!page.revisions) continue;
while (true) {
match = re.exec(page.revisions[0].slots.main.content);
if (!match) break;
// Normalize title
var title = new mw.Title(match[2]).getPrefixedText(),
items = dialog.group.getItems(),
duplicate = false;
for (var j = 0; j < items.length; j++) {
// Script is already added, skip
if (items[j].getData().name == title) {
duplicate = true;
break;
}
}
if (duplicate) continue;
dialog.group.addItems([new ScriptWidget({
name: title,
enabled: !match[1]
}, dialog)]);
}
}
});
}
});
};
var windowManager = new OO.ui.WindowManager(),
dialog = new ManagerDialog({
size: 'large'
});
$(document.body).append(windowManager.$element);
windowManager.addWindows([dialog]);
windowManager.openWindow(dialog);
mw.loader.addStyleTag(
'.userscript-listing { display: block; position: relative; }' +
'.userscript-listing.oo-ui-iconElement { padding-left: 2.5em; }' +
'.userscript-listing.oo-ui-iconElement .oo-ui-iconElement-icon { position: absolute; left: 0.6em; }' +
'.userscript-listing > * { vertical-align: middle; }' +
'.userscript-disabled { text-decoration: line-through; }' +
'.userscript-label { margin-right: 1em; }'
);
}
mw.loader.using('mediawiki.api').then(function() {
loadScripts();
});
var portletLinks = mw.util.addPortletLink('p-personal', '#', 'Scripts', 'scriptmanager', 'Manage user scripts', '', '#pt-watchlist');
if (portletLinks) portletLinks.addEventListener('click', manageScripts);
})();