// ==UserScript==
// @name Stig's Flickr Fixr
// @namespace dk.rockland.userscript.flickr.fixr
// @description Show photographer's albums on photostream-pages, Increase display-size and quality of "old" uploads, Photographer's other photos by tag-links, Links to album-map and album-comments, Actually show a geotagged photo on the associated map, Top-pagers - And more to come?...
// @author Stig Nygaard, https://www.rockland.dk, https://www.flickr.com/photos/stignygaard/
// @homepageURL https://www.flickr.com/groups/flickrhacks/discuss/72157655601688753/
// @supportURL https://www.flickr.com/groups/flickrhacks/discuss/72157655601688753/
// @icon https://raw.githubusercontent.com/StigNygaard/Stigs_Flickr_Fixr/master/WebExtension/icons/fixr32.png
// @icon64 https://raw.githubusercontent.com/StigNygaard/Stigs_Flickr_Fixr/master/WebExtension/icons/fixr64.png
// @match https://*.flickr.com/*
// @match *://*.flickr.net/*
// @exclude *://api.flickr.com/*
// @exclude *://identify.flickr.com/*
// @exclude *://*.flickr.com/signin/*
// @exclude *://*.flickr.com/signup/*
// @exclude *://*.flickr.com/account/*
// @version 2022.03.11.0
// @run-at document-start
// @grant none
// @noframes
// @downloadURL none
// ==/UserScript==
// CHANGELOG - The most recent or important updates/versions:
var changelog = [
{version: '2022.06.19.0', description: 'Remove the photo upscale feature. Not so relevant/important anymore, and very was very unreliable (sensitive to site changes). Lots of old ugly code I got rid of there... '},
{version: '2022.03.11.0', description: 'Adapt to Flickr changes, to fix issue on photopages.'},
{version: '2022.01.23.0', description: 'New feature: Control slideshow speed (Supported in webextension - not supported in userscript version of Flickr Fixr)'},
{version: '2021.01.29.0', description: 'Fix broken icon paths which probably prevented userscript installing/updating. I still recommend installing browser extension instead of userscript, I keep forgetting making sure userscript still works.'},
{version: '2020.06.22.0', description: 'Removing 90% of map-fix for showing geolocation of a photo. Finally Flickr has mostly fixed issue themselves. Restoring insertion of Google Maps link which broke by the Flickr update.'},
{version: '2020.06.21.0', description: 'A little bit of cleaning, a warning to userscript users - and "sub-options" for the tag-links feature (in webextension version)'},
{version: '2020.05.31.0', description: 'Improved fix to show location of geotagged photo (Zoom in). Some code cleaning...'},
{version: '2019.10.19.0', description: 'Adjusting to Flickr 2019 updates.'},
{version: '2018.11.29.0', description: 'New feature: Show available RSS/Atom newsfeeds on pages.'},
{version: '2018.10.15.1', description: 'Add Options page to Firefox and Chrome browser extensions, to enable or disable individual features of Flickr Fixr (Userscript version is still all or nothing).'},
{version: '2018.10.15.0', description: 'New feature: Added Collections and Map to topmenus.'},
{version: '2018.08.19.0', description: 'New features: Added link leading to Tags page in topmenus. Added display of full Taken and Upload time, plus link for photographer\'s other photos from (approx.) same day.'},
{version: '2018.05.20.0', description: 'New feature: Added a subtle warning if photostreams are shown in Date-taken order instead of Date-uploaded order.'},
{version: '2017.07.31.0', description: 'New feature: Adding a Google Maps link on geotagged photos. Also: Removing unused code. Development code now in GitHub repository: https://github.com/StigNygaard/Stigs_Flickr_Fixr'},
{version: '2016.03.11.1', description: 'New features: A link to "recent uploads page" added on the Explore page. Ctrl-click fix for opening tabs in background on search pages (Firefox-only problem?).'},
{version: '2016.02.09.0', description: 'New feature: Link to Explore Calendar added to Explore page.'},
{version: '2016.02.06.2', description: 'New feature: Top-pagers! Hover the mouse in the center just above photostreams to show a pagination-bar.'},
{version: '2015.11.28.1', description: 'New feature: Album-headers are now updated with links to album-map and album-comments.'},
{version: '2015.08.26.4', description: 'Initial userscript release version. Photo scale/replace, album column and tag-link feature.'}
];
var DEBUG = false;
function log(s) {
if (DEBUG && console) {
console.log(s);
}
}
if (DEBUG) {
if ('loading' === document.readyState) {
log("This script is running at document-start time.");
} else {
log("This script is running with document.readyState: " + document.readyState);
}
window.addEventListener('DOMContentLoaded', function () {
log('(onDOMContentLoaded)');
}, false);
window.addEventListener('focus', function () {
log('(onfocus)');
}, false);
window.addEventListener('load', function () {
log('(onload)');
}, false);
window.addEventListener('pageshow', function () {
log('(onpageshow)');
}, false);
window.addEventListener('resize', function () {
log('(onresize)');
}, false);
window.addEventListener('hashchange', function () {
log('(onhashchange)');
}, false);
window.addEventListener('blur', function () {
log('(onblur)');
}, false);
}
// FIXR page-tracker
var fixr = fixr || {
context: {
pageType: '',
pageSubType: '',
userId: '',
photographerId: '', // value might be delayed (If uninitialized, try call initPhotographerId())
photographerIcon: '',
photographerAlias: '', // (pathalias) bonus-info sometimes initialized (from url) when initializing photoId or albumId
photographerName: '',
photoId: '',
albumId: '',
groupId: '',
galleryId: ''
},
content: null,
pageactionsCount: 0,
timerResizeActionDelayed: 0,
onPageHandlers: [],
onResizeHandlers: [],
onFocusHandlers: [],
onStandaloneHandlers: [],
runningDirty: function () { // In-development and extra experiments enabled?
return (DEBUG && (fixr.context.userId === '10259776@N00'));
},
timer: {
_test: 0 // TODO
},
style: {
_declarations: '',
add: function (decl) {
fixr.style._declarations += decl + ' ';
},
init() {
if (!document.getElementById('fixrStyle')) {
let styleElem = createRichElement('style', {
type: 'text/css',
id: 'fixrStyle'
}, fixr.style._declarations);
document.getElementsByTagName('head')[0].appendChild(styleElem);
log('fixrStyle has been ADDED');
} else {
log('fixrStyle was already present');
}
}
},
clock: {
_d: null,
_pst: null, // Pacific Standard Time
_explore: null,
tick: function () {
this._d = new Date();
this._pst = new Date(this._d);
this._pst.setHours(this._d.getHours() - 8); // PST = UTC-08
this._explore = new Date(this._d);
this._explore.setHours(this._d.getHours() - 28); // Explore beat, yesterday UTC-4
// this._y.setDate(this._y.getDate() - 1);
return this._pst;
},
pst: function () { // yyyy-mm-dd tt:mm PST
return (this._pst || this.tick()).toISOString().substring(0, 16).replace('T', ' ') + ' PST';
},
explore: function () { // yyyy-mm-dd tt:mm Explore beat!
if (this._explore === null) {
this.tick();
}
return this._explore.toISOString().substring(0, 16).replace('T', ' ') + ' Explore beat!';
}
},
isWebExtension: function () {
return (typeof GM_info === 'undefined') && (typeof GM === 'undefined');
},
isUserscript: function () {
return !fixr.isWebExtension();
},
initUserId: function () {
if (window.auth && window.auth.user && window.auth.user.nsid) {
fixr.context.userId = window.auth.user.nsid;
return true;
}
return false;
},
initPhotographerName: function () {
if (fixr.content.querySelector('a.owner-name')) {
fixr.context.photographerName = fixr.content.querySelector('a.owner-name').innerText;
return true;
}
return false;
},
initPhotographerId: function () { // photographer/attribution id
// todo: This needs a rewrite some day...
var elem;
if (document.querySelector('div.photostream-page-view')) {
// photostream
elem = document.querySelector('div.photostream-page-view div.fluid-photostream-coverphoto-view .avatar.person');
} else if (document.querySelector('div.photo-page-scrappy-view')) {
// photopage
elem = document.querySelector('div.photo-page-scrappy-view div.sub-photo-view .photo-attribution .avatar.person');
} else if (document.querySelector('div.photo-page-lightbox-scrappy-view')) {
// photopage lightbox
elem = document.querySelector('div.photo-page-lightbox-scrappy-view div.photo-well-view .photo-attribution .avatar.person');
} else if (document.querySelector('div.album-page-view')) {
// album page
elem = document.querySelector('div.album-page-view div.album-container div.album-header-view .album-attribution .avatar.person');
} else if (document.querySelector('div.coverphoto-content .avatar.person')) {
// fallback, modern design pages
elem = document.querySelector('div.coverphoto-content .avatar.person');
} else if (document.querySelector('div.subnav-middle div.sn-avatar > img')) {
// fallback, old design pages
elem = document.querySelector('div.subnav-middle div.sn-avatar > img');
} else {
log('fixr.initPhotographerId() - We do not look for photographerId on this page');
return true;
}
if (!elem) {
log('fixr.initPhotographerId() - Attribution elem NOT found - returning false');
return false;
} // re-run a little later???
log('fixr.initPhotographerId() - Attribution elem found');
var result;
if (elem.tagName.toUpperCase() === 'IMG' && elem.src) {
result = elem.src.match(/https:(\/\/[^#\?]+\.com\/[^#\?]+\/buddyicon[^\?\#]+)[^#]*#(\d+\@N\d{2})/i);
} else if (elem.style.backgroundImage) {
log('fixr.initPhotographerId() - elem has style.backgroundImage "' + elem.style.backgroundImage + '", now looking for the attribution id...');
// var pattern = /\/buddyicons\/(\d+\@N\d{2})\D+/i;
result = elem.style.backgroundImage.match(/url[^#\?]+(\/\/[^#\?]+\.com\/[^#\?]+\/buddyicon[^\?\#]+)[^#]*#(\d+\@N\d{2})/i);
}
if (result) {
log('fixr.initPhotographerId() - Attribution pattern match found: ' + result[0]);
log('fixr.initPhotographerId() - the attribution icon is ' + result[1]);
log('fixr.initPhotographerId() - the attribution id is ' + result[2]);
fixr.context.photographerIcon = result[1];
fixr.context.photographerId = result[2];
log('fixr.initPhotographerId() - returning true...');
return true;
} else {
log('fixr.initPhotographerId() - attribution pattern match not found');
return false;
}
},
initPhotoId: function () { // Photo Id
// *flickr.com/photos/user/PId/*
var pattern = /^\/photos\/([^\/]+)\/([\d]{2,})/i;
var result = window.location.pathname.match(pattern);
if (result) {
log('url match med photoId=' + result[2]);
log('url match med photographerAlias=' + result[1]);
fixr.context.photoId = result[2];
fixr.context.photographerAlias = result[1];
return true;
} else {
log('*** initPhotoId() returnerer false! reg-pattern fandt ikke match i pathname=' + window.location.pathname);
}
return false;
},
initAlbumId: function () {
// *flickr.com/photos/user/albums/AId/*
// *flickr.com/photos/user/sets/AId/*
var pattern = /^\/photos\/([^\/]+)\/albums\/([\d]{2,})/i;
var result = window.location.pathname.match(pattern);
if (!result) {
pattern = /^\/photos\/([^\/]+)\/sets\/([\d]{2,})/i;
result = window.location.pathname.match(pattern);
}
if (result) {
log('url match med albumId=' + result[2]);
log('url match med photographerAlias=' + result[1]);
fixr.context.albumId = result[2];
fixr.context.photographerAlias = result[1];
return true;
}
return false;
},
pageActions: function () {
fixr.clock.tick();
if (fixr.content) {
log('fixr.pageActions() has started with fixr.content defined');
} else {
log('fixr.pageActions() was called, but fixr.content NOT defined');
return;
}
fixr.pageactionsCount++;
for (var p in fixr.context) { // reset context on new page
if (fixr.context.hasOwnProperty(p)) {
fixr.context[p] = '';
}
}
if (fixr.content.querySelector('div.photostream-page-view')) {
if (fixr.content.querySelector('div.slideshow-view')) {
fixr.context.pageType = 'PHOTOSTREAM SLIDESHOW';
} else {
fixr.context.pageType = 'PHOTOSTREAM';
}
} else if (fixr.content.querySelector('div.photo-page-scrappy-view')) {
fixr.context.pageType = 'PHOTOPAGE';
if (fixr.content.querySelector('div.vr-overlay-view') && fixr.content.querySelector('div.vr-overlay-view').hasChildNodes()) {
fixr.context.pageSubType = 'VR'; // maybe I can find a better way to detect, not sure how reliable this is?
} else if (fixr.content.querySelector('div.videoplayer')) {
fixr.context.pageSubType = 'VIDEO';
} else {
fixr.context.pageSubType = 'PHOTO';
}
} else if (fixr.content.querySelector('div.photo-page-lightbox-scrappy-view')) {
fixr.context.pageType = 'PHOTOPAGE LIGHTBOX';
if (fixr.content.querySelector('div.vr-overlay-view') && fixr.content.querySelector('div.vr-overlay-view').hasChildNodes()) {
fixr.context.pageSubType = 'VR'; // VR-mode currently not supported in lightbox?
} else if (fixr.content.querySelector('div.videoplayer')) {
fixr.context.pageSubType = 'VIDEO';
} else {
fixr.context.pageSubType = 'PHOTO';
}
} else if (fixr.content.querySelector('div.albums-list-page-view')) {
fixr.context.pageType = 'ALBUMSLIST';
} else if (fixr.content.querySelector('div.album-page-view')) {
if (fixr.content.querySelector('div.slideshow-view')) {
fixr.context.pageType = 'ALBUM SLIDESHOW';
} else {
fixr.context.pageType = 'ALBUM';
}
} else if (fixr.content.querySelector('div.cameraroll-page-view')) {
fixr.context.pageType = 'CAMERAROLL';
} else if (fixr.content.querySelector('div.explore-page-view')) {
fixr.context.pageType = 'EXPLORE';
} else if (fixr.content.querySelector('div.favorites-page-view')) {
if (fixr.content.querySelector('div.slideshow-view')) {
fixr.context.pageType = 'FAVORITES SLIDESHOW';
} else {
fixr.context.pageType = 'FAVORITES';
}
} else if (fixr.content.querySelector('div.groups-list-view')) {
fixr.context.pageType = 'GROUPSLIST'; // personal grouplist
} else if (fixr.content.querySelector('div#activityFeed')) { // id=main i stedet for id=fixr.content
fixr.context.pageType = 'ACTIVITYFEED'; // aka. front page -> UPDATES ?
} else if (fixr.content.querySelector('div#allsizes-photo')) {
fixr.context.pageType = 'SIZES'; // View all sizes - page
} else {
// fixr.context.pageType = ''; // unknown
}
log('fixr.context.pageType = ' + fixr.context.pageType);
log('fixr.context.pageSubType = ' + fixr.context.pageSubType);
if (fixr.initUserId()) {
log('fixr.initUserId() returned with succes: ' + fixr.context.userId);
} else {
log('fixr.initUserId() returned FALSE!');
}
if (fixr.initPhotographerId()) {
log('fixr.initPhotographerId() returned true in first try...');
} else {
log('fixr.initPhotographerId() returned false - re-running delayed...');
setTimeout(fixr.initPhotographerId, 1800);
}
if (fixr.initPhotoId()) {
log('fixr.initPhotoId() returned true in first try...');
} else {
log('fixr.initPhotoId() returned false - re-running delayed...');
setTimeout(fixr.initPhotoId, 1500);
}
if (fixr.initAlbumId()) {
log('fixr.initAlbumId() returned true in first try...');
}
if (fixr.initPhotographerName()) {
log('fixr.initPhotographerName() returned true in first try...');
} else {
setTimeout(fixr.initPhotographerName, 1500);
}
// Now run the page handlers....
if (fixr.onPageHandlers && fixr.onPageHandlers !== null && fixr.onPageHandlers.length) {
log('We have ' + fixr.onPageHandlers.length + ' onPage handlers starting now...');
for (var f = 0; f < fixr.onPageHandlers.length; f++) {
fixr.onPageHandlers[f]();
}
}
},
setupContent: function () {
if (document.getElementById('content')) {
fixr.content = document.getElementById('content');
} else if (document.getElementById('main')) {
fixr.content = document.getElementById('main'); // frontpage
}
if (fixr.content && fixr.content.id) {
log('fixr.content.id = ' + fixr.content.id);
} else {
log('content or main element NOT found!');
}
},
runPageActionsIfMissed: function () {
if (fixr.pageactionsCount === 0) {
log('Vi kører fixr.pageActions() på bagkant via onload...');
fixr.setupContent();
if (fixr.content === null) {
log('Vi kan IKKE køre fixr.pageActions() på bagkant, da fixr.content ikke er defineret');
return;
}
fixr.pageActions();
} else {
log('ej nødvendigt at køre fixr.pageActions() på bagkant i dette tilfælde...');
}
},
runIfStandalonePage: function () {
if (fixr.content === null && fixr.pageactionsCount === 0) { // if really looks like a "standalone page"...
// Now run the standalone handlers
if (fixr.onStandaloneHandlers && fixr.onStandaloneHandlers !== null && fixr.onStandaloneHandlers.length) {
log('We have ' + fixr.onStandaloneHandlers.length + ' standalone handlers starting now...');
for (var f = 0; f < fixr.onStandaloneHandlers.length; f++) {
fixr.onStandaloneHandlers[f]();
}
}
}
},
runDelayedPageActionsIfMissed: function () {
setTimeout(fixr.runPageActionsIfMissed, 2000);
setTimeout(fixr.runIfStandalonePage, 500);
},
resizeActions: function () {
if (fixr.onResizeHandlers && fixr.onResizeHandlers !== null && fixr.onResizeHandlers.length) {
for (var f = 0; f < fixr.onResizeHandlers.length; f++) {
fixr.onResizeHandlers[f]();
}
}
},
resizeActionsDelayed: function () { // or "preburner"
clearTimeout(fixr.timerResizeActionDelayed);
fixr.timerResizeActionDelayed = setTimeout(fixr.resizeActions, 250);
},
focusActions: function () {
if (fixr.onFocusHandlers && fixr.onFocusHandlers !== null && fixr.onFocusHandlers.length) {
for (var f = 0; f < fixr.onFocusHandlers.length; f++) {
fixr.onFocusHandlers[f]();
}
}
},
setupObserver: function () {
log('fixr.setupObserve INITIALIZATION START');
fixr.setupContent();
if (fixr.content === null) {
log('Init fails because content not defined');
return;
}
// create an observer instance
var observer = new MutationObserver(function (mutations) {
log('NEW PAGE MUTATION!');
//mutations.forEach(function(mutation) {
// log('MO: '+mutation.type); // might check for specific type of "mutations" (MutationRecord)
//});
fixr.pageActions();
}); // MutationObserver end
// configuration of the observer:
var config = {attributes: false, childList: true, subtree: false, characterData: false};
observer.observe(fixr.content, config);
log('fixr.setupObserve INITIALIZATION DONE');
},
init: function (runNow, onPageHandlerArray, onResizeHandlerArray, onFocusHandlerArray, onStandaloneHandlerArray) {
// General page-change observer setup:
if (document.readyState === 'interactive') { // already late?
fixr.setupObserver();
}
window.addEventListener('DOMContentLoaded', fixr.setupObserver, false); // Page on DOMContentLoaded
window.addEventListener('load', fixr.runDelayedPageActionsIfMissed, false); // Page on load
window.addEventListener('resize', fixr.resizeActionsDelayed, false); // også på resize
window.addEventListener('focus', fixr.focusActions, false);
if (onPageHandlerArray && onPageHandlerArray !== null && onPageHandlerArray.length) {
fixr.onPageHandlers = onPageHandlerArray; // Replace by adding with a one-by-one by "helper" for flexibility?
}
fixr.onPageHandlers.push(fixr.style.init); // styles
if (onResizeHandlerArray && onResizeHandlerArray !== null && onResizeHandlerArray.length) {
fixr.onResizeHandlers = onResizeHandlerArray; // Replace by adding with a one-by-one by "helper" for flexibility?
}
if (onFocusHandlerArray && onFocusHandlerArray !== null && onFocusHandlerArray.length) {
fixr.onFocusHandlers = onFocusHandlerArray;
}
if (onStandaloneHandlerArray && onStandaloneHandlerArray !== null && onStandaloneHandlerArray.length) { // on standalone pages, not part of "single page application"
fixr.onStandaloneHandlers = onStandaloneHandlerArray;
fixr.onStandaloneHandlers.push(fixr.style.init); // styles
}
if (runNow && runNow.length) {
log('We have ' + runNow.length + ' early running methods starting now at document.readyState = ' + document.readyState);
for (var f = 0; f < runNow.length; f++) {
runNow[f]();
}
}
}
};
// FIXR page-tracker end
const fkey = "9b8140dc97b93a5c80751a9dad552bd4"; // This api key is for Flickr Fixr only. Get your own key for free at https://www.flickr.com/services/apps/create/
function escapeHTML(str) {
return str.replace(/[&"'<>]/g, (m) => ({"&": "&", '"': """, "'": "'", "<": "<", ">": ">"})[m]);
}
function createRichElement(tagName, attributes, ...content) {
let element = document.createElement(tagName);
if (attributes) {
for (const [attr, value] of Object.entries(attributes)) {
element.setAttribute(attr, value);
}
}
if (content && content.length) {
element.append(...content);
}
return element;
}
function insertGMapLink() {
if (fixr.context.pageType !== 'PHOTOPAGE') {
return; // exit if not photopage
}
log('insertGMapLink() running at readystate=' + document.readyState + ' and with photoId=' + fixr.context.photoId);
if (fixr.context.photoId) {
var maplink = fixr.content.querySelector('a.static-maps');
if (maplink) {
if (!document.getElementById('googlemapslink') && maplink.getAttribute('href') && (maplink.getAttribute('href').includes('map/?'))) {
try {
let lat = maplink.getAttribute('href').match(/Lat=(\-?[\d\.]+)/i)[1];
let lon = maplink.getAttribute('href').match(/Lon=(\-?[\d\.]+)/i)[1];
let gmaplink = createRichElement('a', {
href: 'https://www.google.com/maps/search/?api=1&query=' + lat + ',' + lon,
id: 'googlemapslink'
}, 'Show location on Google Maps');
fixr.content.querySelector('li.c-charm-item-location').insertAdjacentElement('beforeend', createRichElement('div', {class: 'location-data-container'}, gmaplink));
} catch (e) {
log('Failed creating Google Maps link: ' + e);
}
} else {
log('link NOT inserted by insertGMapLink(). Invalid element or link already created. readystate=' + document.readyState);
}
} else {
log('NO maplink found at readystate=' + document.readyState + '. Re-try later?');
}
} else {
log('NO photoId found at readystate=' + document.readyState);
}
}
function insertGMapLinkDelayed() {
if (fixr.context.pageType === 'PHOTOPAGE') {
log('insertGMapLinkDelayed() running... with pageType=' + fixr.context.pageType);
setTimeout(insertGMapLink, 1500); // make maplink work better on photopage
setTimeout(insertGMapLink, 3500); // Twice. Photopage is sometimes a bit slow building
setTimeout(insertGMapLink, 8000); // Triple. Photopage is sometimes very slow building
}
}
function mapInitializer() {
if (window.location.href.includes('flickr.com/map/?')) {
// https://developer.mozilla.org/en-US/docs/Web/API/URL
const url = new URL(window.location.href);
// https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
const imgId = url.searchParams.get('photo');
if (imgId) {
const focusImg = document.getElementById('f_img_thumb_' + imgId);
if (focusImg) {
focusImg.click(); // close and ...
focusImg.click(); // reopen to highlight position on map
}
}
}
}
const topMenuItems_style = '.fluid-subnav .extraitems a {padding: 12px 10px !important} .subnav-refresh ul.nav-links.extraitems li.sn-navitem a {padding: 13px 10px 12px 10px !important}';
function topMenuItems() {
// User dropdown menu
var m = document.querySelector('li[data-context=you] > ul.gn-submenu') || document.querySelector('li[data-context=you] div#you-panel ul');
if (m) {
var gid = null;
if (m.querySelector('a[data-track=gnYouGroupsClick]')) {
gid = m.querySelector('a[data-track=gnYouGroupsClick]').parentElement;
}
if (!gid && m.querySelector('a[data-track=You-groups]')) {
gid = m.querySelector('a[data-track=You-groups]').parentElement;
}
var aad = m.querySelector('a[data-track=gnYouSetsClick]') || m.querySelector('a[data-track=You-sets]');
if (aad && gid) {
if (gid.hasAttribute('aria-label') && !m.querySelector('li[aria-label=Tags]')) {
// latest design
gid.insertAdjacentElement('afterend', createRichElement('li', {
class: 'menuitem',
role: 'menuitem',
'aria-label': 'Tags'
}, createRichElement('a', {'data-track': 'gnYouTagsClick', href: '/photos/me/tags'}, 'Tags')));
aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
class: 'menuitem',
role: 'menuitem',
'aria-label': 'Map'
}, createRichElement('a', {'data-track': 'gnYouMapClick', href: '/photos/me/map'}, 'Map')));
aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
class: 'menuitem',
role: 'menuitem',
'aria-label': 'Collections'
}, createRichElement('a', {
'data-track': 'gnYouCollectionsClick',
href: '/photos/me/collections'
}, 'Collections')));
} else if (gid.classList.contains('gn-subnav-item') && !m.querySelector('a[data-track=You-tags]')) {
// earlier design
gid.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
'data-track': 'You-tags',
href: '/photos/me/tags'
}, 'Tags')));
aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
'data-track': 'You-map',
href: '/photos/me/map'
}, 'Map')));
aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
'data-track': 'You-collections',
href: '/photos/me/collections'
}, 'Collections')));
}
}
}
// Photographer menu bar
m = document.querySelector('ul.links[role=menubar]') || document.querySelector('ul.nav-links');
if (m) {
var gib = m.querySelector('li#groups') || m.querySelector('li.sn-groups');
var aab = m.querySelector('li#albums a') || m.querySelector('li.sn-navitem-sets a');
if (aab && gib) {
m.classList.add('extraitems'); // mark extra items being added (so adjust spacing in style)
if (gib.id === 'groups' && !m.querySelector('li#tags')) {
// latest design
gib.insertAdjacentElement('afterend', createRichElement('li', {
id: 'tags',
class: 'link',
role: 'menuitem'
}, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/tags'}, createRichElement('span', {}, 'Tags'))));
aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
id: 'map',
class: 'link',
role: 'menuitem'
}, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/map'}, createRichElement('span', {}, 'Map'))));
aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
id: 'collections',
class: 'link',
role: 'menuitem'
}, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/collections'}, createRichElement('span', {}, 'Collections'))));
} else if (gib.classList.contains('sn-groups') && !m.querySelector('li.sn-tags')) {
// earlier design
gib.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-tags'}, createRichElement('a', {
'data-track': 'YouSubnav-tags',
href: '/photos/' + fixr.context.photographerId + '/tags'
}, 'Tags')));
aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-map'}, createRichElement('a', {
'data-track': 'YouSubnav-map',
href: '/photos/' + fixr.context.photographerId + '/map'
}, 'Map')));
aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-collections'}, createRichElement('a', {
'data-track': 'YouSubnav-collections',
href: '/photos/' + fixr.context.photographerId + '/collections'
}, 'Collections')));
}
}
}
}
var album = { // cache to avoid repeating requests
albumId: '',
commentCount: 0,
comment: [],
description: ''
};
var _wsGetPhotosetCommentsLock = 0;
function wsGetPhotosetComments() { // Call Flickr REST API to get album comments
var diff = Date.now() - _wsGetPhotosetCommentsLock;
if ((_wsGetPhotosetCommentsLock > 0) && (diff < 50)) {
log('Skipping wsGetPhotosetComments() because already running?: ' + diff);
// *** maybe add a check to see if we are still on same album?!
return;
}
_wsGetPhotosetCommentsLock = Date.now();
function handleResponse(response) {
if (response.ok) {
if (response.headers.get('content-type') && response.headers.get('content-type').includes('application/json')) {
return response.json()
}
throw new Error('Response was not in expected json format.');
}
throw new Error('Network response was not ok.');
}
function handleResult(obj) {
album.albumId = fixr.context.albumId;
album.commentCount = -1;
if (obj.stat === "ok") {
log("flickr.photosets.comments.getList returned ok");
if (obj.comments && obj.comments.photoset_id) {
album.albumId = obj.comments.photoset_id;
}
if (obj.comments && obj.comments.comment) {
album.commentCount = obj.comments.comment.length;
} else {
album.commentCount = 0;
}
} else {
log('flickr.photosets.comments.getList returned an ERROR: obj.stat=' + obj.stat + ', obj.code=' + obj.code + ', obj.message=' + obj.message);
}
if (document.getElementById('albumCommentCount')) {
if (album.commentCount === -1) {
document.getElementById('albumCommentCount').innerText = '?';
} else {
document.getElementById('albumCommentCount').innerText = String(album.commentCount);
}
} else {
log('albumCommentCount element not found');
}
_wsGetPhotosetCommentsLock = 0;
}
function handleError(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
log('There has been a problem with your fetch operation: ' + error);
}
if (fixr.isWebExtension()) {
// Call fetch() from background-script in WebExtensions, because changes in Chrome/Chromium https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
browser.runtime.sendMessage({
msgtype: "flickrservice",
method: "flickr.photosets.comments.getList",
fkey: fkey,
options: {photoset_id: fixr.context.albumId}
}).then(handleResult).catch(handleError);
} else { // Userscript (So far it still works, also on Chrome/Tampermonkey...)
fetch('https://api.flickr.com/services/rest/?method=flickr.photosets.comments.getList&api_key=' + fkey + '&photoset_id=' + fixr.context.albumId + '&format=json&nojsoncallback=1').then(handleResponse).then(handleResult).catch(handleError);
}
}
var albums = { // cache albums to avoid repeating requests
ownerId: '',
column: '',
count: 0
};
function getAlbumlist() {
var _reqAlbumlist = null;
if (window.XMLHttpRequest) {
_reqAlbumlist = new XMLHttpRequest();
if (typeof _reqAlbumlist.overrideMimeType !== 'undefined') {
_reqAlbumlist.overrideMimeType('text/html');
}
_reqAlbumlist.onreadystatechange = function () {
if (_reqAlbumlist.readyState === 4 && _reqAlbumlist.status === 200) {
log('_reqAlbumlist returned status=' + _reqAlbumlist.status); // + ', \ntext:\n' + _reqAlbumlist.responseText);
var doc = document.implementation.createHTMLDocument("sizeDoc");
doc.documentElement.innerHTML = _reqAlbumlist.responseText; // NOTICE, this is NOT inserted directly into HTML-document! In the following DOM-content of doc is being analyzed...
albums.ownerId = fixr.context.photographerId;
albums.column = new DocumentFragment();
albums.count = 0;
var alist = Array.from(doc.body.querySelectorAll('div.photo-list-album-view'));
var imgPattern = /url\([\'\"]*([^\)\'\"]+)(\.[jpgtifn]{3,4})[\'\"]*\)/i;
var columnhead = createRichElement('div', {style: 'margin:0 0 .8em 0'});
if (document.getElementById('albumTeaser')) {
if (alist && alist.length > 0) {
columnhead.textContent = "Albums";
albums.column.appendChild(columnhead);
albums.count = alist.length;
for (let e of alist.slice(0, 10)) {
var imgUrl = '';
//log(e.outerHTML);
// var result = e.style.backgroundImage.match(imgPattern); // strangely not working in Chrome
var result = (e.outerHTML).match(imgPattern); // quick work-around for above (works for now)
if (result) {
// imgUrl = result[1].replace(/_[a-z]$/, '') + '_s' + result[2];
imgUrl = result[1].replace(/_[a-z]$/, '') + '_q' + result[2];
log('imgUrl=' + imgUrl);
} else {
log('No match on imgPattern');
}
var a = e.querySelector('a[href][title]'); // sub-element
if (a && a !== null) {
log('Album title: ' + a.title);
log('Album url: ' + a.getAttribute('href'));
var album = document.createElement("div");
let thumbnail = createRichElement('img', {src: imgUrl, class: 'asquare', alt: ''});
let albumtitle = createRichElement('div', {style: 'margin:0 0 .8em 0'}, a.title);
let anchor = createRichElement('a', {href: a.getAttribute('href')}, thumbnail, albumtitle);
album.appendChild(anchor);
albums.column.appendChild(album);
} else {
log('a element not found?');
}
}
} else if (alist) {
if (doc.body.querySelector('h3')) {
columnhead.textContent = doc.body.querySelector('h3').innerText;
albums.column.appendChild(columnhead);
}
} else {
log('(e Undefined) Problem reading albums or no albums??? : ' + _reqAlbumlist.responseText);
}
var foot = document.createElement("div");
var cursive = document.createElement("i");
var moreanchor = document.createElement("a");
moreanchor.href = "/photos/" + (fixr.context.photographerAlias || fixr.context.photographerId) + "/albums/";
moreanchor.textContent = albums.count > 10 ? 'More albums...' : (albums.count === 0 ? 'No albums found...' : '');
cursive.appendChild(moreanchor);
foot.appendChild(cursive);
albums.column.appendChild(foot);
document.getElementById('albumTeaser').appendChild(albums.column);
} else {
log('albumTeaser NOT FOUND!?!');
}
} else {
// wait for the call to complete
}
};
if (fixr.context.photographerId) {
var url = 'https://' + window.location.hostname + '/photos/' + (fixr.context.photographerAlias || fixr.context.photographerId) + '/albums';
_reqAlbumlist.open('GET', url, true);
_reqAlbumlist.send(null);
} else {
log('Attribution user (photographer) not found');
}
} else {
log('understøtter ikke XMLHttpRequest');
}
}
const albumTeaser_style = 'div#albumTeaser {border:none;margin:0;padding:0;position:absolute;top:0;right:-120px;width:100px}';
function albumTeaser() {
if (fixr.context.pageType !== 'PHOTOSTREAM') {
return; // exit if not photostream
}
log('albumTeaser() running');
var dpc = document.querySelector('div.photolist-container');
if (!dpc) {
return;
}
log('AlbumTeaser found div.photolist-container');
if (!document.getElementById('albumTeaser')) {
dpc.style.position = "relative";
dpc.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'albumTeaser'}));
}
if (document.getElementById('albumTeaser')) {
getAlbumlist(); // også check på fixr.context.photographerId ?
}
}
var _timerAlbumTeaserDelayed;
function albumTeaserDelayed() {
if (fixr.context.pageType !== 'PHOTOSTREAM') {
return; // exit if not photostream
}
log('albumTeaserDelayed() running...');
clearTimeout(_timerAlbumTeaserDelayed);
_timerAlbumTeaserDelayed = setTimeout(albumTeaser, 1500);
}
const exploreCalendar_style = '#exploreCalendar {border:none;margin:0;padding:0;position:absolute;top:38px;right:-120px;width:100px} #exploreCalendar div {margin:0 0 .8em 0} #exploreCalendar img.asquare {width:75px;height:59px}';
function exploreCalendar() {
if (fixr.context.pageType !== 'EXPLORE') {
return; // exit if not explore/interestingness
}
log('exploreCalendar() running');
let dtr = document.querySelector('div.title-row');
if (!dtr) {
return;
}
log('exploreCalendar found div.photo-list-view');
if (!document.getElementById('exploreCalendar')) {
dtr.style.position = "relative";
let exploreMonth = fixr.clock.explore().substring(0, 7).replace('-', '/');
let explCal = createRichElement('a', {href: '/explore/interesting/' + exploreMonth + '/'}, createRichElement('img', {
src: 'https://c2.staticflickr.com/2/1701/24895062996_78719dec15_o.jpg',
class: 'asquare',
alt: ''
}), createRichElement('div', {}, 'Explore Calendar'));
let freshUpl = createRichElement('a', {
href: '/search/?text=&view_all=1&media=photos&content_type=1&dimension_search_mode=min&height=640&width=640&safe_search=2&sort=date-posted-desc&min_upload_date=' + (Math.floor(Date.now() / 1000) - 7200),
title: 'If you are an adventurer and want to explore something different than everybody else...'
}, createRichElement('img', {
src: 'https://c2.staticflickr.com/2/1617/25534100345_b4a3fe78f1_o.jpg',
class: 'asquare',
alt: ''
}), createRichElement('div', {}, 'Fresh uploads'));
dtr.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'exploreCalendar'}, explCal, freshUpl));
log('San Francisco PST UTC-8: ' + fixr.clock.pst());
log('Explore Beat (Yesterday, UTC-4): ' + fixr.clock.explore());
}
}
var _timerExploreCalendarDelayed;
function exploreCalendarDelayed() {
if (fixr.context.pageType !== 'EXPLORE') {
return; // exit if not explore/interestingness
}
log('albumTeaserDelayed() running...');
clearTimeout(_timerExploreCalendarDelayed);
_timerExploreCalendarDelayed = setTimeout(exploreCalendar, 1500);
}
function ctrlClick(e) {
var elem, evt = e ? e : event;
if (evt.srcElement) elem = evt.srcElement;
else if (evt.target) elem = evt.target;
if (evt.ctrlKey) {
log('Ctrl clicked. Further scripted click-event handling canceled. Allow the default ctrl-click handling in my browser.');
evt.stopPropagation();
}
}
function ctrlClicking() {
var plv = document.querySelectorAll('div.photo-list-view');
for (var i = 0; i < plv.length; i++) {
log('ctrlClicking(): plv[' + i + '] found!');
// Allow me to open tabs in background by ctrl-click in Firefox:
plv[i].parentNode.addEventListener('click', ctrlClick, true);
}
}
var _timerCtrlClicking;
function ctrlClickingDelayed() {
log('ctrlClickingDelayed() running...');
clearTimeout(_timerCtrlClicking);
_timerCtrlClicking = setTimeout(ctrlClicking, 1500);
}
const topPagination_style = '#topPaginationContainer{width:250px;height:40px;margin:0 auto;position:absolute;top:0;left:0;right:0;border:none} #topPagination{width:720px;margin:0;position:absolute;top:0;left:-235px;text-align:center;z-index:10;display:none;border:none;padding:10px 0 10px 0;overflow:hidden} .album-toolbar-content #topPagination{top:-16px} .group-pool-subheader-view #topPagination{top:-7px} .title-row #topPagination{width:830px;left:-290px;top:-12px} #topPaginationContainer:hover #topPagination{display:block}';
function topPagination() {
log('topPagination()');
var bottomPagination = document.querySelector('.pagination-view');
if (!bottomPagination) {
bottomPagination = document.querySelector('.explore-pagination');
}
if (bottomPagination && !document.getElementById('topPagination')) {
if (bottomPagination.childElementCount > 0) {
var topPagination = bottomPagination.cloneNode(true);
topPagination.id = 'topPagination';
var topPaginationContainer = document.createElement('div');
topPaginationContainer.id = 'topPaginationContainer';
topPaginationContainer.appendChild(topPagination);
var topbar = document.querySelector('.fluid-magic-tools-view');
if (!topbar) topbar = document.querySelector('.album-toolbar-content');
if (!topbar) topbar = document.querySelector('.group-pool-subheader-view');
if (!topbar) topbar = document.querySelector('.title-row');
if (topbar) {
log('topPagination: root found, inserting container');
topbar.appendChild(topPaginationContainer);
}
}
}
}
const albumExtras_style = '.album-map-icon{background:url("https://c2.staticflickr.com/6/5654/23426346485_334afa6e8f_o_d.png") no-repeat;height:21px;width:24px;top:6px;left:3px} .album-comments-icon{background:url("https://c1.staticflickr.com/5/4816/46041390622_f8a0cf0148_o.png") -32px -460px no-repeat;height:21px;width:24px;top:6px;left:3px}';
function albumExtras() { // links to album's map and comments
if (fixr.context.pageType !== 'ALBUM') {
return; // exit if not albumpage
}
if (fixr.context.albumId) {
log('albumsExtra() med album=' + fixr.context.albumId);
} else {
log('Exit albumsExtra(). Mangler albumId');
return;
}
var elist = document.querySelector('div.album-engagement-view');
if (elist && !document.getElementById('albumCommentCount')) {
// map-link:
var mapdiv = document.createElement('div');
mapdiv.className = 'create-book-container';
mapdiv.title = 'Album on map';
mapdiv.style.textAlign = 'center';
var maplink = document.createElement('a');
maplink.href = '/photos/' + fixr.context.photographerAlias + '/albums/' + fixr.context.albumId + '/map/';
maplink.style.fontSize = '14px';
maplink.style.color = '#FFF';
var mapicon = document.createElement('span');
mapicon.title = 'Album on map';
mapicon.className = 'album-map-icon';
maplink.appendChild(mapicon);
mapdiv.appendChild(maplink);
elist.appendChild(mapdiv);
// comments-link:
var comurl = '/photos/' + fixr.context.photographerAlias + '/sets/' + fixr.context.albumId + '/comments/'; // /sets/* urls works, /albums/* urls currently doesn't work (yet?)
// var comurl = '#'; // NEW?!
var cmdiv = document.createElement('div');
cmdiv.className = 'create-book-container';
cmdiv.title = 'Comments';
cmdiv.style.textAlign = 'center';
var cmlink = document.createElement('a');
cmlink.href = comurl;
cmlink.style.fontSize = '14px';
cmlink.style.color = '#FFF';
cmlink.id = 'albumCommentsLink';
var cmicon = document.createElement('span');
cmicon.title = 'Album comments';
cmicon.className = 'album-comments-icon';
cmicon.id = 'albumCommentCount';
cmlink.appendChild(cmicon);
cmdiv.appendChild(cmlink);
elist.appendChild(cmdiv);
wsGetPhotosetComments();
}
}
const updateTags_style_avatar = 'a.fixrTag>img {width:1em;height:1em;margin:0;padding:0;position:relative;top:3px}';
const updateTags_style_hover = 'ul.tags-list>li.tag>a.fixrTag,ul.tags-list>li.autotag>a.fixrTag{display:none;} ul.tags-list>li.tag:hover>a.fixrTag,ul.tags-list>li.autotag:hover>a.fixrTag{display:inline;} ' + updateTags_style_avatar;
const updateTags_style_persist = 'ul.tags-list>li.tag>a.fixrTag,ul.tags-list>li.autotag>a.fixrTag{display:inline;} ' + updateTags_style_avatar;
function updateTags() {
if (fixr.context.pageType !== 'PHOTOPAGE') {
return; // exit if not photopage
}
if (fixr.context.photographerAlias === '') {
fixr.initPhotoId();
}
if (fixr.context.photographerId === '') {
fixr.initPhotographerId();
}
if (fixr.context.photographerName === '') {
fixr.initPhotographerName();
}
log('updateTags() med photographerAlias=' + fixr.context.photographerAlias + ', photographerId=' + fixr.context.photographerId + ' og photographerName=' + fixr.context.photographerName);
if (document.querySelector('ul.tags-list')) {
let tags = document.querySelectorAll('ul.tags-list>li');
if (tags && tags !== null && tags.length > 0) {
log('updateTags() Looping ' + tags.length + ' tags...');
let iconHref = fixr.context.photographerIcon.match(/^([^_]+)(_\w)?\.[jpgntif]{3,4}$/)[1] + String(fixr.context.photographerIcon.match(/^[^_]+(_\w)?(\.[jpgntif]{3,4})$/)[2]); // do we know for sure it is square?
for (let tag of tags) {
let atag = tag.querySelector('a[title][href*="/photos/tags/"],a[title][href*="?tags="],a[title][href*="?q="]');
if (atag) {
let realtag = (atag.href.match(/((\/tags\/)|(\?tags\=)|(\?q\=))([\S]+)$/i))[5];
if (!(tag.querySelector('a.fixrTag'))) {
let avatar = document.createElement('img');
avatar.src = iconHref;
avatar.alt = '*';
let taglink = document.createElement('a');
taglink.className = 'fixrTag';
taglink.href = '/photos/' + (fixr.context.photographerAlias || fixr.context.photographerId) + '/tags/' + realtag + '/';
taglink.title = atag.title + ' by ' + fixr.context.photographerName;
taglink.appendChild(avatar);
tag.insertBefore(taglink, tag.firstChild);
}
} else {
log('updateTags(): atag not matched.');
}
}
} else {
log('updateTags(): No tags defined (yet?)');
}
} else {
log('updateTags(): taglist container not found');
}
}
function updateTagsDelayed() {
log('updateTagsDelayed() running... with pageType=' + fixr.context.pageType);
if (fixr.context.pageType === 'PHOTOPAGE') {
setTimeout(updateTags, 2500);
setTimeout(updateTags, 4500); // Twice. Those tags are sometimes a bit slow emerging
setTimeout(updateTags, 8500); // Triple. Those tags are sometimes very slow emerging
}
}
const photoDates_style = '.has-date-info {position:relative} .date-info{z-index:10;padding:0 .5em 0 .5em;display:none;position:absolute;top:30px;left:-40px;width:400px;margin-right:-400px;background-color:rgba(255,250,150,0.9);color:#000;border:1px solid #d4b943;border-radius:4px;} .has-date-info:hover .date-info{display:block;} .date-info label {display:inline-block; min-width: 5em;}';
function photoDates() {
var elem = document.querySelector('div.view.sub-photo-date-view');
if (elem && !elem.classList.contains('has-date-info')) {
elem.classList.add('has-date-info');
elem.insertAdjacentElement('beforeend', createRichElement('div', {class: 'date-info'}, 'Date info!'));
wsGetPhotoInfo(); // get dates
}
}
function photoDatesDelayed() {
log('photoDates() running... with pageType=' + fixr.context.pageType);
if (fixr.context.pageType === 'PHOTOPAGE') {
setTimeout(photoDates, 2000);
setTimeout(photoDates, 4000); // Twice.
}
}
function shootingSpaceballs() {
// Enable image context-menu on "View sizes" page by removing overlaying div.
// This is *not* meant as a tool for unauthorized copying and distribution of other peoples photos.
// Please respect image ownership and copyrights!
if (fixr.context.pageType === 'SIZES') {
function trashing() {
let trash = document.querySelector('div.spaceball');
while (trash && trash.parentNode) {
trash.parentNode.removeChild(trash);
trash = document.querySelector('div.spaceball');
}
}
let asp = document.querySelector('#allsizes-photo');
if (asp) {
document.body.addEventListener('click', trashing, false);
asp.click();
}
// document.body.style.backgroundColor = "#cfc";
}
}
const orderwarning_style = '.filter-sort.warning p {animation:wink 3s ease 1s 1;} @keyframes wink {0% {background-color:transparent;} 50% {background-color:rgba(255,250,150,0.9);} 100% {background-color:transparent;}} .filter-sort.warning:after{content:"You are looking at this photostream in Date-taken order. Change order to Date-uploaded, to be sure to see latest uploads in the top of photostreams.";z-index:10;padding:.5em;display:none;position:relative;top:-2px;right:-50px;width:400px;margin-right:-400px;background-color:rgba(255,250,150,0.9);color:#000;border:1px solid #d4b943;border-radius:4px;} .filter-sort.warning:hover:after{display:block;}';
function orderWarning() {
if (fixr.context.pageType === 'PHOTOSTREAM') {
var e = document.querySelector('.dropdown-link.filter-sort');
if (e) {
if (['Date taken', 'Fecha de captura', 'Aufnahmedatum', 'Date de prise de vue', 'Data dello scatto', 'Tirada na data', 'Ngày chụp', 'Tanggal pengambilan', '拍攝日期', '촬영 날짜'].includes(e.innerText.trim())) {
e.classList.add('warning');
} else {
e.classList.remove('warning');
}
}
}
}
const newsfeedLinks_style = 'div#feedlinks {border:none;margin:0;padding:0;position:absolute;top:0;right:10px;width:100px} ul.gn-tools>div#feedlinks {right:-120px} div#gn-wrap>div#feedlinks, div.header-wrap>div#feedlinks, header#branding>div#feedlinks {right:-120px} div#feedlinks>a {display:block;float:left;margin:10px 8px 0 0;width:16px} div#feedlinks>a>img {display:block;width:16px;height:16px} ul.gn-tools>div#feedlinks>a {margin-top:2px}';
function newsfeedLinks() {
var elem = document.getElementById('feedlinks');
if (elem) {
elem.textContent = '';
}
setTimeout(updateNewsfeedLinks, 500); // give Flickr time to update link tags in head
}
function updateNewsfeedLinks() {
var feedlinks = document.querySelectorAll('head > link[rel="alternate"][type="application/rss+xml"], head > link[rel="alternate"][type="application/atom+xml"], head > link[rel="alternate"][type="application/atom+xml"], head > link[rel="alternate"][type="application/json"]');
log('Number of feed links found: ' + feedlinks.length);
var dgnc = document.querySelector('div.global-nav-container ul.gn-tools') || document.querySelector('div#gn-wrap') || document.querySelector('div#global-nav') || document.querySelector('div.header-wrap') || document.querySelector('header#branding') || document.querySelector('div.custom-header-container>div>div');
if (dgnc) {
if (!document.getElementById('feedlinks')) {
dgnc.style.position = "relative";
dgnc.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'feedlinks'}));
}
var elem = document.getElementById('feedlinks');
if (elem) {
var feedicons = new DocumentFragment();
for (const link of feedlinks) {
var feedlink = document.createElement('a');
feedlink.href = link.href;
var feedsymbol = document.createElement('img');
feedsymbol.src = 'https://c1.staticflickr.com/5/4869/32220441998_601de47e20_o.png';
feedsymbol.alt = 'Feedlink';
feedsymbol.style.width = '16px';
feedsymbol.style.height = '16px';
feedsymbol.title = link.title;
feedlink.appendChild(feedsymbol);
feedicons.appendChild(feedlink);
}
elem.textContent = ''; // remove existing content
elem.appendChild(feedicons);
}
}
}
function initSlideshowSpeedHook() {
let timeoutScript = document.createElement('script');
timeoutScript.src = browser.runtime.getURL('inject/timeout.js');
timeoutScript.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(timeoutScript);
}
let slideshowSpeed = '5'; // Flickr default (seconds)
function initSlideshowSpeed() {
if (document.body) {
document.body.setAttribute('data-slideshowspeed', slideshowSpeed);
}
}
var _wsGetPhotoInfoLock = 0;
function wsGetPhotoInfo() { // Call Flickr REST API to get photo info
var diff = Date.now() - _wsGetPhotoInfoLock;
if ((_wsGetPhotoInfoLock > 0) && (diff < 50)) {
log('Skipping wsGetPhotoInfo() because already running?: ' + diff);
// *** maybe add a check to see if we are still on same photo?!
return;
}
_wsGetPhotoInfoLock = Date.now();
function handleResponse(response) {
if (response.ok) {
if (response.headers.get('content-type') && response.headers.get('content-type').includes('application/json')) {
return response.json()
}
throw new Error('Response was not in expected json format.');
}
throw new Error('Network response was not ok.');
}
function handleResult(obj) {
var elem = document.querySelector('.date-info');
if (obj.stat === "ok") {
log("flickr.photos.getInfo returned ok");
if (obj.photo && obj.photo.id) {
var uploadDate = new Date(0);
var debugstr = '';
if (obj.photo.dateuploaded) {
uploadDate = new Date(obj.photo.dateuploaded * 1000);
debugstr = 'UploadDate: ' + uploadDate.toString();
}
let dateDetail = createRichElement('p', {'x-ms-format-detection': 'none'});
let takenLabel = createRichElement('label', {}, 'Taken:');
let uploadedLabel = createRichElement('label', {}, 'Uploaded:');
let brElem = document.createElement('br');
if (obj.photo.dates) {
if (obj.photo.dateuploaded !== obj.photo.dates.posted) {
log('Unexpected Date difference!');
}
if (obj.photo.dates.posted) {
if (obj.photo.dates.posted < obj.photo.dateuploaded) {
uploadDate = new Date(obj.photo.dates.posted * 1000); // GMT/UTC
}
debugstr += '
PostDate: ' + uploadDate.toString();
}
if (obj.photo.dates.taken && obj.photo.dates.takenunknown.toString() === '0') {
debugstr += '
TakenDate: ' + obj.photo.dates.taken + ' (granularity=' + obj.photo.dates.takengranularity + ')';
var takenDateTmp = obj.photo.dates.taken.replace(/[^\d-:\s]/g, ''); // "2018-03-30 00:35:44" (Remove any unexpected characters)
var takenDate = new Date(Date.parse(takenDateTmp.replace(' ', 'T')));
var dayStart = new Date(Date.parse(takenDateTmp.substring(0, 10) + 'T00:00:00'));
var dayEnd = new Date(Date.parse(takenDateTmp.substring(0, 10) + 'T23:59:59'));
var takenTimeIndex = takenDate.toString().search(/\d{2}[:\.]\d{2}[:\.]\d{2}/);
if (obj.photo.dates.takengranularity.toString() === '0') { // 0 Y-m-d H:i:s - full datetime
let linkElem = createRichElement('a', {href: '/search/?user_id=' + fixr.context.photographerId + '&view_all=1&min_taken_date=' + (Math.floor(dayStart.getTime() / 1000) - 43200) + '&max_taken_date=' + (Math.floor(dayEnd.getTime() / 1000) + 43200)}, takenDate.toString().substring(0, takenTimeIndex - 1));
dateDetail.append(takenLabel, ' ', linkElem, takenDate.toString().substring(takenTimeIndex - 1, takenTimeIndex + 8) + ' "Camera Time"', brElem);
} else if (obj.photo.dates.takengranularity.toString() === '4') { // 4 Y-m
dateDetail.append(takenLabel, ' ' + takenDateTmp.substring(0, 7), brElem);
} else if (obj.photo.dates.takengranularity.toString() === '6') { // 6 Y
dateDetail.append(takenLabel, ' ' + takenDateTmp.substring(0, 4), brElem);
} else if (obj.photo.dates.takengranularity.toString() === '8') { // 8 Circa...
dateDetail.append(takenLabel, ' Circa ' + takenDateTmp.substring(0, 4), brElem);
} else {
log('Unexpected value for photo.dates.takengranularity: ' + obj.photo.dates.takengranularity);
}
}
if (obj.photo.dates.lastupdate) { // photo has been updated/replaced
debugstr += '
UpdateDate: ' + (new Date(obj.photo.dates.lastupdate * 1000)).toString();
}
}
if (elem) {
var uploadDateStr = uploadDate.toString();
var n = uploadDateStr.indexOf('(');
if (n > 0) {
dateDetail.append(uploadedLabel, ' ' + uploadDateStr.substring(0, n));
} else {
dateDetail.append(uploadedLabel, ' ' + uploadDateStr);
}
elem.textContent = '';
elem.append(dateDetail);
var withTitle = elem.parentElement.querySelector('span[title]');
if (withTitle) {
withTitle.removeAttribute('title');
}
}
}
} else {
if (elem) {
elem.textContent = 'Cannot fetch detailed date details on private photos';
}
log('flickr.photos.getInfo returned an ERROR: obj.stat=' + obj.stat + ', obj.code=' + obj.code + ', obj.message=' + obj.message);
}
_wsGetPhotoInfoLock = 0;
}
function handleError(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
log('There has been a problem with your fetch operation: ' + error);
var elem = document.querySelector('.date-info');
if (elem) {
elem.textContent = 'There was an error fetching detailed date details...';
}
}
if (fixr.isWebExtension()) {
// Call fetch() from background-script in WebExtensions, because changes in Chrome/Chromium https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
browser.runtime.sendMessage({
msgtype: "flickrservice",
method: "flickr.photos.getInfo",
fkey: fkey,
options: {photo_id: fixr.context.photoId}
}).then(handleResult).catch(handleError);
} else { // Userscript (So far it still works, also on Chrome/Tampermonkey...)
fetch('https://api.flickr.com/services/rest/?method=flickr.photos.getInfo&api_key=' + fkey + '&photo_id=' + fixr.context.photoId + '&format=json&nojsoncallback=1').then(handleResponse).then(handleResult).catch(handleError);
}
}
function stereotest() {
var self = "flickrfixruserscript";
var other = "flickrfixrwebextension";
if (fixr.isWebExtension()) {
self = "flickrfixrwebextension";
other = "flickrfixruserscript";
}
document.body.classList.add(self);
if (document.body.classList.contains(other)) {
alert("It looks like you are running both Stigs Flickr Fixr userscript and Flickr Fixr browser extension at once. Please uninstall or disable one of them to avoid errors and unpredictable behaviors!");
}
}
function runEarly() {
//localStorage.setItem('filterFeedEvents', 'people'); // Try to make People feed default.
}
function userscriptWarning() {
if (!document.body.classList.contains("flickrfixrwebextension")) { // Skip warning if both versions are installed (There will be another warning from stereotest()).
let info = (GM_info ? GM_info : (typeof GM === 'object' && GM !== null && typeof GM.info === 'object' ? GM.info : null));
if (info) {
let prevDate = localStorage.getItem("fixr_userscript_warning_timestamp");
let prevVersion = localStorage.getItem("fixr_userscript_warning_version");
if (!prevVersion || prevVersion !== info.script.version) {
localStorage.setItem("fixr_userscript_warning_timestamp", Date.now().toString());
localStorage.setItem("fixr_userscript_warning_version", info.script.version);
alert('\nYou are running the userscript-version of Flickr Fixr via ' + info.scriptHandler + '. Did you know that Flickr Fixr is also available as a regular browser extension for most webbrowsers? \n\nSupport and test of userscript-version is limited, and some features are not available or might stop working. It is highly recommended to use the regular browser extensions instead. \n\nYou can find Flickr Fixr browser extension in the Add-on webstores for Chrome, Firefox and Edge.\n');
}
}
}
}
const shared_style = 'img.asquare {width:75px;height:75px;border:none;margin:0;padding:0;transition:all 0.3s ease} a:hover>img.asquare{transform:scale(1.3)}'; // used by multiple features
function handlerInitFixr(options) { // Webextension init
let runNow = [];
let onPageHandlers = [];
let onResizeHandlers = [];
let onFocusHandlers = [];
let onStandaloneHandlers = [];
fixr.style.add(shared_style);
onPageHandlers.push(stereotest);
if (options.topMenuItems) {
fixr.style.add(topMenuItems_style);
onPageHandlers.push(topMenuItems);
onStandaloneHandlers.push(topMenuItems);
}
if (options.ctrlClicking) {
onPageHandlers.push(ctrlClicking);
}
if (options.albumExtras) {
fixr.style.add(albumExtras_style);
onPageHandlers.push(albumExtras);
}
if (options.topPagination) {
fixr.style.add(topPagination_style);
onPageHandlers.push(topPagination);
}
if (options.shootingSpaceballs) {
onPageHandlers.push(shootingSpaceballs);
}
if (options.orderWarning) {
fixr.style.add(orderwarning_style);
onPageHandlers.push(orderWarning);
}
if (options.newsfeedLinks) {
fixr.style.add(newsfeedLinks_style);
onPageHandlers.push(newsfeedLinks);
onStandaloneHandlers.push(newsfeedLinks);
}
if (options.photoDates) {
fixr.style.add(photoDates_style);
onPageHandlers.push(photoDatesDelayed);
}
if (options.ctrlClicking) {
onPageHandlers.push(ctrlClickingDelayed);
}
if (options.exploreCalendar) {
fixr.style.add(exploreCalendar_style);
onPageHandlers.push(exploreCalendarDelayed);
}
if (options.albumTeaser) {
fixr.style.add(albumTeaser_style);
onPageHandlers.push(albumTeaserDelayed);
}
if (options.insertGMapLink) {
onPageHandlers.push(insertGMapLinkDelayed);
onStandaloneHandlers.push(mapInitializer);
}
if (options.updateTags) {
if (options.updateTags_tagmode === 'updateTags_persist') {
fixr.style.add(updateTags_style_persist);
} else {
fixr.style.add(updateTags_style_hover);
}
onPageHandlers.push(updateTagsDelayed);
}
if (options.slideshowSpeedControl) {
slideshowSpeed = options.slideshowSpeedControl_value;
runNow.push(initSlideshowSpeedHook);
onPageHandlers.push(initSlideshowSpeed);
}
fixr.init(runNow, onPageHandlers, onResizeHandlers, onFocusHandlers, onStandaloneHandlers);
}
if (window.location.href.includes('flickr.com\/services\/api\/explore\/')) {
// We are on Flickr API Explorer (WAS used for note handling before Flickr returned native note-support) and outside "normal" flickr page flow. fixr wont do here...
} else {
if (fixr.isWebExtension()) {
log('WebExtension - init with options...');
withOptionsDo(handlerInitFixr); // Load selected features and run fixr.init with them...
} else {
log('Userscript - fixr.init...');
fixr.style.add(shared_style);
fixr.style.add(albumExtras_style);
fixr.style.add(topPagination_style);
fixr.style.add(orderwarning_style);
fixr.style.add(topMenuItems_style);
fixr.style.add(photoDates_style);
fixr.style.add(newsfeedLinks_style);
fixr.style.add(exploreCalendar_style);
fixr.style.add(albumTeaser_style);
fixr.style.add(updateTags_style_hover);
// FIXR fixr.init([runNow], [onPageHandlers], [onResizeHandlers], [onFocusHandlers], [onStandaloneHandlers])
fixr.init([/* runEarly */], [stereotest, topMenuItems, ctrlClicking, albumExtras, topPagination, shootingSpaceballs, orderWarning, newsfeedLinks, photoDatesDelayed, ctrlClickingDelayed, exploreCalendarDelayed, albumTeaserDelayed, insertGMapLinkDelayed, updateTagsDelayed, userscriptWarning], [], [], [topMenuItems, newsfeedLinks, mapInitializer]);
}
}