Stig's Flickr Fixr

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?...

当前为 2019-05-16 提交的版本,查看 最新版本

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