User:Vitorperrut555/common.js
Jump to navigation
Jump to search
Note: After saving, you have to bypass your browser's cache to see the changes. Internet Explorer: press Ctrl-F5, Mozilla: hold down Shift while clicking Reload (or press Ctrl-Shift-R), Opera/Konqueror: press F5, Safari: hold down Shift + Alt while clicking Reload, Chrome: hold down Shift while clicking Reload.
The accompanying .css page for this skin can be added at User:Vitorperrut555/common.css. |
/**
* User interface enhancement to star images and add them to a personal favorites gallery
* @author [[User:Dschwen]], 2013
* Dependencies: mediawiki.util, mediawiki.Title, mediawiki.user
*/
/* jshint laxcomma:true, smarttabs:true */
/* global mw, $ */
(function () {
"use strict";
//virtual indent
// only run on file pages
if (mw.config.get('wgNamespaceNumber') !== 6 || !window.localStorage)
return;
var user = mw.user.getName(),
title = new mw.Title(mw.config.get('wgPageName')),
file = title.getMain(),
favesPage = 'User:' + user + '/Favorites',
ls = window.localStorage,
favCache = JSON.parse(ls.getItem('favCache') || '{}'),
isFaved = !!favCache[file],
favLink = $(mw.util.addPortletLink($('#p-views').length ? 'p-views' : 'p-cactions', '#', '-', 'ca-fave', '-')),
hasPersonalLink = false,
editToken = mw.user.tokens.get('csrfToken');
// toggle link
function toggleLink() {
var a = favLink.find('a');
a.text(isFaved ? 'Unfave' : 'Fave');
a.prop('title', isFaved ? 'Remove from favorites' : 'Add to favorites');
}
// add a link in the top right row next to the watchlist link (this might be too much clutter)
function addPersonalLink() {
// do favCache.keys().length ? what about support
for (var k in favCache) {
if (favCache.hasOwnProperty(k)) {
if (!hasPersonalLink) {
mw.util.addPortletLink('p-personal', '/wiki/' + favesPage, 'Favorites', 'pt-fave', 'Your favorite images', undefined, '#pt-watchlist');
hasPersonalLink = true;
}
break;
}
}
}
// refresh fave status (on window focus)
function refreshFaveStatus() {
favCache = JSON.parse(ls.getItem('favCache') || '{}');
isFaved = !!favCache[file];
toggleLink();
}
$(refreshFaveStatus);
// load /Favorites page
function loadFavorites(callback) {
// normalize the gallery tag placement in the receved text
function normalizeGalleryTags(text) {
// check if gallery tags are on the page
if (!/<[Gg]allery[\s>]/.test(text)) {
// no: prepend them on top
text = '<gallery>\n</gallery>\n' + text;
} else {
// make sure nothing comes before a closing gallery tag on the same line
text = text.replace(/([^\n])<\/([Gg])allery>/, '$1\n</$2allery>');
// make sure nothing comes after an opening gallery tag on the same line
text = text.replace(/<([Gg])allery([^>]*)>([^\n])/, '<$1allery$2>\n$3');
}
callback(text);
}
// fetch raw text
$.get(mw.util.wikiScript('index'), {
action: 'raw',
title: favesPage
}, undefined, 'text')
.done(normalizeGalleryTags)
.fail(function (xhr, a, b) {
if (xhr.status === 404) {
// The /Favorites page does not yet exist, initialize empty page
normalizeGalleryTags('');
} else if (xhr.status === 200 && xhr.responseText) {
// sometimes jquery throws a parse error (even though we requested the dataType to be 'string'!)
mw.notify("Come on Fabrice, this should not happen! (" + xhr.status + "," + a + "," + b + ")");
normalizeGalleryTags(xhr.responseText);
} else {
mw.notify("Unable to load Favorites. (" + xhr.status + "," + a + "," + b + ")");
ls.removeItem('favLock');
}
});
}
// save picks function
function saveFavorites(text, callback) {
// call API
$.post(mw.util.wikiScript('api'), {
format: 'json',
action: 'edit',
title: favesPage,
summary: 'Saving Favorites with [[MediaWiki:Gadget-Favorites.js]]',
text: text,
token: editToken
})
.done(callback)
.fail(function () {
mw.notify("Unable to save Favorites.");
})
.always(function () {
// remove the lock in either case
ls.removeItem('favLock');
});
}
function commitTransactions() {
var lock = parseInt(ls.getItem('favLock') || "0", 10),
now = new Date(),
time = now.getTime();
// check if lock is set and if so, was it set less than a minute ago?
if (lock > 0 && (time - lock) < (60 * 1000)) {
// already running, try again in 5 seconds
setTimeout(commitTransactions, 5000);
}
ls.setItem('favLock', time);
// load the /Favorites page
loadFavorites(function (text) {
// fetch transactions again (in case page loading took a long time)
var trans = JSON.parse(ls.getItem('favTrans') || '{}'),
applied = 0;
// to be executed when all transactions are applied
function transactionsApplied() {
// fetch transactions again (in case page saving took a long time)
var newTrans = JSON.parse(ls.getItem('favTrans') || '{}'),
file;
// now remove all transactions in newTrans that are identical to the transactions in trans that we just processed
for (file in trans) {
if (trans.hasOwnProperty(file) && file in newTrans && trans[file].action == newTrans[file].action) {
delete newTrans[file];
}
}
ls.setItem('favTrans', JSON.stringify(newTrans));
}
// process the page text and apply transactions
var line = text.split('\n'),
n = line.length,
i,
newLine = [],
token,
file,
title,
norm,
galleryFound = false,
inGallery = false;
for (i = 0; i < n; ++i) {
if (inGallery) {
if (/<\/[Gg]allery>/.test(line[i])) {
// closing the current gallery block
inGallery = false;
} else {
// parsing an image line in a gallery block
token = line[i].split('|');
// skip if the gallery line is malformed (for example if a deleted image has been removed)
try {
title = new mw.Title(token[0]);
} catch (err) {
// remove image from /Favorites page (by not adding it to newLine[])
applied++;
continue;
}
norm = title.getMain();
// remove any image that is in the transaction list (both add and rem!)
if (norm in trans) {
// remove image from /Favorites page (by not adding it to newLine[])
applied++;
continue;
}
}
} else {
if (/<[Gg]allery[\s>]/.test(line[i])) {
// opening of a new gallery block
inGallery = true;
if (!galleryFound) {
// this is the first gallery block, add new faves on top
newLine.push(line[i]);
for (file in trans) {
if (trans.hasOwnProperty(file) && trans[file].action == "add") {
newLine.push("File:" + file + '|"' + file + '" by [[User:' + trans[file].author + ']]');
applied++;
}
}
galleryFound = true;
continue;
}
}
}
newLine.push(line[i]);
}
// were any changes applied to the /Favorites page?
var newText = newLine.join('\n');
if (applied > 0) {
// yes, save the new /Favorites page text
saveFavorites(newText, transactionsApplied);
} else {
// no, consider the transactions processed
transactionsApplied();
}
// we now know the supposed contents of the /Favorites page, might as well use it to make sure the favCache is up to date
refreshFaveCache(newText);
});
}
// process the page text of the /Favorites gallery page and update the favCache
function refreshFaveCache(text) {
// process the page text and rebuild favorites Cache
favCache = {};
var line = text.split('\n'),
n = line.length,
i,
now = new Date(),
time = now.getTime(),
inGallery = false,
token,
title,
norm;
for (i = 0; i < n; ++i) {
if (inGallery) {
if (/<\/[Gg]allery>/.test(line[i])) {
// closing the current gallery block
inGallery = false;
} else {
// parsing an image line in a gallery block
token = line[i].split('|');
try {
title = new mw.Title(token[0]);
} catch (err) {
continue;
}
norm = title.getMain();
favCache[norm] = 1;
}
} else {
if (/<[Gg]allery[\s>]/.test(line[i])) {
// opening of a new gallery block
inGallery = true;
}
}
}
// store cache in localStorage
ls.setItem('favCache', JSON.stringify(favCache));
// set timestamp for last refresh
ls.setItem('favTimestamp', time);
refreshFaveStatus();
addPersonalLink();
}
// thank uploader using the Thanks API (thanks is not journaled, if the tab is closed too early.. ...well, shucks)
function thankUploader() {
// callback to deploy the actual thanks request after we found out the 1st revision id
function sendThanks(data) {
var firstRev = data.query.pages[data.query.pageids[0]].revisions[0].revid;
// thanks API request
$.get(mw.util.wikiScript('api'), {
action: 'thank',
rev: firstRev,
source: 'Favorites Gadget',
token: editToken
});
}
// first get the id of the first revision of the current file page
$.get(mw.util.wikiScript('api'), {
format: 'json',
action: 'query',
titles: mw.config.get('wgPageName'),
indexpageids: true,
prop: 'revisions',
rvdir: 'newer',
rvlimit: 1
})
.done(sendThanks)
.fail(function () {
mw.notify("Unable to thank Uploader.");
});
}
// hook portlet link handler
favLink.on('click', function (e) {
if (!e.target || !confirm(e.target.title + '?'))
return;
// change faved flag
isFaved = !isFaved;
if (isFaved) {
// now insert into favCache if it is a favorite
favCache[file] = 1;
} else {
// or delete from cache if unfaved
delete favCache[file];
}
// store in localStorage
ls.setItem('favCache', JSON.stringify(favCache));
// determine image author/uploader
var author = $('.filehistory a.mw-userlink').first().clone().find('.adminMark').remove().end().eq(0).text();
// add transaction (use localStorage to share data across tabs, if the servers are really slow and multiple images were faved before the edit to /Favorites is made)
var trans = JSON.parse(ls.getItem('favTrans') || '{}');
trans[file] = {
action: isFaved ? "add" : "rem",
author: author
};
ls.setItem('favTrans', JSON.stringify(trans));
commitTransactions();
if (isFaved) {
thankUploader();
}
// change link appearance and description to reflect new operation
toggleLink();
e.preventDefault();
});
// check if we have pending transactions from an aborted save
function checkPendingTasks() {
var trans = JSON.parse(ls.getItem('favTrans') || '{}'),
pending = 0,
t;
for (t in trans)
if (trans.hasOwnProperty(t))
pending++;
if (pending > 0) {
commitTransactions();
} else {
// check if we need to refresh the favorites cache (every 15mins)
var cacheTime = parseInt(ls.getItem('favTimestamp') || "0", 10),
now = new Date(),
time = now.getTime();
if (cacheTime === 0 || (time - cacheTime) > (15 * 60 * 1000)) {
loadFavorites(refreshFaveCache);
} else {
addPersonalLink();
}
}
}
$(checkPendingTasks);
}());
loader.load( 'ext.gadget.VisualFileChange' );
// Load dependencies (MediaWiki API and EXIF.js)
mw.loader.using(['mediawiki.api', 'jquery'], function() {
mw.loader.load('https://cdnjs.cloudflare.com/ajax/libs/exif-js/2.3.0/exif.min.js');
(function() {
var filePage = mw.config.get('wgPageName');
var api = new mw.Api();
function addButton() {
console.log('Page Name:', filePage);
// Check if the page is an image with EXIF data extensions
if (!/\.(jpg|jpeg|tiff|tif|heif|webp)$/i.test(filePage)) {
console.log('Page is not an image with EXIF data extensions.');
return;
}
// Check if the page already contains a {{location}} or {{GPS EXIF}} or {{GPS-EXIF}} tag
api.get({
action: 'query',
prop: 'revisions',
titles: filePage,
rvprop: 'content',
formatversion: '2'
}).done(function(data) {
var page = data.query.pages[0];
if (page.missing) {
console.error('Page not found');
return;
}
var content = page.revisions[0].content;
console.log('Page Content:', content);
var hasLocationTag = /{{location\|/i.test(content);
var hasGPSExifTag = /{{GPS EXIF}}/i.test(content) || /{{GPS-EXIF}}/i.test(content);
console.log('Has Location Tag:', hasLocationTag);
console.log('Has GPS EXIF or GPS-EXIF Tag:', hasGPSExifTag);
if (hasGPSExifTag && hasLocationTag) {
var buttonRemoveGPSTag = $('<button>')
.text('Location found, remove GPS EXIF tag')
.css({
padding: '10px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
margin: '10px'
})
.click(function() { removeGPSTag(content); });
var buttonOverwriteLocation = $('<button>')
.text('Overwrite current location')
.css({
padding: '10px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
margin: '10px'
})
.click(function() { extractAndInsertLocation(content, true); });
$('#content').prepend(buttonRemoveGPSTag);
$('#content').prepend(buttonOverwriteLocation);
}
if (hasGPSExifTag) {
var fileUrl = `https://commons.wikimedia.org/wiki/Special:FilePath/${filePage.split(':')[1]}`;
getExifData(fileUrl, function(lat, lon) {
if (!lat && !lon) {
var buttonLocationNotFound = $('<button>')
.text('Location not found in EXIF, remove GPS EXIF tag')
.css({
padding: '10px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
margin: '10px'
})
.click(function() { removeGPSTag(content); });
$('#content').prepend(buttonLocationNotFound);
}
});
}
if (!hasLocationTag) {
var button = $('<button>')
.text('Add location tag from EXIF data')
.css({
padding: '10px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
margin: '10px'
})
.click(function() { extractAndInsertLocation(content, false); });
$('#content').prepend(button);
}
}).fail(function(err) {
console.error('API request failed', err);
});
}
function getExifData(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob';
xhr.onload = function() {
if (this.status === 200) {
EXIF.getData(this.response, function() {
var lat = EXIF.getTag(this, "GPSLatitude");
var lon = EXIF.getTag(this, "GPSLongitude");
var latRef = EXIF.getTag(this, "GPSLatitudeRef") || "N";
var lonRef = EXIF.getTag(this, "GPSLongitudeRef") || "W";
if (lat && lon) {
var latitude = (lat[0] + lat[1] / 60 + lat[2] / 3600) * (latRef === "N" ? 1 : -1);
var longitude = (lon[0] + lon[1] / 60 + lon[2] / 3600) * (lonRef === "E" ? 1 : -1);
latitude = latitude.toFixed(7);
longitude = longitude.toFixed(7);
callback(latitude, longitude);
} else {
callback(null, null);
}
});
} else {
callback(null, null);
}
};
xhr.send();
}
function findClosingBrackets(content, startIndex) {
var openBrackets = 0;
for (var i = startIndex; i < content.length; i++) {
if (content[i] === '{' && content[i + 1] === '{') {
openBrackets++;
i++; // Skip the second '{'
} else if (content[i] === '}' && content[i + 1] === '}') {
openBrackets--;
i++; // Skip the second '}'
if (openBrackets === 0) {
return i + 1; // Return the index after the closing brackets
}
}
}
return -1; // Return -1 if no closing brackets are found
}
function insertLocationTemplate(lat, lon, currentContent) {
var newContent = currentContent;
// Remove existing {{location}} and {{GPS EXIF}} or {{GPS-EXIF}} tags if they exist (case-insensitive)
newContent = newContent.replace(/{{location\|.*?}}/gi, '');
newContent = newContent.replace(/{{GPS EXIF}}/gi, '');
newContent = newContent.replace(/{{GPS-EXIF}}/gi, '');
if (lat && lon) {
// Find the end of the {{Information}} section
var infoStartIndex = newContent.indexOf('{{Information');
var infoEndIndex = findClosingBrackets(newContent, infoStartIndex);
if (infoEndIndex !== -1) {
// Correctly insert after Information section:
newContent = newContent.slice(0, infoEndIndex) +
`\n{{location|${lat}|${lon}}} ` +
newContent.slice(infoEndIndex);
} else {
// If {{Information}} section not found, append at the end
newContent += `\n{{location|${lat}|${lon}}}`;
}
}
var summary = 'Replacing existing location tags with {{location}} template based on EXIF data';
api.postWithToken('csrf', {
action: 'edit',
title: filePage,
text: newContent,
summary: summary,
minor: true
}).done(function() {
console.log('Page updated successfully');
location.reload();
}).fail(function(err) {
console.error('Edit failed', err);
});
}
function removeGPSTag(currentContent) {
var newContent = currentContent.replace(/{{GPS EXIF}}/gi, '');
newContent = newContent.replace(/{{GPS-EXIF}}/gi, '');
var summary = 'Location present or missing from EXIF, removing {{GPS EXIF}} tag';
api.postWithToken('csrf', {
action: 'edit',
title: filePage,
text: newContent,
summary: summary,
minor: true
}).done(function() {
console.log('Page updated successfully');
location.reload();
}).fail(function(err) {
console.error('Edit failed', err);
});
}
function extractAndInsertLocation(currentContent, overwrite) {
var fileUrl = `https://commons.wikimedia.org/wiki/Special:FilePath/${filePage.split(':')[1]}`;
getExifData(fileUrl, function(lat, lon) {
if (lat && lon) {
if (overwrite) {
insertLocationTemplate(lat, lon, currentContent);
} else {
insertLocationTemplate(lat, lon, currentContent);
}
} else {
console.log('No location data found in EXIF');
// Hide the button if no location data is found
$('button:contains("Add location tag from EXIF data")').hide();
}
});
}
$(document).ready(addButton);
})();