// <nowiki>
function loadDepenedencies() {
return mw.loader.using(['mediawiki.util', 'mediawiki.api', 'mediawiki.RegExp', 'oojs-ui-core', 'oojs-ui-widgets', 'oojs-ui-windows']);
}
function setupForTesting() {
/*
$('<div>').attr('id', 'qunit').insertBefore('#firstHeading');
// mw.loader.load( 'https://code.jquery.com/qunit/qunit-2.4.1.css', 'text/css' );
return mw.loader.using('jquery.qunit');
*/
return true;
}
function isSuitable() {
var config = mw.config.get(['wgAction', 'wgDiffOldId', 'wgNamespaceNumber', 'wgPageName']);
if (
config.wgAction !== 'view' ||
config.wgNamespaceNumber !== 0 ||
config.wgDiffOldId !== null
) {
return $.Deferred().reject();
}
return config;
}
var getLeadWikitext = function getLeadWikitext(api, pageName) {
return api.get({
"action": "parse",
"format": "json",
"page": pageName,
"prop": "wikitext",
"section": "0"
}).then(function (response) {
return response.parse.wikitext['*'];
});
};
var getTemplateParameters = function getTemplateParameters(wikitext) {
var params = {};
var unnamedParamCount = 0;
var templateParamsPattern = /\|(?!(?:[^{]+}|[^\[]+]))(?:.|\s)*?(?=(?:\||$)(?!(?:[^{]+}|[^\[]+])))/g;
var parts = wikitext.match(templateParamsPattern);
return parts.map(function (part, position) {
var isEmptyParameter = part.trim() === '|'; // i.e. first parameter of {{foo||bar}
if (isEmptyParameter) {
unnamedParamCount++;
return {
name: unnamedParamCount.toString(),
value: '',
wikitext: part
};
}
var equalsIndex = part.indexOf('=');
var bracesIndex = part.indexOf('{{');
var hasNoEqualsSign = equalsIndex === -1;
var firstEqualsSignWithinBraces = (bracesIndex !== -1) && (bracesIndex < equalsIndex);
var isUnnamedParameter = hasNoEqualsSign || firstEqualsSignWithinBraces;
if (isUnnamedParameter) {
unnamedParamCount++;
return {
name: unnamedParamCount.toString(),
value: part.slice(1).trim(),
wikitext: {
name: '|',
value: part
}
};
} else {
return {
name: part.slice(1, equalsIndex).trim(),
value: part.slice(equalsIndex + 1).trim(),
wikitext: {
name: part.slice(0, equalsIndex + 1),
value: part.slice(equalsIndex + 1)
}
};
}
});
};
var getInfoboxTemplate = function getInfoboxTemplate(wikitext) {
var infoboxPattern = /\{\{\s*(.?[Ii]nfobox.+?)\s*(\|(?:.|\n)*?(?:(?:\{\{(?:.|\n)*?(?:(?:\{\{(?:.|\n)*?\}\})(?:.|\n)*?)*?\}\})(?:.|\n)*?)*|)\}\}\n?/;
var infoboxParts = infoboxPattern.exec(wikitext);
if (!infoboxParts || !infoboxParts[0] || !infoboxParts[1]) {
throw new Error('Unable to parse infobox from wikitext');
}
var name = infoboxParts[1];
var params = (infoboxParts[2]) ? getTemplateParameters(infoboxParts[2]) : null;
return {
name: name,
params: params,
wikitext: infoboxParts[0]
};
};
/**
* @param {File} file source file
* @param {Number} maxResolution maximum resolution in pixels
* @returns {Promise} Promise of (1) a File with the given max resoltion, and (2) a data url of the resized image
**/
var resizeImageFile = function resizeImageFile(file, maxResolution) {
var resizeFilePromise = $.Deferred();
var origImg = document.createElement("img");
var reader = new FileReader();
reader.onload = function (e) {
origImg.addEventListener('load', function () {
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.drawImage(origImg, 0, 0);
var resolution = origImg.width * origImg.height;
var scaleFactor = (resolution > maxResolution)
? Math.sqrt(maxResolution / resolution)
: 1;
var width = origImg.width * scaleFactor;
var height = origImg.height * scaleFactor;
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(origImg, 0, 0, width, height);
var dataurl = canvas.toDataURL(file.type);
canvas.toBlob(function (blob) {
resizeFilePromise.resolve(
new File([blob], file.name, { type: file.type }),
dataurl
);
}, file.type);
}, false);
origImg.src = e.target.result;
};
reader.readAsDataURL(file);
return resizeFilePromise.promise();
};
var makeDescriptionText = function makeDescriptionText(article, developer, publisher) {
return '==Summary==\n{{Non-free use rationale video game cover\n' +
'| Article = ' + article + '\n' +
'| Use = Infobox\n' +
'| Publisher = ' + publisher + '\n' +
'| Developer = ' + developer + '\n}}\n' +
'==Licensing==\n{{Non-free video game cover}}';
};
/**
* @param {Object} api
* @param {File} file
* @param {String} text wikitext for the file description page
* @param {Object} title mw.Title object
* @returns {Promise} Promise of result object, or an error code and a jqxhr object
*/
var uploadFile = function uploadFile(api, file, text, title) {
var filename = title.getMain();
return api.postWithToken('csrf',
{
"action": "upload",
"format": "json",
"filename": filename,
"comment": "Upload cover image (using [[User:Evad37/Covery|Covery]])",
"text": text,
"file": file
},
{ contentType: 'multipart/form-data' }
/* on success, will get an object like:
{ upload:
filename: "Image_page_sandbox_1000x596.png",
imageinfo: {
bitdepth: 8,
canonicaltitle: "File:Image page sandbox 1000x596.png",
...
},
result: "Success"
}
*/
);
};
var createFileTalkpage = function (api, fileTitle) {
return api.postWithToken('csrf', {
action: 'edit',
format: 'json',
title: fileTitle.getTalkPage().toString(),
text: '{{WikiProject Video games}}',
summary: 'WikiProject tagging (using [[User:Evad37/Covery|Covery]])',
createonly: true
});
};
/**
* @param {String} pageTitle
* @returns {Promise} {wikitext: {String} Revision wikitext, timestamp: {String} last edit timestamp}
*/
var getRevisionWikitext = function getRevisionWikitext(api, pageTitle) {
return api.get({
"action": "query",
"format": "json",
"prop": "revisions",
"titles": pageTitle,
"rvprop": "timestamp|content",
"rvslots": "main"
})
.then(function (response) {
return $.map(response.query.pages, function (page) {
return {
wikitext: page.revisions[0].slots.main['*'],
timestamp: page.revisions[0].timestamp
};
})[0];
});
};
var paramByName = function paramByName(name) {
return function (param) { return param.name === name; };
};
var makeInfoboxWikitext = function makeInfoboxWikitext(originalInfobox, newParameters) {
var updatedParametersWikitext = originalInfobox.params.map(function (param) {
var updatedParam = newParameters.find(paramByName(param.name));
return param.wikitext.name + (updatedParam
? ' ' + updatedParam.value + '\n'
: param.wikitext.value);
});
var originalParametersList = originalInfobox.params.map(function (param) {
return param.name;
});
var parametersToAddWikitext = newParameters.filter(function (param) {
return !originalParametersList.includes(param.name);
}).map(function (param) {
return '|' + param.name + ' = ' + param.value + '\n';
});
return '{{' + originalInfobox.name + '\n' +
updatedParametersWikitext.join('') +
parametersToAddWikitext.join('') + '}}';
};
var updateWikitext = function (revisionWikitext, infobox, filename, caption, alt) {
if (revisionWikitext.indexOf(infobox.wikitext) === -1) {
return $.Deferred().reject('Edit conflict');
}
var newInfobox = makeInfoboxWikitext(infobox, [
{ name: 'image', value: filename },
{ name: 'caption', value: caption },
{ name: 'alt', value: alt }
]);
return revisionWikitext.replace(infobox.wikitext, newInfobox);
};
var editPage = function (api, pageTitle, wikitext, timestamp) {
return api.postWithToken('csrf', {
action: 'edit',
title: pageTitle,
text: wikitext,
summary: "Added cover image (using [[User:Evad37/Covery|Covery]])",
basetimestamp: timestamp,
nocreate: true
});
};
var updatePage = function updatePage(api, page, infobox, fileTitle, caption, alt) {
var filename = fileTitle.getMainText();
return getRevisionWikitext(api, page)
.then(function (revision) {
return $.when(
updateWikitext(revision.wikitext, infobox, filename, caption, alt),
revision.timestamp
);
})
.then(function (updatedWikitext, timestamp) {
return editPage(api, page, updatedWikitext, timestamp);
});
};
var updateTalkpageWikitext = function updateTalkpageWikitext(revisionWikitext) {
/* Redirects to {{WikiProject Video games}} :
// Template:Cvgproj (redirect page) (links | edit)
// Template:WikiProject Video Games (redirect page) (links | edit)
// Template:WPVG (redirect page) (links | edit)
// Template:Vgproj (redirect page) (links | edit)
// Template:Wpvg (redirect page) (links | edit)
// Template:WP video games (redirect page) (links | edit)
// Template:WP cvg (redirect page) (links | edit)
// Template:WikiProject Rockstar Games (redirect page) (links | edit)
// Template:WGVG (redirect page) (links | edit)
// Template:WP Video games (redirect page) (links | edit)
// Template:WikiProject VG (redirect page) (links | edit)
// Template:WikiProject video games (redirect page)
*/
var bannerPattern = /\{\{\s*([Ww](?:P|p|G|ikiProject) ?c?[Vv](?:ideo )?[Gg](?:ames)?|[Cc]?[Vv]gproj|[Ww]ikiProject Rockstar Games)\s*(\|(?:.|\n)*?(?:(?:\{\{(?:.|\n)*?(?:(?:\{\{(?:.|\n)*?\}\})(?:.|\n)*?)*?\}\})(?:.|\n)*?)*|)\}\}\n?/;
var banner = bannerPattern.exec(revisionWikitext);
var noBannerPrersent = !banner || !banner[0];
if (noBannerPrersent) {
return '{{WikiProject Video games}}\n' + revisionWikitext;
}
var noParamsInBanner = !banner[2];
if (noParamsInBanner) {
return false;
}
var params = getTemplateParameters(banner[2]);
var coverParam = getTemplateParameters(banner[2]).find(paramByName('cover'));
if (!coverParam) {
return false;
}
var updatedBannerWikitext = banner[0].replace(coverParam.wikitext.name + coverParam.wikitext.value, '');
return revisionWikitext.replace(banner[0], updatedBannerWikitext);
}
var updateTalkpage = function updateTalkpage(api, page) {
var talkpageTitle = mw.Title.newFromText(page).getTalkPage();
var talkpage = talkpageTitle && talkpageTitle.toString();
return getRevisionWikitext(api, talkpage)
.then(function (revision) {
return $.when(
updateTalkpageWikitext(revision.wikitext),
revision.timestamp
);
})
.then(function (updatedWikitext, timestamp) {
if (!updatedWikitext) {
return 'Done';
}
return editPage(api, talkpage, updatedWikitext, timestamp);
})
};
var CoveryDialog = function CoveryDialog(config) {
CoveryDialog.super.call(this, config);
}
OO.inheritClass(CoveryDialog, OO.ui.ProcessDialog);
CoveryDialog.static.name = 'coveryDialog';
CoveryDialog.static.title = 'Covery';
CoveryDialog.static.size = 'large';
CoveryDialog.static.actions = [
{ flags: 'primary', label: 'Upload', action: 'upload' },
{ flags: 'safe', label: 'Cancel' }
];
// Customize the initialize() function to add content and layouts:
CoveryDialog.prototype.initialize = function () {
CoveryDialog.super.prototype.initialize.call(this);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
/* Form content: */
this.content = new OO.ui.FieldsetLayout();
this.fileSelect = new OO.ui.SelectFileWidget();
this.urlInput = new OO.ui.TextInputWidget({
type: 'url',
placeholder: 'http://'
});
this.imagePreview = new OO.ui.LabelWidget({ label: 'preview' });
this.titleInput = new OO.ui.TextInputWidget();
this.captionInput = new OO.ui.TextInputWidget();
this.altTextInput = new OO.ui.TextInputWidget();
this.developerInput = new OO.ui.TextInputWidget();
this.publisherInput = new OO.ui.TextInputWidget();
this.fileSelectField = new OO.ui.FieldLayout(this.fileSelect, { label: 'Upload a file...', align: 'left' });
this.fileSelect.field = this.fileSelectField;
this.urlInputField = new OO.ui.FieldLayout(this.urlInput, { label: '...or enter a URL', align: 'left' });
this.urlInput.field = this.urlInputField;
this.imagePreviewField = new OO.ui.FieldLayout(this.imagePreview, { label: 'Preview:', align: 'top' });
this.titleInputField = new OO.ui.FieldLayout(this.titleInput, { label: 'File name', align: 'left' });
this.captionInputField = new OO.ui.FieldLayout(this.captionInput, { label: 'Caption', align: 'left' });
this.altTextInputField = new OO.ui.FieldLayout(this.altTextInput, { label: 'Alt text', align: 'left' });
this.developerInputField = new OO.ui.FieldLayout(this.developerInput, { label: 'Developer', align: 'left' });
this.publisherInputField = new OO.ui.FieldLayout(this.publisherInput, { label: 'Publisher', align: 'left' });
this.content.addItems([
this.fileSelectField,
this.urlInputField,
this.titleInputField,
this.imagePreviewField,
this.captionInputField,
this.altTextInputField,
this.developerInputField,
this.publisherInputField
]);
/* Progress status content: */
this.progressStatusContent = new OO.ui.FieldsetLayout({ label: 'Status' });
this.progressBar = new OO.ui.ProgressBarWidget({
progress: 0
});
this.progressField = new OO.ui.FieldLayout(this.progressBar, { label: '', align: 'below' });
this.progressStatusContent.addItems([this.progressField]);
this.progressStatusContent.toggle(false); //hide
this.panel.$element.append([
this.content.$element,
this.progressStatusContent.$element
]);
this.$body.append(this.panel.$element);
this.fileSelect.connect(this, { 'change': 'onFileSelectChange' });
this.urlInput.connect(this, { 'change': 'onUrlInputChange' });
this.titleInput.connect(this, { 'flag': 'onTitleInputFlag' });
this.developerInput.connect(this, { 'change': 'onRequiredInputChange' });
this.publisherInput.connect(this, { 'change': 'onRequiredInputChange' });
};
CoveryDialog.prototype.onFileChosen = function (filePromise, widgetUsed, otherWidget) {
widgetUsed.pushPending();
widgetUsed.field.setErrors([]);
otherWidget.setDisabled(true);
var self = this;
$.when(filePromise)
.then(function (file) {
return resizeImageFile(file, 100000);
})
.then(
function (resizedFile, resizedDataURL) {
self.resizedFile = resizedFile;
self.imagePreview.$element.empty().show().append(
$('<img>').attr('src', resizedDataURL)
);
widgetUsed.popPending();
widgetUsed.setIndicator('required');
otherWidget.setDisabled(false);
otherWidget.setIndicator(null);
if (resizedFile.name) { self.titleInput.setValue(resizedFile.name); }
self.onRequiredInputChange();
},
function (code) {
var errorMessage = (code) ? 'An error occured: ' + code : 'An unexpected error occured';
self.resizedFile = null;
widgetUsed.popPending();
widgetUsed.setIndicator('clear');
widgetUsed.field.setErrors([errorMessage]);
otherWidget.setDisabled(false);
otherWidget.setIndicator(null);
self.onRequiredInputChange();
}
);
};
CoveryDialog.prototype.onFileSelectChange = function (file) {
this.onFileChosen(file, this.fileSelect, this.urlInput);
}
CoveryDialog.prototype.onUrlInputChange = function (value) {
if (!value) {
this.urlInput.setIcon(null);
return;
}
var hasImageExtension = /\.(?:gif|png|jpe?g|svg|tiff?)$/i.test(value);
if (!hasImageExtension) {
this.urlInput.setIcon('ellipsis');
return;
}
var filePromise = fetch(value).then(function (result) { return result.blob(); });
this.onFileChosen(filePromise, this.urlInput, this.fileSelect);
};
CoveryDialog.prototype.onTitleInputFlag = function (flag) {
if (flag.invalid === true) {
this.actions.setAbilities({
upload: false
});
} else if (flag.invalid === false) {
this.onRequiredInputChange();
}
}
// Only allow uploading if requirements are met
CoveryDialog.prototype.onRequiredInputChange = function (change) {
var self = this;
$.when(change && change.titleIsValid || this.titleInput.getValidity())
.then(
function () {
var requirementsMet = (
!self.fileSelect.isPending() &&
!self.urlInput.isPending() &&
!!self.resizedFile &&
!!self.titleInput.getValue().length &&
!!self.developerInput.getValue().length &&
!!self.publisherInput.getValue().length
);
self.actions.setAbilities({
upload: requirementsMet
});
},
function () {
self.actions.setAbilities({
upload: false
});
}
);
};
// Specify the dialog height (or don't to use the automatically generated height).
CoveryDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
// Set up the window with data passed to it at the time of opening.
CoveryDialog.prototype.getSetupProcess = function (data) {
data = data || {};
return CoveryDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.api = data.api;
this.infobox = data.infobox;
this.pageName = data.pageName;
var developerParam = data.infobox.params.find(paramByName('developer'));
var publisherParam = data.infobox.params.find(paramByName('publisher'));
this.developerInput.setValue(developerParam && developerParam.value || '');
this.publisherInput.setValue(publisherParam && publisherParam.value || '');
this.titleInput.setValidation(function (value) {
var title = mw.Title.newFromFileName(value);
if (title === null) {
return false;
};
return data.api.get({
action: "query",
format: "json",
prop: "imageinfo",
titles: title.toString(),
iiprop: ""
}).then(function (response) {
return $.map(response.query.pages, function (page) {
return page.missing === "" && page.imagerepository === "";
})[0];
});
});
}, this);
};
CoveryDialog.prototype.setProgressStatus = function (label, progress) {
this.progressBar.setProgress(progress);
this.progressField.setLabel(label);
}
// Specify processes to handle the actions.
CoveryDialog.prototype.getActionProcess = function (action) {
if (action === 'upload') {
this.content.toggle(false); // hide
this.progressStatusContent.toggle(true); // show
this.setProgressStatus('Uploading...', 1);
var fileTitle = mw.Title.newFromFileName(this.titleInput.getValue());
return new OO.ui.Process(function () {
return this.uploaded || uploadFile(
this.api,
this.resizedFile,
makeDescriptionText(
this.pageName,
this.developerInput.getValue(),
this.publisherInput.getValue()
),
fileTitle
).then(
function () { return true; },
function (errorCode) { return $.Deferred().reject(new OO.ui.Error('Error uploading: ' + errorCode)) }
);
}, this)
.next(function () {
this.uploaded = true;
this.setProgressStatus('Uploaded file!', 25);
}, this)
.next(function () {
this.setProgressStatus('Uploaded file! Creating file talk page...', 26);
return this.createdFileTalkpage || createFileTalkpage(
this.api,
fileTitle
).then(
function () { return true; },
function (errorCode) { return $.Deferred().reject(new OO.ui.Error('Error creating file talk page: ' + errorCode)) }
);
}, this)
.next(function () {
this.createdFileTalkpage = true;
this.setProgressStatus('Uploaded file! Created file talk page!', 50);
}, this)
.next(function () {
this.setProgressStatus('Uploaded file! Created file talk page! Updating article...', 51);
return this.updatedArticle || updatePage(
this.api,
this.pageName,
this.infobox,
fileTitle,
this.captionInput.getValue(),
this.altTextInput.getValue()
).then(
function () { return true; },
function (errorCode) { return $.Deferred().reject(new OO.ui.Error('Error editing article: ' + errorCode)) }
);
}, this)
.next(function () {
this.updatedArticle = true;
this.setProgressStatus('Uploaded file! Created file talk page! Updated article!', 75);
}, this)
.next(function () {
this.setProgressStatus('Uploaded file! Created file talk page! Updated article! Updating article talk page...', 76);
return updateTalkpage(
this.api,
this.pageName
).then(
function () { return true; },
function (errorCode) { return $.Deferred().reject(new OO.ui.Error('Error editing article talk page: ' + errorCode)) }
);
}, this)
.next(function () {
this.setProgressStatus('All done! Reloading article...', 100);
return 1200;
}, this)
.next(function () {
return this.close({ sucess: true });
}, this);
} else if (action === 'cancel') {
return new OO.ui.Process(function () {
return this.close();
}, this);
}
// Fallback to parent handler
return CoveryDialog.super.prototype.getActionProcess.call(this, action);
};
// Use the getTeardownProcess() method to perform actions whenever the dialog is closed.
// This method provides access to data passed into the window's close() method
// or the window manager's closeWindow() method.
CoveryDialog.prototype.getTeardownProcess = function (data) {
return CoveryDialog.super.prototype.getTeardownProcess.call(this, data)
.first(function () {
// Perform any cleanup as needed
}, this);
};
var showDialog = function showDialog(data) {
var coveryWindowFactory = new OO.Factory();
coveryWindowFactory.register(CoveryDialog);
var mainWindowManager = new OO.ui.WindowManager({ factory: coveryWindowFactory });
$('body').append(mainWindowManager.$element);
var instance = mainWindowManager.openWindow('coveryDialog', data);
return instance.closed;
};
var startCovery = function startCovery(api, pageName) {
return getLeadWikitext(api, pageName)
.then(getInfoboxTemplate)
.then(function (infobox) {
return showDialog({
api: api,
pageName: pageName,
infobox: infobox
});
})
.then(function (data) {
if (data && data.sucess) {
___location.reload();
}
});
};
$.when(
isSuitable(),
loadDepenedencies(),
$.ready()
).then(function (config) {
var portletLink = mw.util.addPortletLink('p-tb', '#', 'Upload cover', 'tb-covery');
$(portletLink).click(function (e) {
e.preventDefault();
var api = new mw.Api({
ajax: {
headers: {
'Api-User-Agent': 'Covery/1.0.0 ( https://en.wikipedia.org/wiki/User:Evad37/Covery )'
}
}
});
startCovery(api, config.wgPageName);
});
});
if (mw.config.get('wgUserName') === 'Evad37') {
$.when(
loadDepenedencies(),
setupForTesting(),
$.ready()
).then(function () {
var FakeApi = function () {
this.realApi = new mw.Api({
ajax: {
headers: {
'Api-User-Agent': 'Covery/1.0.0 ( https://en.wikipedia.org/wiki/User:Evad37/Covery )'
}
}
});
};
FakeApi.prototype.get = function (query) {
return this.realApi.get(query);
};
FakeApi.prototype.postWithToken = function (token, params) {
console.log(params);
return $.Deferred().resolve({ result: true });
};
var portletLink = mw.util.addPortletLink('p-tb', '#', 'Test covery', 'tb-testcovery');
$(portletLink).click(function (e) {
e.preventDefault();
startCovery(new FakeApi, mw.config.get('wgPageName'));
});
/*
QUnit.module("Name for group of tests");
QUnit.test("Some test", function( assert ) {
assert.ok(someCondition, "Description");
assert.notOk(someCondition, "Description");
assert.equal(firstVar, secondVar, "Description")
assert.deepEqual(firstObject, secondObject, "Description")
});
QUnit.test("Some async test", function( assert ) {
assert.expect();
var done = assert.async();
$.when(
// Some async code...
).then(function() {
// asserts go here, then...
done();
})
});
*/
})
}
// </nowiki>