MediaWiki:Common.js
Jump to navigation
Jump to search
Note: After saving, 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: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/* Any JavaScript here will be loaded for all users on every page load. */
var filters = {
basic: {
vendor: "vendor",
type: "type",
sfosversions: "SailfishOS versions"
},
advanced: { // advanced filters have to be expanded to be seen
ram: "RAM",
soc: "SoC",
cpucores: "CPU cores",
cpufreq: "CPU frequency",
screendimension: "screen dimension"
}
};
// default filter
var default_hash_string = 'vendor=;';
var prefixes = ["some text here that (hopefully) never will be part of a value", "M", "G", "T"];
// you can add your own sort funtion to sort items in the dropdowns
var sort_functions = {
/*type: function(a, b) {
var values = ["phone", "phablet", "tablet"]; // all other types will be appended behind these
a_val = values.indexOf(a);
b_val = values.indexOf(b);
return (a_val == -1 ? 2147483647 : a_val) - (b_val == -1 ? 2147483647 : b_val);
},*/
cpucores: function(a, b) {
var values = ["single", "dual", "tri", "quad", "penta", "hexa", "octo"];
return values.indexOf(a) - values.indexOf(b);
},
sfosversions: function(a, b) {
return (a - b);
},
cpufreq: function(a, b) {
var a_val = parseFloat(a);
var b_val = parseFloat(b);
if (isNaN(a_val)) {
return 1;
}
if (isNaN(b_val)) {
return -1;
}
var i = 0;
while (prefixes[i] && a.indexOf(prefixes[i]) == -1) {
a_val *= 1000;
i++;
}
i = 0;
while (prefixes[i] && b.indexOf(prefixes[i]) == -1) {
b_val *= 1000;
i++;
}
return (a_val - b_val);
},
screendimension: function(a, b) {
var a_val = parseFloat(a);
var b_val = parseFloat(b);
if (isNaN(a_val)) {
return 1;
}
if (isNaN(b_val)) {
return -1;
}
return (a_val - b_val);
}
};
sort_functions.ram = sort_functions.cpufreq;
// no need to edit something behind this
/*** utility functions ***/
// from https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(searchElement /*, fromIndex */ ) {
"use strict";
if (this == null) {
throw new TypeError();
}
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) {
return -1;
}
var n = 0;
if (arguments.length > 1) {
n = Number(arguments[1]);
if (n != n) { // shortcut for verifying if it's NaN
n = 0;
} else if (n != 0 && n != Infinity && n != -Infinity) {
n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
}
if (n >= len) {
return -1;
}
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) {
return k;
}
}
return -1;
};
}
// Array Remove - by John Resig (MIT Licensed), from http://ejohn.org/blog/javascript-array-remove/
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
function findPos(obj) {
var curleft = 0;
var curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
}
while (obj = obj.offsetParent);
}
return {left: curleft, top: curtop};
}
function clickElement($elem) {
if ($elem.click) {
$elem.click();
}
else {
var evt = document.createEvent("MouseEvents");
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
$elem.dispatchEvent(evt);
}
}
// from http://davidwalsh.name/javascript-clone
function clone (src) {
function mixin (dest, source, copyFunc) {
var name, s, i, empty = {};
for (name in source) {
// the (!(name in empty) || empty[name] !== s) condition avoids copying properties in "source"
// inherited from Object.prototype. For example, if dest has a custom toString() method,
// don't overwrite it with the toString() method that source inherited from Object.prototype
s = source[name];
if (!(name in dest) || (dest[name] !== s && (!(name in empty) || empty[name] !== s))) {
dest[name] = copyFunc ? copyFunc(s) : s;
}
}
return dest;
}
if (!src || typeof src != "object" || Object.prototype.toString.call(src) === "[object Function]") {
// null, undefined, any non-object, or function
return src; // anything
}
if (src.nodeType && "cloneNode" in src) {
// DOM Node
return src.cloneNode(true); // Node
}
if (src instanceof Date) {
// Date
return new Date(src.getTime()); // Date
}
if (src instanceof RegExp) {
// RegExp
return new RegExp(src); // RegExp
}
var r, i, l;
if (src instanceof Array) {
// array
r = [];
for (i = 0, l = src.length; i < l; ++i) {
if (i in src) {
r.push(clone(src[i]));
}
}
// we don't clone functions for performance reasons
// }else if(d.isFunction(src)){
// // function
// r = function(){ return src.apply(this, arguments); };
}
else {
// generic objects
r = src.constructor ? new src.constructor() : {};
}
return mixin(r, src, clone);
}
function isEmpty(o) {
for (var p in o) {
if (!o.hasOwnProperty || o.hasOwnProperty(p))
return false;
}
return true;
}
function listen(el, evnt, func, bubble) {
if (el.addEventListener) { // W3C DOM
el.addEventListener(evnt, func, bubble ? true : false);
}
else if (el.attachEvent) { // IE DOM
el.attachEvent("on"+evnt, func);
}
}
function unlisten(el, evnt, func, bubble) {
if (el.removeEventListener) { // W3C DOM
el.removeEventListener(evnt, func, bubble ? true : false);
}
else if (el.detachEvent) { // IE DOM
el.detachEvent("on"+evnt, func);
}
}
/*!
* contentloaded.js
*
* Author: Diego Perini (diego.perini at gmail.com)
* Summary: cross-browser wrapper for DOMContentLoaded
* Updated: 20101020
* License: MIT
* Version: 1.2
*
* URL:
* http://javascript.nwbox.com/ContentLoaded/
* http://javascript.nwbox.com/ContentLoaded/MIT-LICENSE
*
*/
// @win window reference
// @fn function reference
function contentLoaded(win, fn) {
var done = false, top = true,
doc = win.document, root = doc.documentElement,
add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
pre = doc.addEventListener ? '' : 'on',
init = function(e) {
if (e.type == 'readystatechange' && doc.readyState != 'complete') return;
(e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
if (!done && (done = true)) fn.call(win, e.type || e);
},
poll = function() {
try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
init('poll');
};
if (doc.readyState == 'complete') fn.call(win, 'lazy');
else {
if (doc.createEventObject && root.doScroll) {
try { top = !win.frameElement; } catch(e) { }
if (top) poll();
}
doc[add](pre + 'DOMContentLoaded', init, false);
doc[add](pre + 'readystatechange', init, false);
win[add](pre + 'load', init, false);
}
}
/*** end utlility functions */
var filter_data = {};
var all_filter_data;
var $filter;
var $devices;
var $emptyNote;
var closeListener;
var oldHref;
function init_device_filter() {
if (document.getElementById("sfosDeviceFilterList").length == 0)
return;
// fill filter_data with empty arrays for each filter
for (var category in filters) {
for (var key in filters[category]) {
filter_data[key] = [];
}
}
$devices = [];
var $devices_children = document.getElementById("sfosDeviceFilterList").childNodes;
for (var i=0; i<$devices_children.length; i++) {
if ($devices_children[i].className && $devices_children[i].className.match(/\bdevice\b/)) {
$devices.push($devices_children[i]);
}
}
$filter = document.createElement("div");
$filter.id = "sfosFilter";
$filter.innerHTML = '<label>Filter: <span class="holoInput"><input type="text/css" placeholder="search query" id="filter_devices" /></span></label>';
document.getElementById("sfosDeviceFilterList").parentNode.insertBefore($filter, document.getElementById("sfosDeviceFilterList"));
$emptyNote = document.getElementById("sfosDeviceFilterEmptyNote");
// search query filtering
listen(document.getElementById("filter_devices"), "keyup", function(e) {
filter_data.searchable = this.value.toLowerCase();
refilter_devices();
});
listen(document.getElementById("filter_devices"), "change", function(e) {
filter_data.searchable = this.value.toLowerCase();
update_location_hash();
});
// get all possible values for the dropdown filters
for (var i=0; i<$devices.length; i++) {
for (var key in filter_data) {
var value = $devices[i].getAttribute("data-" + key);
if (value == "") {
$devices[i].setAttribute("data-" + key, "N/A");
value = "N/A";
}
if (key == "sfosversions") {
//var versions = value.split(/,?\s+/);
var versions = value.split(",");
for (var k=0; k<versions.length; k++) {
if (filter_data[key].indexOf(versions[k]) == -1) {
filter_data[key].push(versions[k]);
}
}
}
else {
if (filter_data[key].indexOf(value) == -1) {
filter_data[key].push(value);
}
}
}
}
// sort all filter data
for (var key in filter_data) {
if (typeof sort_functions[key] !== "undefined")
filter_data[key].sort(sort_functions[key]);
else
filter_data[key].sort();
}
// add dropdown menus
for (var category in filters) {
var $parent;
if (category == "advanced") {
var $allDevicesButton = document.createElement("a");
$allDevicesButton.href = "#";
$allDevicesButton.id = "allDevicesButton";
$allDevicesButton.innerHTML = "show all devices";
listen($allDevicesButton, "click", function(e) {
e.preventDefault();
filter_data = clone(all_filter_data);
update_location_hash();
return false;
});
$filter.appendChild($allDevicesButton);
if (!isEmpty(filters[category])) {
$filter.appendChild(document.createElement("br"));
var $button = document.createElement("a");
$button.href = "#";
$button.id = "advancedButton";
$button.innerHTML = "advanced filters";
listen($button, "click", function(e) {
e.preventDefault();
this.className = (this.className == "expanded") ? "collapsed" : "expanded";
this.nextSibling.className = (this.nextSibling.className == "expanded") ? "collapsed" : "expanded";
this.blur();
return false;
});
$filter.appendChild($button);
$parent = document.createElement("div");
$parent.id = "advancedFilters";
$parent.className = "collapsed";
$filter.appendChild($parent);
}
}
else {
$parent = $filter;
}
for (var key in filters[category]) {
var $dropdown = document.createElement("a");
$dropdown.href = "#";
$dropdown.className = "dropdown";
$dropdown.innerHTML = '<b>' + filters[category][key] + "</b><span>" + filter_data[key].length + "</span>/" + filter_data[key].length;
listen($dropdown, "click", function(e) {
e.preventDefault();
var $dropdown = this;
var $menu = this.nextSibling
if ($menu.style.display == 'block') {
// hide it now
$menu.style.display = '';
this.removeAttribute('data-active');
}
else {
this.setAttribute('data-active', 'true');
var position = findPos(this);
var parentPosition = findPos(this.parentNode);
$menu.style.display = 'block';
$menu.style.position = 'absolute';
$menu.style.top = position.top - parentPosition.top + this.offsetHeight - 1 + 'px';
$menu.style.left = position.left - parentPosition.left + 'px';
// capture click event to close menu on click anywhere else on the page
closeListener = function(e) {
var currElem = e.target;
// go through all of e.target's parentNodes
while (currElem != null && currElem != document.body) {
if (currElem == $menu) {
return;
}
if (currElem == $dropdown) {
unlisten(document, "click", closeListener, true);
return;
}
currElem = currElem.parentNode;
}
clickElement($dropdown);
unlisten(document, "click", closeListener, true);
};
listen(document, "click", closeListener, true);
}
this.blur();
return false;
});
$parent.appendChild($dropdown);
var $menu = document.createElement("ul");
$menu.className = "dropdownMenu";
$menu.setAttribute("data-filter", key);
$parent.appendChild($menu);
// all and none buttons
var $li = document.createElement("li");
$li.className = "buttons";
var $all = document.createElement("a");
$all.href = "#";
$all.innerHTML = "all";
listen($all, "click", function(e) {
e.preventDefault();
var $ul = this.parentNode.parentNode.parentNode;
var key = $ul.getAttribute("data-filter");
filter_data[key] = clone(all_filter_data[key]);
var $checkedCnt = $ul.previousSibling.getElementsByTagName("span")[0];
var value = all_filter_data[key].length;
$checkedCnt.innerHTML = value;
$checkedCnt.className = "selected-" + value;
var $currElem = this.parentNode.parentNode;
while ($currElem = $currElem.nextSibling) {
$currElem.className = "checked";
}
this.blur();
update_location_hash();
return false;
});
var $none = document.createElement("a");
$none.href = "#";
$none.innerHTML = "none";
listen($none, "click", function(e) {
e.preventDefault();
var $ul = this.parentNode.parentNode.parentNode;
var key = $ul.getAttribute("data-filter");
filter_data[key] = [];
var $checkedCnt = $ul.previousSibling.getElementsByTagName("span")[0];
$checkedCnt.innerHTML = "0";
$checkedCnt.className = "selected-0";
var $currElem = this.parentNode.parentNode;
while ($currElem = $currElem.nextSibling) {
$currElem.className = "";
}
this.blur();
update_location_hash();
return false;
});
var $span = document.createElement("span");
$span.appendChild($all);
$li.appendChild($span);
$span = document.createElement("span");
$span.appendChild($none);
$li.appendChild($span);
$menu.appendChild($li);
for (var i=0; i<filter_data[key].length; i++) {
$li = document.createElement("li");
$li.className = "checked";
$li.setAttribute("data-value", filter_data[key][i]);
$li.innerHTML = filter_data[key][i];
listen($li, "click", function(e) {
var key = this.parentNode.getAttribute("data-filter");
var $checkedCnt = this.parentNode.previousSibling.getElementsByTagName("span")[0];
var value = parseInt($checkedCnt.innerHTML);
if (this.className.indexOf("checked") == -1) { // unchecked, now checked
this.className = "checked";
filter_data[key].push(this.getAttribute("data-value"));
$checkedCnt.innerHTML = ++value;
}
else { // checked, now unchecked
this.className = "";
filter_data[key].remove(filter_data[key].indexOf(this.getAttribute("data-value")));
$checkedCnt.innerHTML = --value;
}
$checkedCnt.className = "selected-" + value;
update_location_hash();
});
$menu.appendChild($li);
}
}
}
// create active filters
// standard = all on, so we can use filter_data
// just add the search query thing
filter_data.searchable = "";
all_filter_data = clone(filter_data);
if (location.hash == "" || location.hash == "#") {
location.hash = default_hash_string;
}
// react on hash changes
// from http://stackoverflow.com/a/9339972
if (!('onhashchange' in window)) {
oldHref = location.href;
setInterval(function() {
var newHref = location.href;
if (oldHref !== newHref) {
oldHref = newHref;
decode_hash_string.call(window, {
'type': 'hashchange',
'newURL': newHref,
'oldURL': oldHref
});
}
}, 150);
}
else {
listen(window, "hashchange", decode_hash_string);
}
// initial filtering
decode_hash_string();
}
function update_location_hash() {
var str = "";
for (var key in filter_data) {
if (key == "searchable") {
if (filter_data.searchable.length > 0) {
str += 'searchable="' + filter_data.searchable + '";';
}
continue;
}
if (filter_data[key].length == 0) {
str += key + '=;'
}
else if (filter_data[key].length != all_filter_data[key].length) {
str += key + '="' + filter_data[key].join('","') + '";'
}
}
if (str == "" && "pushState" in history) {
history.pushState("", document.title, location.pathname + location.search);
// pushState does not trigger the hashchange event
decode_hash_string();
}
else {
location.hash = str;
}
}
function decode_hash_string() {
var hash = location.hash;
filter_data = clone(all_filter_data);
if (location.hash == "" || location.hash == "#") {
// all ok, the full set is required
console.info("hashchange", hash);
console.log("no need to filter");
}
else {
hash = hash.slice(1);
console.info("hashchange", hash);
while (hash != "") {
var breakPos = hash.indexOf("=");
var key = hash.slice(0, breakPos);
hash = hash.slice(breakPos+1);
if (hash[0] == ";") {
filter_data[key] = [];
hash = hash.slice(1);
}
else if (key == "searchable") {
breakPos = hash.indexOf('";');
var value = hash.slice(1, breakPos);
hash = hash.slice(breakPos+2);
filter_data[key] = value;
}
else {
breakPos = hash.indexOf('";');
var value = hash.slice(1, breakPos);
hash = hash.slice(breakPos+2);
filter_data[key] = value.split('","');
}
}
}
var $dropdownMenus = $filter.getElementsByTagName("ul");
for (var i=0; i<$dropdownMenus.length; i++) {
var key = $dropdownMenus[i].getAttribute("data-filter");
var $checkedCnt = $dropdownMenus[i].previousSibling.getElementsByTagName("span")[0];
var value = filter_data[key].length;
$checkedCnt.innerHTML = value;
$checkedCnt.className = "selected-" + value;
var $currElem = $dropdownMenus[i].firstChild;
while ($currElem = $currElem.nextSibling) {
value = $currElem.getAttribute("data-value");
$currElem.className = (filter_data[key].indexOf(value) == -1) ? "" : "checked";
}
}
document.getElementById("filter_devices").value = filter_data.searchable
refilter_devices();
}
function refilter_devices() {
var hidden_cnt = 0;
for (var i=0; i<$devices.length; i++) {
$devices[i].removeAttribute("data-hidden");
// first, check if device matches search query
if ($devices[i].getAttribute("data-searchable").toLowerCase().indexOf(filter_data.searchable) == -1) {
$devices[i].setAttribute("data-hidden", "true");
hidden_cnt++;
continue;
}
// then go through all filters and check if device matches here
for (var key in filter_data) {
if (key == "searchable") { // already handled outside the loop
continue;
}
var value = $devices[i].getAttribute("data-" + key);
if (key == "sfosversions") { // requires special handling because it's a list
//var versions = value.split(/,?\s+/);
var versions = value.split(",");
var suitableVersion = false;
for (var k=0; k<versions.length; k++) {
if (filter_data[key].indexOf(versions[k]) > -1) {
suitableVersion = true;
break;
}
}
if (!suitableVersion) {
$devices[i].setAttribute("data-hidden", "true");
hidden_cnt++;
break;
}
continue;
}
if (filter_data[key].indexOf(value) == -1) {
$devices[i].setAttribute("data-hidden", "true");
hidden_cnt++;
break;
}
}
}
if (hidden_cnt == $devices.length) {
$emptyNote.removeAttribute("data-hidden");
}
else {
$emptyNote.setAttribute("data-hidden", "true");
}
}
contentLoaded(window, init_device_filter);