User:NguoiDungKhongDinhDanh/QuickFunction.js
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/**
* Run Javascript functions quickly from your tab.
* Not supposed to be a replacement for browser console.
* Basic knowledge of JS is required to use this.
**/
mw.loader.using(['mediawiki.util', 'ext.pygments', 'jquery', 'jquery.ui']).then(function() {
if (mw.config.get('wgCodeEditorCurrentLanguage')) {
return; // We don't mess with already-exist codeEditor(s).
}
mw.util.addPortletLink('p-tb', 'javascript:void(0)', 'QuickFunction', 't-qfn');
$('#t-qfn').click(function(e) {
e.preventDefault();
if ($('#qfn-dialog').length) {
$('#qfn-dialog').dialog('open');
return;
}
// Styles.
var url = 'https://upload.wikimedia.org/wikipedia/commons/thumb';
var css = [
`.qfn-logline::before {
margin-right: 0.3em;
}`,
`[dir="rtl"] .qfn-logline::before {
margin-right: unset;
margin-left: 0.3em;
}`,
`.qfn-logline-info::before {
content: url(${url}/2/2b/OOjs_UI_icon_information.svg/10px-OOjs_UI_icon_information.svg.png)
}`,
`.qfn-logline-warning::before {
content: url(${url}/3/3b/OOjs_UI_icon_alert-warning.svg/10px-OOjs_UI_icon_alert-warning.svg.png)
}`,
`.qfn-logline-error::before {
content: url(${url}/3/33/OOjs_UI_icon_clear-destructive.svg/10px-OOjs_UI_icon_clear-destructive.svg.png)
}`,
`#qfn-dialog input,
#qfn-dialog select {
-webkit-transition: outline 250ms;
transition: outline 250ms;
}`,
`#qfn-dialog input:not([disabled]):focus,
#qfn-dialog select:not([disabled]):focus,
#qfn-dialog input:not([disabled]):active,
#qfn-dialog select:not([disabled]):active {
outline: 1px solid #3366CC;
}`
];
mw.loader.addStyleTag(css.join('\n').replace(/^\t{2,}(?=(?:\t\w|\}|#))/gm, ''));
// Other variables.
var advert = ' ([[:m:User:NguoiDungKhongDinhDanh/QuickFunction.js|QFN]])';
var jsonwp = 'User:' + mw.config.get('wgUserName') + '/QuickFunction.json';
// Global general functions.
var QFN = {};
window.QFN = QFN;
// Logging.
QFN.log = function(content, header, type = 'info') {
console[type === 'info' ? 'log' : type](content);
var border, background, text;
switch (type) { // Inspired by/Stolen from Chrome's DevTools.
case 'info':
border = '#C8CCD1';
background = 'transparent';
text = '#202124';
break;
case 'warn':
border = '#FFF5C2';
background = '#FFFBE5';
text = '#5C3C00';
break;
case 'error':
border = '#FFD6D6';
background = '#FFF0F0';
text = '#FF0000';
break;
}
$('#qfn-log').append(
$('<div>').attr({
'class': 'qfn-logline qfn-logline-' + (type === 'warn' ? 'warning' : type)
}).css({
'margin': '0.3em 0',
'border': '1px solid ' + border,
'background': background,
'padding': '0.5em',
'line-height': '',
'font-size': 'small'
}).append(
$('<p>').css({
'display': 'inline-block'
}).html(
$('<strong>').css('color', text).text(header || 'QuickFunction:')
),
$('<div>').css({
'padding': '0 calc(10px + 0.3em)',
'color': text
}).html(content && content.hasOwnProperty('toString') ? content.toString() : content)
)
);
};
['info', 'warn', 'error'].forEach(function(type) {
QFN[type] = function(content, header = null) {
return QFN.log(content, header, type);
};
});
// QFN functions.
QFN.apierror = function(error, response) {
return QFN.warn(response.error.info, 'API error: ' + error);
};
QFN.buttontoggle = function() {
$('#qfn-dialog').parent().find('button').not('#qfn-reload').each(function() {
if ($(this).button('option', 'disabled')) {
$(this).button('enable');
} else {
$(this).button('disable');
}
});
};
// API callers.
QFN.call = function(method, data, callback) {
$.ajax({
url: mw.config.get('wgScriptPath') + '/api.php',
data: data,
dataType: 'json',
method: method,
success: callback,
error: QFN.apierror
});
};
QFN.fapi = function(url, method, data, callback) {
(new mw.ForeignApi(url))[method](data).done(callback).fail(QFN.apierror);
};
// JSON formatters.
QFN.fmt = function(object, replacer = null, space = '\t') {
if (typeof object === 'string') {
try {
return JSON.stringify(JSON.parse(object), replacer, space);
} catch (error) {
QFN.warn('Invalid JSON.');
return '';
}
} else if (typeof object === 'object') {
return JSON.stringify(object, replacer, space);
} else {
QFN.warn('Invalid JSON.');
return '';
}
};
QFN.parse = function(object, callback) {
var content = QFN.fmt(object);
if (content === '') {
QFN.warn('Invalid JSON.');
return;
}
QFN.call('GET', {
action: 'parse',
text: '<syntaxhighlight lang="json">\n' + content + '\n</syntaxhighlight>',
prop: ['text'],
disablelimitreport: true,
disableeditsection: true,
disabletoc: true,
contentmodel: 'wikitext',
format: 'json',
formatversion: 2
}, callback || function(response) {
QFN.info(response.parse.text);
});
};
// Storing & retrieving.
QFN.storage = function() {
var storage;
try {
storage = JSON.parse(localStorage.getItem('QuickFunction-templates') || '{}');
} catch (e) {
storage = {};
}
return storage;
};
QFN.merge = function(silent, callback) {
QFN.fapi('https://meta.wikimedia.org/w/api.php', 'get', {
action: 'query',
prop: ['revisions'],
titles: [jsonwp],
rvprop: ['content'],
rvslots: 'main',
rvlimit: 1,
format: 'json',
formatversion: 2
}, function(response) {
var content = {};
if (!response.query.pages[0].missing) {
try {
content = JSON.parse(response.query.pages[0].revisions[0].slots.main.content);
} catch (e) {
QFN.warn('Invalid JSON.');
return;
}
}
content = Object.assign(QFN.storage(), content);
QFN.store(content, silent = true);
if (callback) callback();
});
};
QFN.content = function() {
var storage = QFN.storage();
storage[$('#qfn-template-name').val()] = $('#qfn-code').textSelection('getContents');
return storage;
};
QFN.store = function(content, silent = false) {
localStorage.setItem('QuickFunction-templates', QFN.fmt(content || QFN.content(), null, null));
if (!silent) {
QFN.parse(localStorage.getItem('QuickFunction-templates'), function(response) {
QFN.info(response.parse.text, 'Stored successfully.');
});
}
};
QFN.save = function() {
var url = 'https://meta.wikimedia.org/w/api.php';
QFN.fapi(url, 'get', {
action: 'query',
meta: 'tokens',
type: 'csrf',
format: 'json',
formatversion: 2
}, function(response) {
var token = response.query.tokens.csrftoken;
QFN.fapi(url, 'post', {
action: 'edit',
title: jsonwp,
text: QFN.fmt(QFN.content()),
summary: '/* Automatic edit */ Saving personal functions' + advert,
token: token,
format: 'json',
formatversion: 2
}, function(response) {
QFN.parse(response, function(parsed) {
QFN.info(parsed.parse.text, 'Edited successfully:');
QFN.update();
});
});
});
};
QFN.update = function() {
$('#qfn-template-names').html(function() {
var r = [];
var list = QFN.storage();
r.push(
$('<option>')
.data('code', '')
.text('Blank')
.prop('selected', true)
);
for (let i in list) {
r.push(
$('<option>')
.data('code', list[i])
.attr('value', list[i])
.text(i)
);
}
return r;
});
};
QFN.delete = function() {
var storage = QFN.storage();
var name = $('#qfn-template-names').find('option:selected').text();
if (name !== 'Blank') {
delete storage[name];
} else {
QFN.warn('"Blank" cannot be deleted.');
return;
}
QFN.store(QFN.fmt(storage, null, null), false);
};
// Dialog content.
$('<div>').attr({
id: 'qfn-dialog'
}).append(
$('<div>').attr({
'id': 'qfn-wrapper'
}).css({
'display': 'flex',
'flex-direction': 'column'
}).append(
$('<input>').attr({
type: 'hidden'
}),
$('<div>').attr({
'id': 'qfn-log'
}).css({
'margin': '0.3em',
'border': '1px solid #C8CCD1',
'resize': 'vertical',
'height': '7.5em',
'min-height': '7.5em',
'overflow-y': 'auto',
'padding': '1em'
}),
$('<div>').css({
'display': 'flex'
}).append(
$('<button>').attr({
id: 'qfn-log-clear'
}).css({
'margin': '0.3em'
}).text(
'Clear'
).button(),
$('<button>').attr({
id: 'qfn-log-toggle'
}).css({
'margin': '0.3em'
}).text(
'Hide log'
).button(),
$('<label>').css({
'display': 'flex',
'align-items': 'center',
'gap': '0.3em',
'flex-grow': '4',
'margin': '0.3em'
}).append(
$('<span>').text('Name:'),
$('<input>').attr({
type: 'text',
autocomplete: 'off',
id: 'qfn-template-name'
}).css({
'flex-grow': '4',
'padding': '0.3em'
})
),
$('<button>').css({
'margin': '0.3em'
}).attr({
'id': 'qfn-template-save',
'title': 'Save content to localStorage'
}).text(
'Save'
).button(),
$('<select>').attr({
id: 'qfn-template-names'
}).css({
'margin': '0.3em',
'padding': '0.3em'
}).append(
$('<option>').text(
'Loading...'
).prop({
'selected': true,
'hidden': true
})
),
$('<button>').css({
'margin': '0.3em'
}).attr({
'id': 'qfn-template-delete',
'title': 'Delete this template from localStorage'
}).text(
'Delete'
).button()
),
$('<div>').attr({
'class': 'qfn-pseudocode'
}).css({
'margin': '0.5em 0.3em',
'font-family': '"Consolas", monospace'
}).append(
$('<span>').append(
'(',
$('<strong>').css('color', 'green').text('function'),
'() {'
)
),
$('<div>').attr('id', 'qfn-code-wrapper'),
$('<div>').attr({
'class': 'qfn-pseudocode'
}).css({
'margin': '0.5em 0.3em',
'font-family': '"Consolas", monospace'
}).append(
$('<span>').append(
'}',
')();'
)
)
)
).appendTo(document.body);
// Start dialog.
$('#qfn-dialog').dialog({
autoOpen: true,
width: '80%',
'min-width': '60%',
height: 600,
'min-height': 400,
title: 'QuickFunction',
buttons: [
// {
// text: 'Stop',
// id: 'qfn-stop',
// disabled: true,
// click: function() {
// window.QuickFunctionRunning.resolve();
// delete window.QuickFunctionRunning;
// QFN.buttontoggle();
// }
// },
{
text: 'Reload this dialog',
id: 'qfn-reload',
click: function() {
if (!confirm('Do you really want to reload?')) return;
$(this).parent().remove();
$(this).remove();
$('#t-qfn').click();
}
},
{
text: 'Run!',
id: 'qfn-run',
click: function() {
QFN.buttontoggle();
if (mw.loader.getState('jquery.textSelection') === 'ready') {
mw.loader.state({
'jquery.textSelection': 'loaded'
});
}
mw.loader.load('jquery.textSelection');
if (!$('#qfn-code').textSelection('getContents')) {
QFN.error('No content found.');
return;
}
// window.QuickFunctionRunning = new $.Deferred();
var code = $('#qfn-code').textSelection('getContents');
try {
$.when.apply($, window.QuickFunctionRunning, (new Function(code))()).then(function() {
QFN.buttontoggle();
});
} catch (e) {
QFN.error(e);
QFN.buttontoggle();
}
}
}
]
});
// Get storage.
QFN.merge(true, QFN.update);
// Coding area. For attribution: [[:en:User:BrandonXLF/SVGEditor.js]].
mw.loader.using(['oojs-ui', 'ext.wikiEditor']).then(function() {
var code = new OO.ui.MultilineTextInputWidget({
rows: 17,
name: 'wpTextbox1', // qfn-code', // Go ask codeEditor for reason.
id: 'qfn-code-wrapper'
});
$('#wpTextbox1', '#content').attr({
name: 'wpTextbox1-temp',
id: 'wpTextbox1-temp'
});
var $container = $('#qfn-code-wrapper');
code.$element.css('max-width', 'unset');
// code.$input.attr('id', 'qfn-code');
code.$input.attr('id', 'wpTextbox1');
mw.config.set('wgCodeEditorCurrentLanguage', 'javascript');
$container.replaceWith(code.$element);
mw.addWikiEditor(code.$input);
if (mw.loader.getState('ext.codeEditor') === 'ready') {
mw.loader.state({'ext.codeEditor': 'loaded'});
}
// From /w/load.php?modules=ext.codeEditor
// mw.loader.implement(['ext.codeEditor@v0khv'], function($, jQuery, require, module) {
// $('#qfn-code').parent().prop('dir', 'ltr');
// $('#qfn-code').wikiEditor('addModule', 'codeEditor');
// $('#qfn-code').on('wikiEditor-toolbar-doneInitialSections', function () {
// $('#qfn-code').data('wikiEditor-context').fn.codeEditorMonitorFragment();
// });
// });
mw.loader.using(['ext.codeEditor'], function() {
window.QFNcodeEditorloaded = true;
// Loaded. Now return my id.
$('body').one('mouseover', function() {
if (window.QFNcodeEditorloaded) {
$('#wpTextbox1', '#qfn-dialog').attr('id', 'qfn-code').removeAttr('name');
$('#wpTextbox1-temp[name="wpTextbox1-temp"]').attr({
name: 'wpTextbox1',
id: 'wpTextbox1'
});
delete window.QFNcodeEditorloaded;
}
});
});
});
// Event handlers.
$('#qfn-log-clear').click(function() {
$('#qfn-log').empty();
});
$('#qfn-log-toggle').click(function() {
var b = $('#qfn-log').is(':visible');
if (b) {
$('#qfn-log').css({
'min-height': b ? 'initial' : '7.5em'
}).slideToggle(500, 'swing');
} else {
$('#qfn-log').slideToggle(500, 'swing', function() {
$(this).css({
'min-height': b ? 'initial' : '7.5em'
});
});
}
$(this).children('span').text(
b ? 'Show log' : 'Hide log'
);
});
// For attribution: //stackoverflow.com/a/469362
[
'input', 'keydown', 'keyup',
'mousedown', 'mouseup', 'select',
'contextmenu', 'drop', 'focusout'
].forEach(function(event) {
$('#qfn-template-name').on(event, function(e) {
if ((function(v) {
return !/^Blank$/.test(v.trim());
})(this.value)) {
if (['keydown', 'mousedown', 'focusout'].includes(e.type)) {
this.setCustomValidity('');
}
this.oldValue = this.value;
this.oldSelectionStart = this.selectionStart;
this.oldSelectionEnd = this.selectionEnd;
} else if (this.hasOwnProperty('oldValue')) {
this.setCustomValidity('Template names cannot be "Blank".');
this.reportValidity();
this.value = this.oldValue;
this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd);
} else {
this.value = '';
}
})
});
$('#qfn-template-names').change(function() {
$('#qfn-template-name').val(
$(this).find('option:selected').text() !== 'Blank' &&
$(this).find('option:selected').text() ||
''
);
$('#qfn-code').textSelection('setContents', $(this).find('option:selected').data('code'));
});
$('#qfn-template-delete').click(function() {
QFN.delete();
QFN.save();
});
$('#qfn-template-save').click(function() {
QFN.store(QFN.content(), true);
QFN.save();
});
});
});