MediaWiki:Common.js
Jump to navigation
Jump to search
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)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/*Portal*/
mw.loader.using( ['jquery.ui.tabs'], function() {
$(function() {
var $tabs
= $("#portal_slider").tabs({ fx: {opacity:'toggle', duration:100} } );
$("[class^=portal_sliderlink]").click(function() { // bind click event to link
$tabs.tabs('select', this.className.replace("portal_sliderlink_", ""));
setTimeout(function() {
$(window).scroll();
}, 1000);
return false;
});
});
});
mw.hook('wikipage.content').add(function($content){
logToConsole("contentHook triggered.");
ColorGuide.initialize();
});
function logToConsole(message){
if(window.console && window.console.log){
console.log(message);
}
}
// Color Guide
//-------------
(function (ColorGuide) {
// constants for ColorGuide element IDs
/** @constant */ var CLASS_PREVIEW_CANVAS = "colorPreviewCanvasHolder";
/** @constant */ var CLASS_PRIMARY_PICKER = "primaryColorPickerHolder";
/** @constant */ var CLASS_SECONDARY_PICKER = "secondaryColorPickerHolder";
/** @constant */ var CLASS_TERTIARY_PICKER = "tertiaryColorPickerHolder";
/** @constant */ var CLASS_BACKGROUND_PICKER = "backgroundColorPickerHolder";
/** @constant */ var CLASS_APPLY_BUTTON = "colorGuideApplyColorButtonHolder";
// constants for allowed dataset attributes on ColorGuide elements
// for example 'MyThing' would resolve to the attribute 'data-my-thing' in HTML
// for more information on 'dataset', see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset
/** @constant
* passes a style to the generated child element */
var DATASET_STYLE = "style";
/** @constant
* wikiFile pointing to the rendered detailTexture */
var DATASET_DETAIL_IMAGE = "detailImgSrc";
/** @constant
* wikiFile pointing to the rendered colorTexture*/
var DATASET_COLOR_IMAGE = 'colorImgSrc';
/** @constant
* preferred width of the canvas in pixels ('123') or fraction of screen width ('123%') */
var DATASET_CANVAS_WIDTH = "canvasWidth";
/** @constant
* maximum width of the canvas in pixels ('123') or fraction of screen width ('123%') */
var DATASET_CANVAS_MAX_WIDTH = "canvasMaxWidth";
/** @constant
* set an initial value for the color pickers ('#RRGGBB')*/
var DATASET_DEFAULT_VALUE = "defaultValue";
/** @constant
* assign element to a group if multiple ColorGuides are on one page */
var DATASET_GROUP = "group";
// constant lists of element classes that make up a valid ColorGuide instance
/** @constant
* @type {string[]} */
var INSTANCE_REQUIRED_ELEMENTS = [CLASS_PREVIEW_CANVAS, CLASS_PRIMARY_PICKER, CLASS_SECONDARY_PICKER, CLASS_TERTIARY_PICKER, CLASS_APPLY_BUTTON];
/** @constant
* @type {string[]} */
var INSTANCE_OPTIONAL_ELEMENTS = [CLASS_BACKGROUND_PICKER];
// constant map of ColorGuide elements and their allowed attributes
/** @typedef {{requiredDataSets: string[], optionalDataSets: string[]}} AllowedDataSets */
/** @constant
* @type {Object.<string, AllowedDataSets>} */
var INSTANCE_ELEMENT_ATTRIBUTES = (function () {
/** @type {Object.<string, AllowedDataSets>} */
var result = {};
result[CLASS_PREVIEW_CANVAS] = {
requiredDataSets: [DATASET_DETAIL_IMAGE, DATASET_COLOR_IMAGE, DATASET_CANVAS_WIDTH],
optionalDataSets: [DATASET_CANVAS_MAX_WIDTH, DATASET_STYLE, DATASET_GROUP],
};
result[CLASS_PRIMARY_PICKER] = {
requiredDataSets: [],
optionalDataSets: [DATASET_DEFAULT_VALUE, DATASET_STYLE, DATASET_GROUP],
};
result[CLASS_SECONDARY_PICKER] = {
requiredDataSets: [],
optionalDataSets: [DATASET_DEFAULT_VALUE, DATASET_STYLE, DATASET_GROUP],
};
result[CLASS_TERTIARY_PICKER] = {
requiredDataSets: [],
optionalDataSets: [DATASET_DEFAULT_VALUE, DATASET_STYLE, DATASET_GROUP],
};
result[CLASS_BACKGROUND_PICKER] = {
requiredDataSets: [],
optionalDataSets: [DATASET_DEFAULT_VALUE, DATASET_STYLE, DATASET_GROUP],
};
result[CLASS_APPLY_BUTTON] = {
requiredDataSets: [],
optionalDataSets: [DATASET_STYLE, DATASET_GROUP],
};
return result;
})();
/**
* @param expectedClasses: {string[]} list of expected classes
* @param foundClasses: {string[]} list of found classes
* @returns string[] list of missing classes, or empty list
*/
function findMissingClasses(expectedClasses, foundClasses) {
var missingClasses = [];
var i;
for (i = 0; i < expectedClasses.length; i++) {
missingClasses.push(expectedClasses[i]);
}
for (i = 0; i < foundClasses.length; i++) {
var foundClass = foundClasses[i];
var foundIndex = missingClasses.indexOf(foundClass);
if (foundIndex >= 0) {
missingClasses.splice(foundIndex, 1);
}
}
return missingClasses;
}
/**
* @returns {Object.<string, (HTMLElement || null)[]>} valid groups by groupName, containing the [requiredElements..., optionalElements...] in order
*/
function validateInstances() {
/** @type {string[]} */
var groupList = [];
/** @type {Object.<string, HTMLElement[]>} */
var groupElements = {};
/** @type {Object.<string, string[]>} */
var groupElementClasses = {};
var i, group, elementIdx, className;
for (i = 0; i < INSTANCE_REQUIRED_ELEMENTS.length; i++) {
className = INSTANCE_REQUIRED_ELEMENTS[i];
var classElements = document.getElementsByClassName(className);
for (elementIdx = 0; elementIdx < classElements.length; elementIdx++) {
/** @type {HTMLElement} */
var element = classElements.item(elementIdx);
group = element.dataset[DATASET_GROUP] || "__null";
groupList.push(group);
var arr = groupElements[group] || [];
arr.push(element);
groupElements[group] = arr;
var arr2 = groupElementClasses[group] || [];
arr2.push(className);
groupElementClasses[group] = arr2;
}
}
/** @type {Object.<string, (HTMLElement || null)[]>} */
var validInstances = {};
for (i = 0; i < groupList.length; i++) {
group = groupList[i];
var missingClasses = findMissingClasses(INSTANCE_REQUIRED_ELEMENTS, groupElementClasses[group]);
if (missingClasses.length > 0) {
logToConsole("Error: ColorGuide group '" + group + "' was missing elements: " + JSON.stringify(missingClasses));
} else {
validInstances[group] = groupElements[group];
}
}
// for every valid group, append optional elements || null
var validGroupList = Object.keys(validInstances);
for (var optionalIdx = 0; optionalIdx < INSTANCE_OPTIONAL_ELEMENTS.length; optionalIdx++) {
/** @type {Object.<string, HTMLElement>} */
var foundOptionals = {};
var optionalClassName = INSTANCE_OPTIONAL_ELEMENTS[optionalIdx];
var optionalClassElements = document.getElementsByClassName(optionalClassName);
for (elementIdx = 0; elementIdx < optionalClassElements.length; elementIdx++) {
/** @type {HTMLElement} */
var optionalElement = optionalClassElements[elementIdx];
group = optionalElement.dataset[DATASET_GROUP] || "__null";
if (validGroupList.indexOf(group) < 0) {
logToConsole("Error: ColorGuide element '" + optionalClassName + "' has invalid group: '" + group + "'");
continue;
}
foundOptionals[group] = optionalElement;
}
for (i = 0; i < validGroupList.length; i++) {
group = validGroupList[i];
validInstances[group].push(foundOptionals[group] || null);
}
}
// verify required dataSet attributes
var classSequence = INSTANCE_REQUIRED_ELEMENTS.concat(INSTANCE_OPTIONAL_ELEMENTS);
for (var j = 0; j < validGroupList.length; j++) {
group = validGroupList[j];
var instanceElements = validInstances[group];
var validDataSet = true;
for (i = 0; i < classSequence.length; i++) {
className = classSequence[i];
var instanceElement = instanceElements[i];
var requiredDataSets = INSTANCE_ELEMENT_ATTRIBUTES[className].requiredDataSets;
for (var dataSetIdx = 0; dataSetIdx < requiredDataSets.length; dataSetIdx++) {
var requiredDataSet = requiredDataSets[dataSetIdx];
if (!(requiredDataSet in instanceElement.dataset)) {
logToConsole("Error: ColorGuide element '" + className + "' of group '" + group
+ "' is missing dataSet attribute '" + requiredDataSet + "'");
validDataSet = false;
break;
}
}
if (!validDataSet) {
break;
}
}
if (!validDataSet) {
delete validInstances[group];
}
}
return validInstances;
}
/** @typedef {function():number} canvasWidthProvider */
/** @typedef {{
* previewCanvas: HTMLCanvasElement,
* widthProvider: canvasWidthProvider,
* detailCanvas: HTMLCanvasElement,
* detailImage: HTMLImageElement,
* colorCanvas: HTMLCanvasElement,
* colorImage: HTMLImageElement,
* primaryPicker: HTMLInputElement,
* secondaryPicker: HTMLInputElement,
* tertiaryPicker: HTMLInputElement,
* backgroundPicker: HTMLInputElement | null
* }} ColorGuideInstance */
/**
* @param {string | null} text
* @returns {{pixels: (number | null), percentage: (number | null)}}
*/
function parseSizeText(text) {
/** @type {number | null} */
var pixelsValue = null;
/** @type {number | null} */
var percentageValue = null;
if (text) {
if (text.charAt(text.length - 1) === '%') {
percentageValue = parseInt(text.slice(0, text.length - 1));
} else {
pixelsValue = parseInt(text);
}
}
return {pixels: pixelsValue, percentage: percentageValue};
}
/**
* uses the mediaWiki api to acquire an object containing the url of an image-file from the wiki
* @param {*} imageName fileName of the image to find
* @param {*} callback function to be called with the queried json object
*/
function queryImageSource(imageName, callback) {
var api = new mw.Api();
api.get({
action: 'query',
titles: imageName,
prop: 'imageinfo',
iiprop: 'url',
format: 'json'
})
.done(callback)
.fail(function (jqXHR, textStatus, errorThrown) {
logToConsole("query failed, error: " + errorThrown);
});
}
/**
* acquires the image url from a (json) data-object obtained by using `queryImageSource`
* @param {*} data json data object containing the path `query.pages.{pageid}.imageinfo[0].url`
* @returns url of image or null if path not found
*/
function getImageSource(data) {
if (!data.hasOwnProperty('query') || !data.query.hasOwnProperty('pages') || Object.keys(data.query.pages).length <= 0) {
return null;
}
var pageKeys = Object.keys(data.query.pages);
if (pageKeys.length <= 0) {
return null;
}
//we only expect one page, so just get whatever the first page happens to be
var page = data.query.pages[pageKeys[0]];
if (!page.hasOwnProperty('imageinfo') || page.imageinfo.length <= 0 || !page.imageinfo[0].hasOwnProperty('url')) {
return null;
}
return page.imageinfo[0].url;
}
/**
* @param container: {HTMLElement}
* @param dataSetAttribute: {string}
* @param onLoadCallback: {}
* @returns {HTMLImageElement}
*/
function addImageHolder(container, dataSetAttribute, onLoadCallback) {
var image = new Image();
image.onload = onLoadCallback;
image.crossOrigin = "anonymous";
image.style.cssText = "display:none";
var imageSourceText = container.dataset[dataSetAttribute];
queryImageSource(imageSourceText, function (data) {
if (data === null) {
logToConsole("image query callback data was undefined or null!");
return;
}
var imageSource = getImageSource(data);
if (imageSource === null) {
logToConsole("unable to find imageSource for file '" + imageSourceText + "' in query result! (probably invalid fileName specified)");
return;
}
image.src = imageSource;
container.appendChild(image);
});
return image;
}
/**
* @param container: {HTMLElement | null}
* @returns {HTMLInputElement | null}
*/
function addColorPicker(container) {
if (container === null) {
return null;
}
//<input type="color" value="value">
var picker = document.createElement("input");
picker.type = "color";
picker.value = container.dataset[DATASET_DEFAULT_VALUE] || "#000000";
picker.style.cssText = container.dataset[DATASET_STYLE];
container.appendChild(picker);
return picker;
}
/**
* @param group: {string} group-name
* @param elements: {HTMLElement[]} elements in order of [requiredElements..., optionalElements...]
* @returns {ColorGuideInstance}
*/
function initializeColorGuideInstance(group, elements) {
var classSequence = INSTANCE_REQUIRED_ELEMENTS.concat(INSTANCE_OPTIONAL_ELEMENTS);
function getElement(elementClass) {
return elements[classSequence.indexOf(elementClass)];
}
var canvasDiv = getElement(CLASS_PREVIEW_CANVAS);
var targetWidth = parseSizeText(canvasDiv.dataset[DATASET_CANVAS_WIDTH] || "640");
var maxWidth = parseSizeText(canvasDiv.dataset[DATASET_CANVAS_MAX_WIDTH] || null);
var previewCanvas = document.createElement("CANVAS");
previewCanvas.style.cssText = canvasDiv.dataset[DATASET_STYLE] || "";
canvasDiv.appendChild(previewCanvas);
var applyButtonDiv = getElement(CLASS_APPLY_BUTTON);
var applyButton = document.createElement("input");
applyButton.type = "button";
applyButton.value = "Apply Colors";
applyButton.style.cssText = applyButtonDiv.dataset[DATASET_STYLE];
applyButton.onclick = function () {ColorGuide.applyColors(group);};
applyButtonDiv.appendChild(applyButton);
/** @type {ColorGuideInstance} */
var instance = {};
instance.previewCanvas = previewCanvas;
instance.widthProvider = function widthProvider() {
var targetPixels = targetWidth.pixels != null ? targetWidth.pixels : (window.innerWidth * targetWidth.percentage / 100);
if (maxWidth.pixels === null && maxWidth.percentage === null) {
return targetPixels;
}
var maxPixels = maxWidth.pixels != null ? maxWidth.pixels : (window.innerWidth * maxWidth.percentage / 100);
return Math.floor(Math.min(targetPixels, maxPixels));
};
instance.detailCanvas = document.createElement("CANVAS");
instance.colorCanvas = document.createElement("CANVAS");
var imagesToLoad = 2;
var onImageLoadCallback = function () {
imagesToLoad--;
if (imagesToLoad === 0) {
ColorGuide.applyColors(group);
}
};
instance.detailImage = addImageHolder(canvasDiv, DATASET_DETAIL_IMAGE, onImageLoadCallback);
instance.colorImage = addImageHolder(canvasDiv, DATASET_COLOR_IMAGE, onImageLoadCallback);
instance.primaryPicker = addColorPicker(getElement(CLASS_PRIMARY_PICKER));
instance.secondaryPicker = addColorPicker(getElement(CLASS_SECONDARY_PICKER));
instance.tertiaryPicker = addColorPicker(getElement(CLASS_TERTIARY_PICKER));
instance.backgroundPicker = addColorPicker(getElement(CLASS_BACKGROUND_PICKER));
return instance;
}
/**
* @returns {Object.<string, ColorGuideInstance>}
*/
function initializeInstances() {
var result = {};
var validInstances = validateInstances();
var groupList = Object.keys(validInstances);
for (var i = 0; i < groupList.length; i++) {
var group = groupList[i];
result[group] = initializeColorGuideInstance(group, validInstances[group]);
}
return result;
}
/** @type {Object.<string, ColorGuideInstance>} */
ColorGuide.instancesByGroup = null;
ColorGuide.initialize = function initialize() {
if (ColorGuide.instancesByGroup === null) {
ColorGuide.instancesByGroup = initializeInstances();
}
};
ColorGuide.applyColors = function applyColors(group) {
var instance = ColorGuide.instancesByGroup[group];
if (instance.detailImage.width !== instance.colorImage.width || instance.detailImage.height !== instance.colorImage.height) {
logToConsole("detailImage and colorImage for group '" + group + "' have different dimensions!");
return;
}
var targetWidth = instance.widthProvider();
var targetHeight = getTargetHeight(instance.detailImage, targetWidth);
instance.previewCanvas.width = targetWidth;
instance.previewCanvas.height = targetHeight;
loadImageToCanvas(instance.detailImage, instance.detailCanvas, targetWidth, targetHeight);
loadImageToCanvas(instance.colorImage, instance.colorCanvas, targetWidth, targetHeight);
if (!allDimensionsMatching({width: targetWidth, height: targetHeight}, instance.detailCanvas, instance.colorCanvas, instance.previewCanvas)) {
logToConsole("mismatching dimensions of detailCanvas, colorCanvas and previewCanvas | group: '" + group + "'");
return;
}
var color1 = hexToRgb(instance.primaryPicker.value);
var color2 = hexToRgb(instance.secondaryPicker.value);
var color3 = hexToRgb(instance.tertiaryPicker.value);
if (color1 == null || color2 == null || color3 == null) {
logToConsole("unable to parse color input for group '" + group + "'");
return;
}
if (instance.backgroundPicker != null) {
instance.previewCanvas.style.backgroundColor = instance.backgroundPicker.value;
} else {
instance.previewCanvas.style.backgroundColor = "transparent";
}
buildResultCanvas(group, instance, color1, color2, color3);
};
/**
* builds the displayCanvas given a loaded detail- and color-canvas, along with the target primary, secondary and tertiary colors
* @param group: {string}
* @param instance: {ColorGuideInstance}
* @param primaryColor: {r: number, g: number, b: number}
* @param secondaryColor: {r: number, g: number, b: number}
* @param tertiaryColor: {r: number, g: number, b: number}
*/
function buildResultCanvas(group, instance, primaryColor, secondaryColor, tertiaryColor) {
var width = instance.previewCanvas.width;
var height = instance.previewCanvas.height;
var detailData = instance.detailCanvas.getContext("2d").getImageData(0, 0, width, height);
var colorData = instance.colorCanvas.getContext("2d").getImageData(0, 0, width, height);
var displayData = new ImageData(width, height);
var redFactor, greenFactor, blueFactor, blackFactor, targetRed, targetGreen, targetBlue, detailRed, detailGreen, detailBlue;
for (var i = 0; i < detailData.data.length; i += 4) {
redFactor = colorData.data[i] / 255;
greenFactor = colorData.data[i + 1] / 255;
blueFactor = colorData.data[i + 2] / 255;
blackFactor = 1.0 - Math.min(redFactor + greenFactor + blueFactor, 1.0);
targetRed = blackFactor * primaryColor.r + blueFactor * secondaryColor.r + greenFactor * tertiaryColor.r;
targetGreen = blackFactor * primaryColor.g + blueFactor * secondaryColor.g + greenFactor * tertiaryColor.g;
targetBlue = blackFactor * primaryColor.b + blueFactor * secondaryColor.b + greenFactor * tertiaryColor.b;
detailRed = detailData.data[i];
detailGreen = detailData.data[i + 1];
detailBlue = detailData.data[i + 2];
displayData.data[i] = detailRed * lerp(targetRed, 255, redFactor) / 255;
displayData.data[i + 1] = detailGreen * lerp(targetGreen, 255, redFactor) / 255;
displayData.data[i + 2] = detailBlue * lerp(targetBlue, 255, redFactor) / 255;
displayData.data[i + 3] = detailData.data[i + 3];
}
instance.previewCanvas.getContext("2d").putImageData(displayData, 0, 0);
}
/**
* validates that all passed canvases have the same dimensions as targetDimensions
* @param targetDimensions: {width: {number}, height: {number}}
* @param arguments: {HTMLCanvasElement...} variable amount of canvases to be checked
* @returns boolean value, true when all Dimensions match, false when they don't
*/
function allDimensionsMatching(targetDimensions) {
for (var i = 1; i < arguments.length; i++) {
if (arguments[i].width !== targetDimensions.width) {
return false;
}
if (arguments[i].height !== targetDimensions.height) {
return false;
}
}
return true;
}
/**
* works just like unity's lerp (minus the clamping)
* @param {*} a startValue (mapped to t = 0)
* @param {*} b endValue (mapped to t = 1)
* @param {*} t
* @returns the value of f(t) given f(0) = a and f(1) = b, where f is a linear function
*/
function lerp(a, b, t) {
return a + ((b - a) * t);
}
/**
* takes a hex color (#rrggbb) and turns it into an rgb object
* @param {string} hex
* @returns {{r: number, g: number, b: number} | null} if the hex color was invalid or an object containing keys{'r', 'g', 'b'}
*/
function hexToRgb(hex) {
var result = /^#?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
/**
* @param image: {number}
* @param targetWidth: {number}
* @returns {number}
*/
function getTargetHeight(image, targetWidth) {
return Math.floor(targetWidth / image.width * image.height);
}
/**
* loads an image to the canvas and rescales both to the targetDimensions
* @param image: {HTMLImageElement}
* @param canvas: {HTMLCanvasElement}
* @param targetWidth: {number}
* @param targetHeight: {number}
*/
function loadImageToCanvas(image, canvas, targetWidth, targetHeight) {
canvas.width = targetWidth;
canvas.height = targetHeight;
var context = canvas.getContext("2d");
context.drawImage(image, 0, 0, targetWidth, targetHeight);
}
})(window.ColorGuide = window.ColorGuide || {});
/* UploadMultipleFiles */
mw.config.set('UMFBypassLicenseCheck',true);
/**
* Make sidebar sections collapsible
*/
$(function(){
$panel = $('#mw-panel');
$("#mw-panel .portal").each(function(index, el){
var $el = $(el);
var $id = $el.attr("id");
if(!$id){
return;
}
// for < 1366px
$el.removeClass('expanded');
// for >= 1366px
if(localStorage.getItem('sidebar_c_'+$id) === "y"){
$el.addClass('collapsed').find('.body').slideUp(0);
}
});
$("#mw-panel .portal").on("click", "h3", function(event){
var $el = $(this).parent();
var $id = $el.attr("id");
if(!$id){
return;
}
event.stopPropagation();
if($panel.width() < 200){
$el.toggleClass('collapsed');
if($el.hasClass('collapsed')){ // more consistent between class and slide status.
localStorage.setItem('sidebar_c_'+$id, "y");
$el.find('.body').slideUp('fast');
}
else{
localStorage.setItem('sidebar_c_'+$id, "n");
$el.find('.body').slideDown('fast');
}
}
else{
$("#mw-panel .portal").not($el).removeClass('expanded');
$el.toggleClass('expanded');
}
});
});