Stig's Flickr Fixr

Show photographer's albums on photostream-pages, 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...

  1. // ==UserScript==
  2. // @name Stig's Flickr Fixr
  3. // @namespace dk.rockland.userscript.flickr.fixr
  4. // @description Show photographer's albums on photostream-pages, 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...
  5. // @author Stig Nygaard, https://www.rockland.dk, https://www.flickr.com/photos/stignygaard/
  6. // @homepageURL https://www.flickr.com/groups/flickrhacks/discuss/72157655601688753/
  7. // @supportURL https://www.flickr.com/groups/flickrhacks/discuss/72157655601688753/
  8. // @icon https://raw.githubusercontent.com/StigNygaard/Stigs_Flickr_Fixr/master/WebExtension/icons/fixr32.png
  9. // @icon64 https://raw.githubusercontent.com/StigNygaard/Stigs_Flickr_Fixr/master/WebExtension/icons/fixr64.png
  10. // @match https://*.flickr.com/*
  11. // @match *://*.flickr.net/*
  12. // @exclude *://api.flickr.com/*
  13. // @exclude *://identify.flickr.com/*
  14. // @exclude *://*.flickr.com/signin/*
  15. // @exclude *://*.flickr.com/signup/*
  16. // @exclude *://*.flickr.com/account/*
  17. // @version 2022.12.26.1
  18. // @run-at document-start
  19. // @grant none
  20. // @noframes
  21. // ==/UserScript==
  22.  
  23. // CHANGELOG - The most recent or important updates/versions:
  24. const changelog = [
  25. {version: '2022.12.26.1', description: 'Last version of the userscript. You should make the switch to the "native" browser-extension versions instead, which is available for Chrome, Edge and Firefox compatible webbrowsers.'},
  26. {version: '2022.10.29.1', description: 'For webextension, optionally make sidebar in searchresults collapsible. Collapsible sidebar feature not available in userscript version.'},
  27. {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... '},
  28. {version: '2022.01.23.0', description: 'New feature: Control slideshow speed (Supported in webextension - not supported in userscript version of Flickr Fixr)'},
  29. {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.'},
  30. {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)'},
  31. {version: '2019.10.19.0', description: 'Adjusting to Flickr 2019 updates.'},
  32. {version: '2018.11.29.0', description: 'New feature: Show available RSS/Atom newsfeeds on pages.'},
  33. {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).'},
  34. {version: '2018.10.15.0', description: 'New feature: Added Collections and Map to topmenus.'},
  35. {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.'},
  36. {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.'},
  37. {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'},
  38. {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?).'},
  39. {version: '2016.02.09.0', description: 'New feature: Link to Explore Calendar added to Explore page.'},
  40. {version: '2016.02.06.2', description: 'New feature: Top-pagers! Hover the mouse in the center just above photostreams to show a pagination-bar.'},
  41. {version: '2015.11.28.1', description: 'New feature: Album-headers are now updated with links to album-map and album-comments.'},
  42. {version: '2015.08.26.4', description: 'Initial userscript release version. Photo scale/replace, album column and tag-link feature.'}
  43. ];
  44.  
  45. const DEBUG = false;
  46.  
  47. function log(...s) {
  48. if (DEBUG && console) {
  49. console.log(...s);
  50. }
  51. }
  52.  
  53. if (DEBUG) {
  54. if ('loading' === document.readyState) {
  55. log("This script is running at document-start time.");
  56. } else {
  57. log("This script is running with document.readyState: " + document.readyState);
  58. }
  59. window.addEventListener('DOMContentLoaded', function () {
  60. log('(onDOMContentLoaded)');
  61. }, false);
  62. window.addEventListener('focus', function () {
  63. log('(onfocus)');
  64. }, false);
  65. window.addEventListener('load', function () {
  66. log('(onload)');
  67. }, false);
  68. window.addEventListener('pageshow', function () {
  69. log('(onpageshow)');
  70. }, false);
  71. window.addEventListener('resize', function () {
  72. log('(onresize)');
  73. }, false);
  74. window.addEventListener('hashchange', function () {
  75. log('(onhashchange)');
  76. }, false);
  77. window.addEventListener('blur', function () {
  78. log('(onblur)');
  79. }, false);
  80. }
  81.  
  82.  
  83. // FIXR page-tracker
  84. var fixr = fixr || {
  85. context: {
  86. pageType: '',
  87. pageSubType: '',
  88. userId: '',
  89. photographerId: '', // value might be delayed (If uninitialized, try call initPhotographerId())
  90. photographerIcon: '',
  91. photographerAlias: '', // (pathalias) bonus-info sometimes initialized (from url) when initializing photoId or albumId
  92. photographerName: '',
  93. photoId: '',
  94. albumId: '',
  95. groupId: '',
  96. galleryId: ''
  97. },
  98. content: null,
  99. pageactionsCount: 0,
  100. timerResizeActionDelayed: 0,
  101. onPageHandlers: [],
  102. onResizeHandlers: [],
  103. onFocusHandlers: [],
  104. onStandaloneHandlers: [],
  105. runningDirty: function () { // In-development and extra experiments enabled?
  106. return (DEBUG && (fixr.context.userId === '10259776@N00'));
  107. },
  108. timer: {
  109. _test: 0 // TODO
  110. },
  111. style: {
  112. _declarations: '',
  113. add: function (decl) {
  114. fixr.style._declarations += decl + ' ';
  115. },
  116. init() {
  117. if (!document.getElementById('fixrStyle')) {
  118. let styleElem = createRichElement('style', {
  119. type: 'text/css',
  120. id: 'fixrStyle'
  121. }, fixr.style._declarations);
  122. document.getElementsByTagName('head')[0].appendChild(styleElem);
  123. log('fixrStyle has been ADDED');
  124. } else {
  125. log('fixrStyle was already present');
  126. }
  127. }
  128. },
  129. clock: {
  130. _d: null,
  131. _pst: null, // Pacific Standard Time
  132. _explore: null,
  133. tick: function () {
  134. this._d = new Date();
  135. this._pst = new Date(this._d);
  136. this._pst.setHours(this._d.getHours() - 8); // PST = UTC-08
  137. this._explore = new Date(this._d);
  138. this._explore.setHours(this._d.getHours() - 28); // Explore beat, yesterday UTC-4
  139. // this._y.setDate(this._y.getDate() - 1);
  140. return this._pst;
  141. },
  142. pst: function () { // yyyy-mm-dd tt:mm PST
  143. return (this._pst || this.tick()).toISOString().substring(0, 16).replace('T', ' ') + ' PST';
  144. },
  145. explore: function () { // yyyy-mm-dd tt:mm Explore beat!
  146. if (this._explore === null) {
  147. this.tick();
  148. }
  149. return this._explore.toISOString().substring(0, 16).replace('T', ' ') + ' Explore beat!';
  150. }
  151. },
  152. isWebExtension: function () {
  153. return (typeof GM_info === 'undefined') && (typeof GM === 'undefined');
  154. },
  155. isUserscript: function () {
  156. return !fixr.isWebExtension();
  157. },
  158. initUserId: function () {
  159. if (window.auth?.user?.nsid) {
  160. fixr.context.userId = window.auth.user.nsid;
  161. return true;
  162. }
  163. return false;
  164. },
  165. initPhotographerName: function () {
  166. if (fixr.content.querySelector('a.owner-name')) {
  167. fixr.context.photographerName = fixr.content.querySelector('a.owner-name').innerText;
  168. return true;
  169. }
  170. return false;
  171. },
  172. initPhotographerId: function () { // photographer/attribution id
  173.  
  174. // todo: This needs a rewrite some day...
  175.  
  176. let elem;
  177. if (document.querySelector('div.photostream-page-view')) {
  178. // photostream
  179. elem = document.querySelector('div.photostream-page-view div.fluid-photostream-coverphoto-view .avatar.person');
  180. } else if (document.querySelector('div.photo-page-scrappy-view')) {
  181. // photopage
  182. elem = document.querySelector('div.photo-page-scrappy-view div.sub-photo-view .photo-attribution .avatar.person');
  183. } else if (document.querySelector('div.photo-page-lightbox-scrappy-view')) {
  184. // photopage lightbox
  185. elem = document.querySelector('div.photo-page-lightbox-scrappy-view div.photo-well-view .photo-attribution .avatar.person');
  186. } else if (document.querySelector('div.album-page-view')) {
  187. // album page
  188. elem = document.querySelector('div.album-page-view div.album-container div.album-header-view .album-attribution .avatar.person');
  189. } else if (document.querySelector('div.coverphoto-content .avatar.person')) {
  190. // fallback, modern design pages
  191. elem = document.querySelector('div.coverphoto-content .avatar.person');
  192. } else if (document.querySelector('div.subnav-middle div.sn-avatar > img')) {
  193. // fallback, old design pages
  194. elem = document.querySelector('div.subnav-middle div.sn-avatar > img');
  195. } else {
  196. log('fixr.initPhotographerId() - We do not look for photographerId on this page');
  197. return true;
  198. }
  199. if (!elem) {
  200. log('fixr.initPhotographerId() - Attribution elem NOT found - returning false');
  201. return false;
  202. } // re-run a little later???
  203. log('fixr.initPhotographerId() - Attribution elem found');
  204. let result;
  205. if (elem.tagName.toUpperCase() === 'IMG' && elem.src) {
  206. result = elem.src.match(/https:(\/\/[^#\?]+\.com\/[^#\?]+\/buddyicon[^\?\#]+)[^#]*#(\d+\@N\d{2})/i);
  207. } else if (elem.style.backgroundImage) {
  208. log('fixr.initPhotographerId() - elem has style.backgroundImage "' + elem.style.backgroundImage + '", now looking for the attribution id...');
  209. // var pattern = /\/buddyicons\/(\d+\@N\d{2})\D+/i;
  210. result = elem.style.backgroundImage.match(/url[^#\?]+(\/\/[^#\?]+\.com\/[^#\?]+\/buddyicon[^\?\#]+)[^#]*#(\d+\@N\d{2})/i);
  211. }
  212. if (result) {
  213. log('fixr.initPhotographerId() - Attribution pattern match found: ' + result[0]);
  214. log('fixr.initPhotographerId() - the attribution icon is ' + result[1]);
  215. log('fixr.initPhotographerId() - the attribution id is ' + result[2]);
  216. fixr.context.photographerIcon = result[1];
  217. fixr.context.photographerId = result[2];
  218. log('fixr.initPhotographerId() - returning true...');
  219. return true;
  220. } else {
  221. log('fixr.initPhotographerId() - attribution pattern match not found');
  222. return false;
  223. }
  224. },
  225. initPhotoId: function () { // Photo Id
  226. // *flickr.com/photos/user/PId/*
  227. const pattern = /^\/photos\/([^\/]+)\/([\d]{2,})/i;
  228. const result = window.location.pathname.match(pattern);
  229. if (result) {
  230. log('url match med photoId=' + result[2]);
  231. log('url match med photographerAlias=' + result[1]);
  232. fixr.context.photoId = result[2];
  233. fixr.context.photographerAlias = result[1];
  234. return true;
  235. } else {
  236. log('*** initPhotoId() returnerer false! reg-pattern fandt ikke match i pathname=' + window.location.pathname);
  237. }
  238. return false;
  239. },
  240. initAlbumId: function () {
  241. // *flickr.com/photos/user/albums/AId/*
  242. // *flickr.com/photos/user/sets/AId/*
  243. let pattern = /^\/photos\/([^\/]+)\/albums\/([\d]{2,})/i;
  244. let result = window.location.pathname.match(pattern);
  245. if (!result) {
  246. pattern = /^\/photos\/([^\/]+)\/sets\/([\d]{2,})/i;
  247. result = window.location.pathname.match(pattern);
  248. }
  249. if (result) {
  250. log('url match med albumId=' + result[2]);
  251. log('url match med photographerAlias=' + result[1]);
  252. fixr.context.albumId = result[2];
  253. fixr.context.photographerAlias = result[1];
  254. return true;
  255. }
  256. return false;
  257. },
  258. pageActions: function () {
  259. fixr.clock.tick();
  260. if (fixr.content) {
  261. log('fixr.pageActions() has started with fixr.content defined');
  262. } else {
  263. log('fixr.pageActions() was called, but fixr.content NOT defined');
  264. return;
  265. }
  266. fixr.pageactionsCount++;
  267. for (let p in fixr.context) { // reset context on new page
  268. if (fixr.context.hasOwnProperty(p)) {
  269. fixr.context[p] = '';
  270. }
  271. }
  272. if (fixr.content.querySelector('div.photostream-page-view')) {
  273. if (fixr.content.querySelector('div.slideshow-view')) {
  274. fixr.context.pageType = 'PHOTOSTREAM SLIDESHOW';
  275. } else {
  276. fixr.context.pageType = 'PHOTOSTREAM';
  277. }
  278. } else if (fixr.content.querySelector('div.photo-page-scrappy-view')) {
  279. fixr.context.pageType = 'PHOTOPAGE';
  280. if (fixr.content.querySelector('div.vr-overlay-view') && fixr.content.querySelector('div.vr-overlay-view').hasChildNodes()) {
  281. fixr.context.pageSubType = 'VR'; // maybe I can find a better way to detect, not sure how reliable this is?
  282. } else if (fixr.content.querySelector('div.videoplayer')) {
  283. fixr.context.pageSubType = 'VIDEO';
  284. } else {
  285. fixr.context.pageSubType = 'PHOTO';
  286. }
  287. } else if (fixr.content.querySelector('div.photo-page-lightbox-scrappy-view')) {
  288. fixr.context.pageType = 'PHOTOPAGE LIGHTBOX';
  289. if (fixr.content.querySelector('div.vr-overlay-view') && fixr.content.querySelector('div.vr-overlay-view').hasChildNodes()) {
  290. fixr.context.pageSubType = 'VR'; // VR-mode currently not supported in lightbox?
  291. } else if (fixr.content.querySelector('div.videoplayer')) {
  292. fixr.context.pageSubType = 'VIDEO';
  293. } else {
  294. fixr.context.pageSubType = 'PHOTO';
  295. }
  296. } else if (fixr.content.querySelector('div.albums-list-page-view')) {
  297. fixr.context.pageType = 'ALBUMSLIST';
  298. } else if (fixr.content.querySelector('div.album-page-view')) {
  299. if (fixr.content.querySelector('div.slideshow-view')) {
  300. fixr.context.pageType = 'ALBUM SLIDESHOW';
  301. } else {
  302. fixr.context.pageType = 'ALBUM';
  303. }
  304. } else if (fixr.content.querySelector('div.cameraroll-page-view')) {
  305. fixr.context.pageType = 'CAMERAROLL';
  306. } else if (fixr.content.querySelector('div.explore-page-view')) {
  307. fixr.context.pageType = 'EXPLORE';
  308. } else if (fixr.content.querySelector('div.favorites-page-view')) {
  309. if (fixr.content.querySelector('div.slideshow-view')) {
  310. fixr.context.pageType = 'FAVORITES SLIDESHOW';
  311. } else {
  312. fixr.context.pageType = 'FAVORITES';
  313. }
  314. } else if (fixr.content.querySelector('div.groups-list-view')) {
  315. fixr.context.pageType = 'GROUPSLIST'; // personal grouplist
  316. } else if (fixr.content.querySelector('div#activityFeed')) { // id=main i stedet for id=fixr.content
  317. fixr.context.pageType = 'ACTIVITYFEED'; // aka. front page -> UPDATES ?
  318. } else if (fixr.content.querySelector('div#allsizes-photo')) {
  319. fixr.context.pageType = 'SIZES'; // View all sizes - page
  320. } else if (fixr.content.querySelector('div.search-photos-unified-page-view')) {
  321. fixr.context.pageType = 'SEARCHRESULTPHOTOS';
  322. } else if (fixr.content.querySelector('div.search-people-page-view')) {
  323. fixr.context.pageType = 'SEARCHRESULTPEOPLE';
  324. } else if (fixr.content.querySelector('div.search-groups-page-view')) {
  325. fixr.context.pageType = 'SEARCHRESULTGROUPS';
  326. } else {
  327. // fixr.context.pageType = ''; // unknown
  328. }
  329.  
  330. log('fixr.context.pageType = ' + fixr.context.pageType);
  331. log('fixr.context.pageSubType = ' + fixr.context.pageSubType);
  332. if (fixr.initUserId()) {
  333. log('fixr.initUserId() returned with succes: ' + fixr.context.userId);
  334. } else {
  335. log('fixr.initUserId() returned FALSE!');
  336. }
  337. if (fixr.initPhotographerId()) {
  338. log('fixr.initPhotographerId() returned true in first try...');
  339. } else {
  340. log('fixr.initPhotographerId() returned false - re-running delayed...');
  341. setTimeout(fixr.initPhotographerId, 1800);
  342. }
  343. if (fixr.initPhotoId()) {
  344. log('fixr.initPhotoId() returned true in first try...');
  345. } else {
  346. log('fixr.initPhotoId() returned false - re-running delayed...');
  347. setTimeout(fixr.initPhotoId, 1500);
  348. }
  349. if (fixr.initAlbumId()) {
  350. log('fixr.initAlbumId() returned true in first try...');
  351. }
  352. if (fixr.initPhotographerName()) {
  353. log('fixr.initPhotographerName() returned true in first try...');
  354. } else {
  355. setTimeout(fixr.initPhotographerName, 1500);
  356. }
  357.  
  358. // Now run the page handlers....
  359. if (fixr.onPageHandlers && fixr.onPageHandlers.length) {
  360. log('We have ' + fixr.onPageHandlers.length + ' onPage handlers starting now...');
  361. for (let f = 0; f < fixr.onPageHandlers.length; f++) {
  362. fixr.onPageHandlers[f]();
  363. }
  364. }
  365. },
  366. setupContent: function () {
  367. if (document.getElementById('content')) {
  368. fixr.content = document.getElementById('content');
  369. } else if (document.getElementById('main')) {
  370. fixr.content = document.getElementById('main'); // frontpage
  371. }
  372. if (fixr.content?.id) {
  373. log('fixr.content.id = ' + fixr.content.id);
  374. } else {
  375. log('content or main element NOT found!');
  376. }
  377. },
  378. runPageActionsIfMissed: function () {
  379. if (fixr.pageactionsCount === 0) {
  380. log('Vi kører fixr.pageActions() på bagkant via onload...');
  381. fixr.setupContent();
  382. if (fixr.content === null) {
  383. log('Vi kan IKKE køre fixr.pageActions() på bagkant, da fixr.content ikke er defineret');
  384. return;
  385. }
  386. fixr.pageActions();
  387. } else {
  388. log('ej nødvendigt at køre fixr.pageActions() på bagkant i dette tilfælde...');
  389. }
  390. },
  391. runIfStandalonePage: function () {
  392. if (fixr.content === null && fixr.pageactionsCount === 0) { // if really looks like a "standalone page"...
  393. // Now run the standalone handlers
  394. if (fixr.onStandaloneHandlers && fixr.onStandaloneHandlers.length) {
  395. log('We have ' + fixr.onStandaloneHandlers.length + ' standalone handlers starting now...');
  396. for (let f = 0; f < fixr.onStandaloneHandlers.length; f++) {
  397. fixr.onStandaloneHandlers[f]();
  398. }
  399. }
  400. }
  401. },
  402. runDelayedPageActionsIfMissed: function () {
  403. setTimeout(fixr.runPageActionsIfMissed, 2000);
  404. setTimeout(fixr.runIfStandalonePage, 500);
  405. },
  406. resizeActions: function () {
  407. if (fixr.onResizeHandlers && fixr.onResizeHandlers.length) {
  408. for (let f = 0; f < fixr.onResizeHandlers.length; f++) {
  409. fixr.onResizeHandlers[f]();
  410. }
  411. }
  412. },
  413. resizeActionsDelayed: function () { // or "preburner"
  414. clearTimeout(fixr.timerResizeActionDelayed);
  415. fixr.timerResizeActionDelayed = setTimeout(fixr.resizeActions, 250);
  416. },
  417. focusActions: function () {
  418. if (fixr.onFocusHandlers && fixr.onFocusHandlers.length) {
  419. for (let f = 0; f < fixr.onFocusHandlers.length; f++) {
  420. fixr.onFocusHandlers[f]();
  421. }
  422. }
  423. },
  424. setupObserver: function () {
  425. log('fixr.setupObserve INITIALIZATION START');
  426. fixr.setupContent();
  427. if (fixr.content === null) {
  428. log('Init fails because content not defined');
  429. return;
  430. }
  431. // create an observer instance
  432. let observer = new MutationObserver(function (mutations) {
  433. log('NEW PAGE MUTATION!');
  434. //mutations.forEach(function(mutation) {
  435. // log('MO: '+mutation.type); // might check for specific type of "mutations" (MutationRecord)
  436. //});
  437. fixr.pageActions();
  438. }); // MutationObserver end
  439. // configuration of the observer:
  440. let config = {attributes: false, childList: true, subtree: false, characterData: false};
  441. observer.observe(fixr.content, config);
  442. log('fixr.setupObserve INITIALIZATION DONE');
  443. },
  444. init: function (runNow, onPageHandlerArray, onResizeHandlerArray, onFocusHandlerArray, onStandaloneHandlerArray) {
  445. // General page-change observer setup:
  446. if (document.readyState === 'interactive') { // already late?
  447. fixr.setupObserver();
  448. }
  449. window.addEventListener('DOMContentLoaded', fixr.setupObserver, false); // Page on DOMContentLoaded
  450. window.addEventListener('load', fixr.runDelayedPageActionsIfMissed, false); // Page on load
  451. window.addEventListener('resize', fixr.resizeActionsDelayed, false); // også på resize
  452. window.addEventListener('focus', fixr.focusActions, false);
  453. if (onPageHandlerArray && onPageHandlerArray.length) {
  454. fixr.onPageHandlers = onPageHandlerArray; // Replace by adding with a one-by-one by "helper" for flexibility?
  455. }
  456. fixr.onPageHandlers.push(fixr.style.init); // styles
  457. if (onResizeHandlerArray && onResizeHandlerArray.length) {
  458. fixr.onResizeHandlers = onResizeHandlerArray; // Replace by adding with a one-by-one by "helper" for flexibility?
  459. }
  460. if (onFocusHandlerArray && onFocusHandlerArray.length) {
  461. fixr.onFocusHandlers = onFocusHandlerArray;
  462. }
  463. if (onStandaloneHandlerArray && onStandaloneHandlerArray.length) { // on standalone pages, not part of "single page application"
  464. fixr.onStandaloneHandlers = onStandaloneHandlerArray;
  465. fixr.onStandaloneHandlers.push(fixr.style.init); // styles
  466. }
  467.  
  468. if (runNow && runNow.length) {
  469. log('We have ' + runNow.length + ' early running methods starting now at document.readyState = ' + document.readyState);
  470. for (let f = 0; f < runNow.length; f++) {
  471. runNow[f]();
  472. }
  473. }
  474. }
  475. };
  476. // FIXR page-tracker end
  477.  
  478.  
  479. 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/
  480.  
  481. function escapeHTML(str) {
  482. return str.replace(/[&"'<>]/g, (m) => ({"&": "&amp;", '"': "&quot;", "'": "&#39;", "<": "&lt;", ">": "&gt;"})[m]);
  483. }
  484.  
  485. function createRichElement(tagName, attributes, ...content) {
  486. let element = document.createElement(tagName);
  487. if (attributes) {
  488. for (const [attr, value] of Object.entries(attributes)) {
  489. element.setAttribute(attr, value);
  490. }
  491. }
  492. if (content?.length) {
  493. element.append(...content);
  494. }
  495. return element;
  496. }
  497.  
  498. function insertGMapLink() {
  499. if (fixr.context.pageType !== 'PHOTOPAGE') {
  500. return; // exit if not photopage
  501. }
  502. log('insertGMapLink() running at readystate=' + document.readyState + ' and with photoId=' + fixr.context.photoId);
  503. if (fixr.context.photoId) {
  504. let maplink = fixr.content.querySelector('a.static-maps');
  505. if (maplink) {
  506. if (!document.getElementById('googlemapslink') && maplink.getAttribute('href') && (maplink.getAttribute('href').includes('map/?'))) {
  507. try {
  508. let lat = maplink.getAttribute('href').match(/Lat=(\-?[\d\.]+)/i)[1];
  509. let lon = maplink.getAttribute('href').match(/Lon=(\-?[\d\.]+)/i)[1];
  510. let gmaplink = createRichElement('a', {
  511. href: 'https://www.google.com/maps/search/?api=1&query=' + lat + ',' + lon,
  512. id: 'googlemapslink'
  513. }, 'Show location on Google Maps');
  514. fixr.content.querySelector('li.c-charm-item-location').insertAdjacentElement('beforeend', createRichElement('div', {class: 'location-data-container'}, gmaplink));
  515. } catch (e) {
  516. log('Failed creating Google Maps link: ' + e);
  517. }
  518. } else {
  519. log('link NOT inserted by insertGMapLink(). Invalid element or link already created. readystate=' + document.readyState);
  520. }
  521. } else {
  522. log('NO maplink found at readystate=' + document.readyState + '. Re-try later?');
  523. }
  524. } else {
  525. log('NO photoId found at readystate=' + document.readyState);
  526. }
  527. }
  528.  
  529. function insertGMapLinkDelayed() {
  530. if (fixr.context.pageType === 'PHOTOPAGE') {
  531. log('insertGMapLinkDelayed() running... with pageType=' + fixr.context.pageType);
  532. setTimeout(insertGMapLink, 1500); // make maplink work better on photopage
  533. setTimeout(insertGMapLink, 3500); // Twice. Photopage is sometimes a bit slow building
  534. setTimeout(insertGMapLink, 8000); // Triple. Photopage is sometimes very slow building
  535. }
  536. }
  537.  
  538. function mapInitializer() {
  539. if (window.location.href.includes('flickr.com/map/?')) {
  540. // https://developer.mozilla.org/en-US/docs/Web/API/URL
  541. const url = new URL(window.location.href);
  542. // https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
  543. const imgId = url.searchParams.get('photo');
  544. if (imgId) {
  545. const focusImg = document.getElementById('f_img_thumb_' + imgId);
  546. if (focusImg) {
  547. focusImg.click(); // close and ...
  548. focusImg.click(); // reopen to highlight position on map
  549. }
  550. }
  551. }
  552. }
  553.  
  554. 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}';
  555.  
  556. function topMenuItems() {
  557. // User dropdown menu
  558. let m = document.querySelector('li[data-context=you] > ul.gn-submenu') || document.querySelector('li[data-context=you] div#you-panel ul');
  559. if (m) {
  560. let gid = null;
  561. if (m.querySelector('a[data-track=gnYouGroupsClick]')) {
  562. gid = m.querySelector('a[data-track=gnYouGroupsClick]').parentElement;
  563. }
  564. if (!gid && m.querySelector('a[data-track=You-groups]')) {
  565. gid = m.querySelector('a[data-track=You-groups]').parentElement;
  566. }
  567. let aad = m.querySelector('a[data-track=gnYouSetsClick]') || m.querySelector('a[data-track=You-sets]');
  568. if (aad && gid) {
  569. if (gid.hasAttribute('aria-label') && !m.querySelector('li[aria-label=Tags]')) {
  570. // latest design
  571. gid.insertAdjacentElement('afterend', createRichElement('li', {
  572. class: 'menuitem',
  573. role: 'menuitem',
  574. 'aria-label': 'Tags'
  575. }, createRichElement('a', {'data-track': 'gnYouTagsClick', href: '/photos/me/tags'}, 'Tags')));
  576. aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
  577. class: 'menuitem',
  578. role: 'menuitem',
  579. 'aria-label': 'Map'
  580. }, createRichElement('a', {'data-track': 'gnYouMapClick', href: '/photos/me/map'}, 'Map')));
  581. aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
  582. class: 'menuitem',
  583. role: 'menuitem',
  584. 'aria-label': 'Collections'
  585. }, createRichElement('a', {
  586. 'data-track': 'gnYouCollectionsClick',
  587. href: '/photos/me/collections'
  588. }, 'Collections')));
  589. } else if (gid.classList.contains('gn-subnav-item') && !m.querySelector('a[data-track=You-tags]')) {
  590. // earlier design
  591. gid.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
  592. 'data-track': 'You-tags',
  593. href: '/photos/me/tags'
  594. }, 'Tags')));
  595. aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
  596. 'data-track': 'You-map',
  597. href: '/photos/me/map'
  598. }, 'Map')));
  599. aad.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'gn-subnav-item'}, createRichElement('a', {
  600. 'data-track': 'You-collections',
  601. href: '/photos/me/collections'
  602. }, 'Collections')));
  603. }
  604. }
  605. }
  606. // Photographer menu bar
  607. m = document.querySelector('ul.links[role=menubar]') || document.querySelector('ul.nav-links');
  608. if (m) {
  609. let gib = m.querySelector('li#groups') || m.querySelector('li.sn-groups');
  610. let aab = m.querySelector('li#albums a') || m.querySelector('li.sn-navitem-sets a');
  611. if (aab && gib) {
  612. m.classList.add('extraitems'); // mark extra items being added (so adjust spacing in style)
  613. if (gib.id === 'groups' && !m.querySelector('li#tags')) {
  614. // latest design
  615. gib.insertAdjacentElement('afterend', createRichElement('li', {
  616. id: 'tags',
  617. class: 'link',
  618. role: 'menuitem'
  619. }, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/tags'}, createRichElement('span', {}, 'Tags'))));
  620. aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
  621. id: 'map',
  622. class: 'link',
  623. role: 'menuitem'
  624. }, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/map'}, createRichElement('span', {}, 'Map'))));
  625. aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {
  626. id: 'collections',
  627. class: 'link',
  628. role: 'menuitem'
  629. }, createRichElement('a', {href: '/photos/' + fixr.context.photographerId + '/collections'}, createRichElement('span', {}, 'Collections'))));
  630. } else if (gib.classList.contains('sn-groups') && !m.querySelector('li.sn-tags')) {
  631. // earlier design
  632. gib.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-tags'}, createRichElement('a', {
  633. 'data-track': 'YouSubnav-tags',
  634. href: '/photos/' + fixr.context.photographerId + '/tags'
  635. }, 'Tags')));
  636. aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-map'}, createRichElement('a', {
  637. 'data-track': 'YouSubnav-map',
  638. href: '/photos/' + fixr.context.photographerId + '/map'
  639. }, 'Map')));
  640. aab.parentElement.insertAdjacentElement('afterend', createRichElement('li', {class: 'sn-navitem sn-collections'}, createRichElement('a', {
  641. 'data-track': 'YouSubnav-collections',
  642. href: '/photos/' + fixr.context.photographerId + '/collections'
  643. }, 'Collections')));
  644. }
  645. }
  646. }
  647. }
  648.  
  649. let album = { // cache to avoid repeating requests
  650. albumId: '',
  651. commentCount: 0,
  652. comment: [],
  653. description: ''
  654. };
  655.  
  656. let _wsGetPhotosetCommentsLock = 0;
  657.  
  658. function wsGetPhotosetComments() { // Call Flickr REST API to get album comments
  659. let diff = Date.now() - _wsGetPhotosetCommentsLock;
  660. if ((_wsGetPhotosetCommentsLock > 0) && (diff < 50)) {
  661. log('Skipping wsGetPhotosetComments() because already running?: ' + diff);
  662. // *** maybe add a check to see if we are still on same album?!
  663. return;
  664. }
  665. _wsGetPhotosetCommentsLock = Date.now();
  666.  
  667. function handleResponse(response) {
  668. if (response.ok) {
  669. if (response.headers.get('content-type') && response.headers.get('content-type').includes('application/json')) {
  670. return response.json()
  671. }
  672. throw new Error('Response was not in expected json format.');
  673. }
  674. throw new Error('Network response was not ok.');
  675. }
  676.  
  677. function handleResult(obj) {
  678.  
  679. album.albumId = fixr.context.albumId;
  680. album.commentCount = -1;
  681.  
  682. if (obj.stat === "ok") {
  683. log("flickr.photosets.comments.getList returned ok");
  684. if (obj.comments?.photoset_id) {
  685. album.albumId = obj.comments.photoset_id;
  686. }
  687. if (obj.comments?.comment) {
  688. album.commentCount = obj.comments.comment.length;
  689. } else {
  690. album.commentCount = 0;
  691. }
  692. } else {
  693. log('flickr.photosets.comments.getList returned an ERROR: obj.stat=' + obj.stat + ', obj.code=' + obj.code + ', obj.message=' + obj.message);
  694. }
  695.  
  696. if (document.getElementById('albumCommentCount')) {
  697. if (album.commentCount === -1) {
  698. document.getElementById('albumCommentCount').innerText = '?';
  699. } else {
  700. document.getElementById('albumCommentCount').innerText = String(album.commentCount);
  701. }
  702. } else {
  703. log('albumCommentCount element not found');
  704. }
  705.  
  706. _wsGetPhotosetCommentsLock = 0;
  707. }
  708.  
  709. function handleError(error) {
  710. console.log('There has been a problem with your fetch operation: ', error.message);
  711. log('There has been a problem with your fetch operation: ' + error);
  712. }
  713.  
  714. if (fixr.isWebExtension()) {
  715. // Call fetch() from background-script in WebExtensions, because changes in Chrome/Chromium https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
  716. browser.runtime.sendMessage({
  717. msgtype: "flickrservice",
  718. method: "flickr.photosets.comments.getList",
  719. fkey: fkey,
  720. options: {photoset_id: fixr.context.albumId}
  721. }).then(handleResult).catch(handleError);
  722. } else { // Userscript (So far it still works, also on Chrome/Tampermonkey...)
  723. 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);
  724. }
  725. }
  726.  
  727.  
  728. let albums = { // cache albums to avoid repeating requests
  729. ownerId: '',
  730. column: '',
  731. count: 0
  732. };
  733.  
  734. function getAlbumlist() {
  735. let _reqAlbumlist = null;
  736. if (window.XMLHttpRequest) { // TODO: Surprised to see I'm still using XMLHttpRequest !
  737. _reqAlbumlist = new XMLHttpRequest();
  738. if (typeof _reqAlbumlist.overrideMimeType !== 'undefined') {
  739. _reqAlbumlist.overrideMimeType('text/html');
  740. }
  741.  
  742. _reqAlbumlist.onreadystatechange = function () {
  743. if (_reqAlbumlist.readyState === 4 && _reqAlbumlist.status === 200) {
  744. log('_reqAlbumlist returned status=' + _reqAlbumlist.status); // + ', \ntext:\n' + _reqAlbumlist.responseText);
  745. let doc = document.implementation.createHTMLDocument("sizeDoc");
  746. doc.documentElement.innerHTML = _reqAlbumlist.responseText; // NOTICE, this is NOT inserted directly into HTML-document! In the following DOM-content of doc is being analyzed...
  747. albums.ownerId = fixr.context.photographerId;
  748. albums.column = new DocumentFragment();
  749. albums.count = 0;
  750. const alist = Array.from(doc.body.querySelectorAll('div.photo-list-album-view'));
  751. const imgPattern = /url\([\'\"]*([^\)\'\"]+)(\.[jpgtifn]{3,4})[\'\"]*\)/i;
  752. let columnhead = createRichElement('div', {style: 'margin:0 0 .8em 0'});
  753. if (document.getElementById('albumTeaser')) {
  754. if (alist && alist.length > 0) {
  755. columnhead.textContent = "Albums";
  756. albums.column.appendChild(columnhead);
  757. albums.count = alist.length;
  758. for (let e of alist.slice(0, 10)) {
  759. let imgUrl = '';
  760. //log(e.outerHTML);
  761. // var result = e.style.backgroundImage.match(imgPattern); // strangely not working in Chrome
  762. let result = (e.outerHTML).match(imgPattern); // quick work-around for above (works for now)
  763. if (result) {
  764. // imgUrl = result[1].replace(/_[a-z]$/, '') + '_s' + result[2];
  765. imgUrl = result[1].replace(/_[a-z]$/, '') + '_q' + result[2];
  766. log('imgUrl=' + imgUrl);
  767. } else {
  768. log('No match on imgPattern');
  769. }
  770. var a = e.querySelector('a[href][title]'); // sub-element
  771. if (a) {
  772. log('Album title: ' + a.title);
  773. log('Album url: ' + a.getAttribute('href'));
  774. let album = document.createElement("div");
  775. let thumbnail = createRichElement('img', {src: imgUrl, class: 'asquare', alt: ''});
  776. let albumtitle = createRichElement('div', {style: 'margin:0 0 .8em 0'}, a.title);
  777. let anchor = createRichElement('a', {href: a.getAttribute('href')}, thumbnail, albumtitle);
  778. album.appendChild(anchor);
  779. albums.column.appendChild(album);
  780. } else {
  781. log('a element not found?');
  782. }
  783. }
  784. } else if (alist) {
  785. if (doc.body.querySelector('h3')) {
  786. columnhead.textContent = doc.body.querySelector('h3').innerText;
  787. albums.column.appendChild(columnhead);
  788. }
  789. } else {
  790. log('(e Undefined) Problem reading albums or no albums??? : ' + _reqAlbumlist.responseText);
  791. }
  792. let foot = document.createElement("div");
  793. let cursive = document.createElement("i");
  794. let moreanchor = document.createElement("a");
  795. moreanchor.href = "/photos/" + (fixr.context.photographerAlias || fixr.context.photographerId) + "/albums/";
  796. moreanchor.textContent = albums.count > 10 ? 'More albums...' : (albums.count === 0 ? 'No albums found...' : '');
  797. cursive.appendChild(moreanchor);
  798. foot.appendChild(cursive);
  799. albums.column.appendChild(foot);
  800. document.getElementById('albumTeaser').appendChild(albums.column);
  801. } else {
  802. log('albumTeaser NOT FOUND!?!');
  803. }
  804. } else {
  805. // wait for the call to complete
  806. }
  807. };
  808.  
  809. if (fixr.context.photographerId) {
  810. const url = 'https://' + window.location.hostname + '/photos/' + (fixr.context.photographerAlias || fixr.context.photographerId) + '/albums';
  811. _reqAlbumlist.open('GET', url, true);
  812. _reqAlbumlist.send(null);
  813. } else {
  814. log('Attribution user (photographer) not found');
  815. }
  816. } else {
  817. log('understøtter ikke XMLHttpRequest');
  818. }
  819. }
  820.  
  821. const albumTeaser_style = 'div#albumTeaser {border:none;margin:0;padding:0;position:absolute;top:0;right:-120px;width:100px}';
  822.  
  823. function albumTeaser() {
  824. if (fixr.context.pageType !== 'PHOTOSTREAM') {
  825. return; // exit if not photostream
  826. }
  827. log('albumTeaser() running');
  828. let dpc = document.querySelector('div.photolist-container');
  829. if (!dpc) {
  830. return;
  831. }
  832. log('AlbumTeaser found div.photolist-container');
  833. if (!document.getElementById('albumTeaser')) {
  834. dpc.style.position = "relative";
  835. dpc.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'albumTeaser'}));
  836. }
  837. if (document.getElementById('albumTeaser')) {
  838. getAlbumlist(); // også check på fixr.context.photographerId ?
  839. }
  840. }
  841.  
  842. let _timerAlbumTeaserDelayed;
  843.  
  844. function albumTeaserDelayed() {
  845. if (fixr.context.pageType !== 'PHOTOSTREAM') {
  846. return; // exit if not photostream
  847. }
  848. log('albumTeaserDelayed() running...');
  849. clearTimeout(_timerAlbumTeaserDelayed);
  850. _timerAlbumTeaserDelayed = setTimeout(albumTeaser, 1500);
  851. }
  852.  
  853. 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}';
  854.  
  855. function exploreCalendar() {
  856. if (fixr.context.pageType !== 'EXPLORE') {
  857. return; // exit if not explore/interestingness
  858. }
  859. log('exploreCalendar() running');
  860. let dtr = document.querySelector('div.title-row');
  861. if (!dtr) {
  862. return;
  863. }
  864. log('exploreCalendar found div.photo-list-view');
  865. if (!document.getElementById('exploreCalendar')) {
  866. dtr.style.position = "relative";
  867. let exploreMonth = fixr.clock.explore().substring(0, 7).replace('-', '/');
  868. let explCal = createRichElement('a', {href: '/explore/interesting/' + exploreMonth + '/'}, createRichElement('img', {
  869. src: 'https://c2.staticflickr.com/2/1701/24895062996_78719dec15_o.jpg',
  870. class: 'asquare',
  871. alt: ''
  872. }), createRichElement('div', {}, 'Explore Calendar'));
  873. let freshUpl = createRichElement('a', {
  874. 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),
  875. title: 'If you are an adventurer and want to explore something different than everybody else...'
  876. }, createRichElement('img', {
  877. src: 'https://c2.staticflickr.com/2/1617/25534100345_b4a3fe78f1_o.jpg',
  878. class: 'asquare',
  879. alt: ''
  880. }), createRichElement('div', {}, 'Fresh uploads'));
  881. dtr.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'exploreCalendar'}, explCal, freshUpl));
  882. log('San Francisco PST UTC-8: ' + fixr.clock.pst());
  883. log('Explore Beat (Yesterday, UTC-4): ' + fixr.clock.explore());
  884. }
  885. }
  886.  
  887. // SIMPLE
  888. // const collapsibleSidebar_style = "#search-unified-content {position: relative} #sidebartoggle {position: absolute; right: 0; top: 5em; width: 3em; height: 1.5em; background-color: #CFC; margin-right:-2em; z-index: 10} body.searchsidebar_closed .sidebar-column {display:none}";
  889. // BETTER
  890. const collapsibleSidebar_style = "body.searchsidebar_closed .search-container-w-sidebar .sidebar-column {width: 0px !important; margin-left: 0 !important} body.searchsidebar_closed .sidebar-content-container > div:not(#sidebartoggle) {overflow:hidden; display:none} .sidebar-content-container, #search-unified-content {position: relative} #sidebartoggle {position: absolute; right: -1.7em; top: 6em; display: flex; justify-content: center; align-items: center; width: 2em; height: 2em; color: #128fdc; border: 2px solid #128fdc; border-radius: 50%; background-color: #FFF; z-index: 10; cursor: pointer; transition: all 0.3s; } body.searchsidebar_closed #sidebartoggle {transform: rotate(180deg);}";
  891. function collapsibleSidebar() {
  892. if (['SEARCHRESULTPHOTOS', 'SEARCHRESULTPEOPLE', 'SEARCHRESULTGROUPS'].includes(fixr.context.pageType)) {
  893. // SIMPLE:
  894. // var cnt = document.querySelector('#search-unified-content');
  895. // if (cnt) {
  896. // var toggle = createRichElement('div', {id: 'sidebartoggle'}, '❯');
  897. // cnt.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'sidebartoggle'}, '[<->]'));
  898. // ...
  899. // BETTER:
  900. let cnt = document.querySelector('.sidebar-content-container');
  901. if (cnt) {
  902. if (!document.getElementById('sidebartoggle')) {
  903. let toggle = createRichElement('div', {id: 'sidebartoggle'}, '❯');
  904. cnt.insertAdjacentElement('afterbegin', toggle);
  905. setTimeout(function () {
  906. window.dispatchEvent(new Event('resize', {'cancelable': true}))
  907. }, 100);
  908. toggle.addEventListener('click', function (e) {
  909. document.body.classList.toggle('searchsidebar_closed');
  910. setTimeout(function () {
  911. window.dispatchEvent(new Event('resize', {'cancelable': true}))
  912. }, 100);
  913. })
  914. }
  915. } else {
  916. setTimeout(collapsibleSidebar, 2000);
  917. }
  918. }
  919. }
  920.  
  921. let _timerExploreCalendarDelayed;
  922.  
  923. function exploreCalendarDelayed() {
  924. if (fixr.context.pageType !== 'EXPLORE') {
  925. return; // exit if not explore/interestingness
  926. }
  927. log('albumTeaserDelayed() running...');
  928. clearTimeout(_timerExploreCalendarDelayed);
  929. _timerExploreCalendarDelayed = setTimeout(exploreCalendar, 1500);
  930. }
  931.  
  932. function ctrlClick(e) {
  933. let elem, evt = e ? e : event;
  934. if (evt.srcElement) elem = evt.srcElement;
  935. else if (evt.target) elem = evt.target;
  936. if (evt.ctrlKey) {
  937. log('Ctrl clicked. Further scripted click-event handling canceled. Allow the default ctrl-click handling in my browser.');
  938. evt.stopPropagation();
  939. }
  940. }
  941.  
  942. function ctrlClicking() {
  943. let plv = document.querySelectorAll('div.photo-list-view');
  944. for (let i = 0; i < plv.length; i++) {
  945. log('ctrlClicking(): plv[' + i + '] found!');
  946. // Allow me to open tabs in background by ctrl-click in Firefox:
  947. plv[i].parentNode.addEventListener('click', ctrlClick, true);
  948. }
  949. }
  950.  
  951. let _timerCtrlClicking;
  952.  
  953. function ctrlClickingDelayed() {
  954. log('ctrlClickingDelayed() running...');
  955. clearTimeout(_timerCtrlClicking);
  956. _timerCtrlClicking = setTimeout(ctrlClicking, 1500);
  957. }
  958.  
  959. 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}';
  960.  
  961. function topPagination() {
  962. log('topPagination()');
  963. let bottomPagination = document.querySelector('.pagination-view');
  964. if (!bottomPagination) {
  965. bottomPagination = document.querySelector('.explore-pagination');
  966. }
  967. if (bottomPagination && !document.getElementById('topPagination')) {
  968. if (bottomPagination.childElementCount > 0) {
  969. let topPagination = bottomPagination.cloneNode(true);
  970. topPagination.id = 'topPagination';
  971. let topPaginationContainer = document.createElement('div');
  972. topPaginationContainer.id = 'topPaginationContainer';
  973. topPaginationContainer.appendChild(topPagination);
  974. let topbar = document.querySelector('.fluid-magic-tools-view');
  975. if (!topbar) topbar = document.querySelector('.album-toolbar-content');
  976. if (!topbar) topbar = document.querySelector('.group-pool-subheader-view');
  977. if (!topbar) topbar = document.querySelector('.title-row');
  978. if (topbar) {
  979. log('topPagination: root found, inserting container');
  980. topbar.appendChild(topPaginationContainer);
  981. }
  982. }
  983. }
  984. }
  985.  
  986. 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}';
  987.  
  988. function albumExtras() { // links to album's map and comments
  989. if (fixr.context.pageType !== 'ALBUM') {
  990. return; // exit if not albumpage
  991. }
  992. if (fixr.context.albumId) {
  993. log('albumsExtra() med album=' + fixr.context.albumId);
  994. } else {
  995. log('Exit albumsExtra(). Mangler albumId');
  996. return;
  997. }
  998. let elist = document.querySelector('div.album-engagement-view');
  999. if (elist && !document.getElementById('albumCommentCount')) {
  1000. // map-link:
  1001. let mapdiv = document.createElement('div');
  1002. mapdiv.className = 'create-book-container';
  1003. mapdiv.title = 'Album on map';
  1004. mapdiv.style.textAlign = 'center';
  1005. let maplink = document.createElement('a');
  1006. maplink.href = '/photos/' + fixr.context.photographerAlias + '/albums/' + fixr.context.albumId + '/map/';
  1007. maplink.style.fontSize = '14px';
  1008. maplink.style.color = '#FFF';
  1009. let mapicon = document.createElement('span');
  1010. mapicon.title = 'Album on map';
  1011. mapicon.className = 'album-map-icon';
  1012. maplink.appendChild(mapicon);
  1013. mapdiv.appendChild(maplink);
  1014. elist.appendChild(mapdiv);
  1015. // comments-link:
  1016. let comurl = '/photos/' + fixr.context.photographerAlias + '/sets/' + fixr.context.albumId + '/comments/'; // /sets/* urls works, /albums/* urls currently doesn't work (yet?)
  1017. // var comurl = '#'; // NEW?!
  1018. let cmdiv = document.createElement('div');
  1019. cmdiv.className = 'create-book-container';
  1020. cmdiv.title = 'Comments';
  1021. cmdiv.style.textAlign = 'center';
  1022. let cmlink = document.createElement('a');
  1023. cmlink.href = comurl;
  1024. cmlink.style.fontSize = '14px';
  1025. cmlink.style.color = '#FFF';
  1026. cmlink.id = 'albumCommentsLink';
  1027. let cmicon = document.createElement('span');
  1028. cmicon.title = 'Album comments';
  1029. cmicon.className = 'album-comments-icon';
  1030. cmicon.id = 'albumCommentCount';
  1031. cmlink.appendChild(cmicon);
  1032. cmdiv.appendChild(cmlink);
  1033. elist.appendChild(cmdiv);
  1034.  
  1035. wsGetPhotosetComments();
  1036. }
  1037. }
  1038.  
  1039. const updateTags_style_avatar = 'a.fixrTag>img {width:1em;height:1em;margin:0;padding:0;position:relative;top:3px}';
  1040. 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;
  1041. const updateTags_style_persist = 'ul.tags-list>li.tag>a.fixrTag,ul.tags-list>li.autotag>a.fixrTag{display:inline;} ' + updateTags_style_avatar;
  1042.  
  1043. function updateTags() {
  1044. if (fixr.context.pageType !== 'PHOTOPAGE') {
  1045. return; // exit if not photopage
  1046. }
  1047. if (fixr.context.photographerAlias === '') {
  1048. fixr.initPhotoId();
  1049. }
  1050. if (fixr.context.photographerId === '') {
  1051. fixr.initPhotographerId();
  1052. }
  1053. if (fixr.context.photographerName === '') {
  1054. fixr.initPhotographerName();
  1055. }
  1056. log('updateTags() med photographerAlias=' + fixr.context.photographerAlias + ', photographerId=' + fixr.context.photographerId + ' og photographerName=' + fixr.context.photographerName);
  1057. if (document.querySelector('ul.tags-list')) {
  1058. let tags = document.querySelectorAll('ul.tags-list>li');
  1059. if (tags && tags.length > 0) {
  1060. log('updateTags() Looping ' + tags.length + ' tags...');
  1061. 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?
  1062. for (let tag of tags) {
  1063. let atag = tag.querySelector('a[title][href*="/photos/tags/"],a[title][href*="?tags="],a[title][href*="?q="]');
  1064. if (atag) {
  1065. let realtag = (atag.href.match(/((\/tags\/)|(\?tags\=)|(\?q\=))([\S]+)$/i))[5];
  1066. if (!(tag.querySelector('a.fixrTag'))) {
  1067. let avatar = document.createElement('img');
  1068. avatar.src = iconHref;
  1069. avatar.alt = '*';
  1070. let taglink = document.createElement('a');
  1071. taglink.className = 'fixrTag';
  1072. taglink.href = '/photos/' + (fixr.context.photographerAlias || fixr.context.photographerId) + '/tags/' + realtag + '/';
  1073. taglink.title = atag.title + ' by ' + fixr.context.photographerName;
  1074. taglink.appendChild(avatar);
  1075. tag.insertBefore(taglink, tag.firstChild);
  1076. }
  1077. } else {
  1078. log('updateTags(): atag not matched.');
  1079. }
  1080. }
  1081. } else {
  1082. log('updateTags(): No tags defined (yet?)');
  1083. }
  1084. } else {
  1085. log('updateTags(): taglist container not found');
  1086. }
  1087. }
  1088.  
  1089. function updateTagsDelayed() {
  1090. log('updateTagsDelayed() running... with pageType=' + fixr.context.pageType);
  1091. if (fixr.context.pageType === 'PHOTOPAGE') {
  1092. setTimeout(updateTags, 2500);
  1093. setTimeout(updateTags, 4500); // Twice. Those tags are sometimes a bit slow emerging
  1094. setTimeout(updateTags, 8500); // Triple. Those tags are sometimes very slow emerging
  1095. }
  1096. }
  1097.  
  1098. 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;}';
  1099.  
  1100. function photoDates() {
  1101. let elem = document.querySelector('div.view.sub-photo-date-view');
  1102. if (elem && !elem.classList.contains('has-date-info')) {
  1103. elem.classList.add('has-date-info');
  1104. elem.insertAdjacentElement('beforeend', createRichElement('div', {class: 'date-info'}, 'Date info!'));
  1105. wsGetPhotoInfo(); // get dates
  1106. }
  1107. }
  1108.  
  1109. function photoDatesDelayed() {
  1110. log('photoDates() running... with pageType=' + fixr.context.pageType);
  1111. if (fixr.context.pageType === 'PHOTOPAGE') {
  1112. setTimeout(photoDates, 2000);
  1113. setTimeout(photoDates, 4000); // Twice.
  1114. }
  1115. }
  1116.  
  1117. function shootingSpaceballs() {
  1118. // Enable image context-menu on "View sizes" page by removing overlaying div.
  1119. // This is *not* meant as a tool for unauthorized copying and distribution of other peoples photos.
  1120. // Please respect image ownership and copyrights!
  1121. if (fixr.context.pageType === 'SIZES') {
  1122. function trashing() {
  1123. let trash = document.querySelector('div.spaceball');
  1124. while (trash && trash.parentNode) {
  1125. trash.parentNode.removeChild(trash);
  1126. trash = document.querySelector('div.spaceball');
  1127. }
  1128. }
  1129.  
  1130. let asp = document.querySelector('#allsizes-photo');
  1131. if (asp) {
  1132. document.body.addEventListener('click', trashing, false);
  1133. asp.click();
  1134. }
  1135. // document.body.style.backgroundColor = "#cfc";
  1136. }
  1137. }
  1138.  
  1139. 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;}';
  1140.  
  1141. function orderWarning() {
  1142. if (fixr.context.pageType === 'PHOTOSTREAM') {
  1143. let e = document.querySelector('.dropdown-link.filter-sort');
  1144. if (e) {
  1145. 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())) {
  1146. e.classList.add('warning');
  1147. } else {
  1148. e.classList.remove('warning');
  1149. }
  1150. }
  1151. }
  1152. }
  1153.  
  1154. 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}';
  1155.  
  1156. function newsfeedLinks() {
  1157. let elem = document.getElementById('feedlinks');
  1158. if (elem) {
  1159. elem.textContent = '';
  1160. }
  1161. setTimeout(updateNewsfeedLinks, 500); // give Flickr time to update link tags in head
  1162. }
  1163.  
  1164. function updateNewsfeedLinks() {
  1165. let 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"]');
  1166. log('Number of feed links found: ' + feedlinks.length);
  1167. let 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');
  1168. if (dgnc) {
  1169. if (!document.getElementById('feedlinks')) {
  1170. dgnc.style.position = "relative";
  1171. dgnc.insertAdjacentElement('afterbegin', createRichElement('div', {id: 'feedlinks'}));
  1172. }
  1173. let elem = document.getElementById('feedlinks');
  1174. if (elem) {
  1175. let feedicons = new DocumentFragment();
  1176. for (const link of feedlinks) {
  1177. let feedlink = document.createElement('a');
  1178. feedlink.href = link.href;
  1179. let feedsymbol = document.createElement('img');
  1180. feedsymbol.src = 'https://c1.staticflickr.com/5/4869/32220441998_601de47e20_o.png';
  1181. feedsymbol.alt = 'Feedlink';
  1182. feedsymbol.style.width = '16px';
  1183. feedsymbol.style.height = '16px';
  1184. feedsymbol.title = link.title;
  1185. feedlink.appendChild(feedsymbol);
  1186. feedicons.appendChild(feedlink);
  1187. }
  1188. elem.textContent = ''; // remove existing content
  1189. elem.appendChild(feedicons);
  1190. }
  1191. }
  1192. }
  1193.  
  1194. function initSlideshowSpeedHook() {
  1195. let timeoutScript = document.createElement('script');
  1196. timeoutScript.src = browser.runtime.getURL('inject/timeout.js');
  1197. timeoutScript.onload = function () {
  1198. this.remove();
  1199. };
  1200. (document.head || document.documentElement).appendChild(timeoutScript);
  1201. }
  1202.  
  1203. let slideshowSpeed = '5'; // Flickr default (seconds)
  1204. function initSlideshowSpeed() {
  1205. if (document.body) {
  1206. document.body.setAttribute('data-slideshowspeed', slideshowSpeed);
  1207. }
  1208. }
  1209.  
  1210. let _wsGetPhotoInfoLock = 0;
  1211.  
  1212. function wsGetPhotoInfo() { // Call Flickr REST API to get photo info
  1213. let diff = Date.now() - _wsGetPhotoInfoLock;
  1214. if ((_wsGetPhotoInfoLock > 0) && (diff < 50)) {
  1215. log('Skipping wsGetPhotoInfo() because already running?: ' + diff);
  1216. // *** maybe add a check to see if we are still on same photo?!
  1217. return;
  1218. }
  1219. _wsGetPhotoInfoLock = Date.now();
  1220.  
  1221. function handleResponse(response) {
  1222. if (response.ok) {
  1223. if (response.headers.get('content-type') && response.headers.get('content-type').includes('application/json')) {
  1224. return response.json()
  1225. }
  1226. throw new Error('Response was not in expected json format.');
  1227. }
  1228. throw new Error('Network response was not ok.');
  1229. }
  1230.  
  1231. function handleResult(obj) {
  1232. let elem = document.querySelector('.date-info');
  1233. if (obj.stat === "ok") {
  1234. log("flickr.photos.getInfo returned ok");
  1235. if (obj.photo?.id) {
  1236. let uploadDate = new Date(0);
  1237. let debugstr = '';
  1238. if (obj.photo.dateuploaded) {
  1239. uploadDate = new Date(obj.photo.dateuploaded * 1000);
  1240. debugstr = 'UploadDate: ' + uploadDate.toString();
  1241. }
  1242. let dateDetail = createRichElement('p', {'x-ms-format-detection': 'none'});
  1243. let takenLabel = createRichElement('label', {}, 'Taken:');
  1244. let uploadedLabel = createRichElement('label', {}, 'Uploaded:');
  1245. let brElem = document.createElement('br');
  1246. if (obj.photo.dates) {
  1247. if (obj.photo.dateuploaded !== obj.photo.dates.posted) {
  1248. log('Unexpected Date difference!');
  1249. }
  1250. if (obj.photo.dates.posted) {
  1251. if (obj.photo.dates.posted < obj.photo.dateuploaded) {
  1252. uploadDate = new Date(obj.photo.dates.posted * 1000); // GMT/UTC
  1253. }
  1254. debugstr += '<br />PostDate: ' + uploadDate.toString();
  1255. }
  1256. if (obj.photo.dates.taken && obj.photo.dates.takenunknown.toString() === '0') {
  1257. debugstr += '<br />TakenDate: ' + obj.photo.dates.taken + ' (granularity=' + obj.photo.dates.takengranularity + ')';
  1258. let takenDateTmp = obj.photo.dates.taken.replace(/[^\d-:\s]/g, ''); // "2018-03-30 00:35:44" (Remove any unexpected characters)
  1259. let takenDate = new Date(Date.parse(takenDateTmp.replace(' ', 'T')));
  1260. let dayStart = new Date(Date.parse(takenDateTmp.substring(0, 10) + 'T00:00:00'));
  1261. let dayEnd = new Date(Date.parse(takenDateTmp.substring(0, 10) + 'T23:59:59'));
  1262. let takenTimeIndex = takenDate.toString().search(/\d{2}[:\.]\d{2}[:\.]\d{2}/);
  1263. if (obj.photo.dates.takengranularity.toString() === '0') { // 0 Y-m-d H:i:s - full datetime
  1264. 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));
  1265. dateDetail.append(takenLabel, ' ', linkElem, takenDate.toString().substring(takenTimeIndex - 1, takenTimeIndex + 8) + ' "Camera Time"', brElem);
  1266. } else if (obj.photo.dates.takengranularity.toString() === '4') { // 4 Y-m
  1267. dateDetail.append(takenLabel, ' ' + takenDateTmp.substring(0, 7), brElem);
  1268. } else if (obj.photo.dates.takengranularity.toString() === '6') { // 6 Y
  1269. dateDetail.append(takenLabel, ' ' + takenDateTmp.substring(0, 4), brElem);
  1270. } else if (obj.photo.dates.takengranularity.toString() === '8') { // 8 Circa...
  1271. dateDetail.append(takenLabel, ' Circa ' + takenDateTmp.substring(0, 4), brElem);
  1272. } else {
  1273. log('Unexpected value for photo.dates.takengranularity: ' + obj.photo.dates.takengranularity);
  1274. }
  1275. }
  1276. if (obj.photo.dates.lastupdate) { // photo has been updated/replaced
  1277. debugstr += '<br />UpdateDate: ' + (new Date(obj.photo.dates.lastupdate * 1000)).toString();
  1278. }
  1279. }
  1280. if (elem) {
  1281. let uploadDateStr = uploadDate.toString();
  1282. let n = uploadDateStr.indexOf('(');
  1283. if (n > 0) {
  1284. dateDetail.append(uploadedLabel, ' ' + uploadDateStr.substring(0, n));
  1285. } else {
  1286. dateDetail.append(uploadedLabel, ' ' + uploadDateStr);
  1287. }
  1288. elem.textContent = '';
  1289. elem.append(dateDetail);
  1290. let withTitle = elem.parentElement.querySelector('span[title]');
  1291. if (withTitle) {
  1292. withTitle.removeAttribute('title');
  1293. }
  1294. }
  1295. }
  1296. } else {
  1297. if (elem) {
  1298. elem.textContent = 'Cannot fetch detailed date details on private photos';
  1299. }
  1300. log('flickr.photos.getInfo returned an ERROR: obj.stat=' + obj.stat + ', obj.code=' + obj.code + ', obj.message=' + obj.message);
  1301. }
  1302. _wsGetPhotoInfoLock = 0;
  1303. }
  1304.  
  1305. function handleError(error) {
  1306. console.log('There has been a problem with your fetch operation: ', error.message);
  1307. log('There has been a problem with your fetch operation: ' + error);
  1308. let elem = document.querySelector('.date-info');
  1309. if (elem) {
  1310. elem.textContent = 'There was an error fetching detailed date details...';
  1311. }
  1312. }
  1313.  
  1314. if (fixr.isWebExtension()) {
  1315. // Call fetch() from background-script in WebExtensions, because changes in Chrome/Chromium https://www.chromium.org/Home/chromium-security/extension-content-script-fetches
  1316. browser.runtime.sendMessage({
  1317. msgtype: "flickrservice",
  1318. method: "flickr.photos.getInfo",
  1319. fkey: fkey,
  1320. options: {photo_id: fixr.context.photoId}
  1321. }).then(handleResult).catch(handleError);
  1322. } else { // Userscript (So far it still works, also on Chrome/Tampermonkey...)
  1323. 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);
  1324. }
  1325. }
  1326.  
  1327. function stereotest() {
  1328. let self = "flickrfixruserscript";
  1329. let other = "flickrfixrwebextension";
  1330. if (fixr.isWebExtension()) {
  1331. self = "flickrfixrwebextension";
  1332. other = "flickrfixruserscript";
  1333. }
  1334. document.body.classList.add(self);
  1335. if (document.body.classList.contains(other)) {
  1336. 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!");
  1337. }
  1338. }
  1339.  
  1340. function runEarly() {
  1341. //localStorage.setItem('filterFeedEvents', 'people'); // Try to make People feed default.
  1342. }
  1343.  
  1344. function weekNo(dt) {
  1345. let date = dt || new Date();
  1346. date.setHours(0, 0, 0, 0);
  1347. // Thursday in current week decides the year.
  1348. date.setDate(date.getDate() + 3 - (date.getDay() + 6) % 7);
  1349. // January 4 is always in week 1.
  1350. let week1 = new Date(date.getFullYear(), 0, 4);
  1351. // Adjust to Thursday in week 1 and count number of weeks from date to week1.
  1352. return 1 + Math.round(((date.getTime() - week1.getTime()) / 86400000
  1353. - 3 + (week1.getDay() + 6) % 7) / 7);
  1354. }
  1355. function userscriptWarning() {
  1356. if (!document.body.classList.contains("flickrfixrwebextension")) { // Skip warning if both versions are installed (There will be another warning from stereotest()).
  1357. let info = (GM_info ? GM_info : (typeof GM === 'object' && GM !== null && typeof GM.info === 'object' ? GM.info : null));
  1358. if (info) {
  1359. let prevWeekNo = localStorage.getItem("fixr_userscript_warning_weekno");
  1360. let prevVersion = localStorage.getItem("fixr_userscript_warning_version");
  1361. if (!prevVersion || prevVersion !== info.script.version || !prevWeekNo || prevWeekNo != weekNo()) {
  1362. localStorage.setItem("fixr_userscript_warning_weekno", weekNo());
  1363. localStorage.setItem("fixr_userscript_warning_version", info.script.version);
  1364. alert('\nYou are running the userscript-version of Flickr Fixr via ' + info.scriptHandler + '. \n\nUserscript version of Flickr Fixr is not supported anymore, and probably wont be upgraded even if a Flickr update breaks this userscript. \n\nFor continued support and updates, make the switch to the Flickr Fixr browser-extension. You can find Flickr Fixr browser extension in the Add-on webstores for Chrome, Firefox and Edge.\n');
  1365. }
  1366. }
  1367. }
  1368. }
  1369.  
  1370. 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
  1371.  
  1372. function handlerInitFixr(options) { // Webextension init
  1373. let runNow = [];
  1374. let onPageHandlers = [];
  1375. let onResizeHandlers = [];
  1376. let onFocusHandlers = [];
  1377. let onStandaloneHandlers = [];
  1378.  
  1379. fixr.style.add(shared_style);
  1380. onPageHandlers.push(stereotest);
  1381. if (options.topMenuItems) {
  1382. fixr.style.add(topMenuItems_style);
  1383. onPageHandlers.push(topMenuItems);
  1384. onStandaloneHandlers.push(topMenuItems);
  1385. }
  1386. if (options.ctrlClicking) {
  1387. onPageHandlers.push(ctrlClicking);
  1388. }
  1389. if (options.albumExtras) {
  1390. fixr.style.add(albumExtras_style);
  1391. onPageHandlers.push(albumExtras);
  1392. }
  1393. if (options.topPagination) {
  1394. fixr.style.add(topPagination_style);
  1395. onPageHandlers.push(topPagination);
  1396. }
  1397. if (options.shootingSpaceballs) {
  1398. onPageHandlers.push(shootingSpaceballs);
  1399. }
  1400. if (options.orderWarning) {
  1401. fixr.style.add(orderwarning_style);
  1402. onPageHandlers.push(orderWarning);
  1403. }
  1404. if (options.newsfeedLinks) {
  1405. fixr.style.add(newsfeedLinks_style);
  1406. onPageHandlers.push(newsfeedLinks);
  1407. onStandaloneHandlers.push(newsfeedLinks);
  1408. }
  1409. if (options.photoDates) {
  1410. fixr.style.add(photoDates_style);
  1411. onPageHandlers.push(photoDatesDelayed);
  1412. }
  1413. if (options.ctrlClicking) {
  1414. onPageHandlers.push(ctrlClickingDelayed);
  1415. }
  1416. if (options.exploreCalendar) {
  1417. fixr.style.add(exploreCalendar_style);
  1418. onPageHandlers.push(exploreCalendarDelayed);
  1419. }
  1420. if (options.albumTeaser) {
  1421. fixr.style.add(albumTeaser_style);
  1422. onPageHandlers.push(albumTeaserDelayed);
  1423. }
  1424. if (options.insertGMapLink) {
  1425. onPageHandlers.push(insertGMapLinkDelayed);
  1426. onStandaloneHandlers.push(mapInitializer);
  1427. }
  1428. if (options.updateTags) {
  1429. if (options.updateTags_tagmode === 'updateTags_persist') {
  1430. fixr.style.add(updateTags_style_persist);
  1431. } else {
  1432. fixr.style.add(updateTags_style_hover);
  1433. }
  1434. onPageHandlers.push(updateTagsDelayed);
  1435. }
  1436. if (options.searchresultsCollapsibleSidebar) {
  1437. fixr.style.add(collapsibleSidebar_style);
  1438. onPageHandlers.push(collapsibleSidebar);
  1439. }
  1440. if (options.slideshowSpeedControl) {
  1441. slideshowSpeed = options.slideshowSpeedControl_value;
  1442. runNow.push(initSlideshowSpeedHook);
  1443. onPageHandlers.push(initSlideshowSpeed);
  1444. }
  1445. fixr.init(runNow, onPageHandlers, onResizeHandlers, onFocusHandlers, onStandaloneHandlers); // WebExtension
  1446. }
  1447.  
  1448. if (window.location.href.includes('flickr.com\/services\/api\/explore\/')) {
  1449. // 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...
  1450. } else {
  1451. if (fixr.isWebExtension()) {
  1452.  
  1453. // WEBEXTENSION SETUP with options
  1454.  
  1455. log('WebExtension - init with options...');
  1456. withOptionsDo(handlerInitFixr); // Load selected features and run fixr.init with them...
  1457.  
  1458. } else {
  1459.  
  1460. // USERSCRIPT SETUP
  1461.  
  1462. log('Userscript - fixr.init...');
  1463. fixr.style.add(shared_style);
  1464. fixr.style.add(albumExtras_style);
  1465. fixr.style.add(topPagination_style);
  1466. fixr.style.add(orderwarning_style);
  1467. fixr.style.add(topMenuItems_style);
  1468. fixr.style.add(photoDates_style);
  1469. fixr.style.add(newsfeedLinks_style);
  1470. fixr.style.add(exploreCalendar_style);
  1471. fixr.style.add(albumTeaser_style);
  1472. fixr.style.add(updateTags_style_hover);
  1473. // FIXR fixr.init([runNow], [onPageHandlers], [onResizeHandlers], [onFocusHandlers], [onStandaloneHandlers])
  1474. fixr.init([/* runEarly */], [stereotest, topMenuItems, ctrlClicking, albumExtras, topPagination, shootingSpaceballs, orderWarning, newsfeedLinks, photoDatesDelayed, ctrlClickingDelayed, exploreCalendarDelayed, albumTeaserDelayed, insertGMapLinkDelayed, updateTagsDelayed, userscriptWarning], [], [], [topMenuItems, newsfeedLinks, mapInitializer]); // Userscript
  1475.  
  1476. }
  1477. }