User:Vitorperrut555/common.js

From Wikimedia Commons, the free media repository
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.
/**
* 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);
    })();