MassiveFap (2023 Fix)

A complete ImageFap.com gallery conversion script featuring customization options and multiple viewing modes. Original author is Ryan Thaut, but it looks like abandoned.

  1. // ==UserScript==
  2. // @name MassiveFap (2023 Fix)
  3. // @author Ryan Thaut + patches from Elandoris and Arti
  4. // @description A complete ImageFap.com gallery conversion script featuring customization options and multiple viewing modes. Original author is Ryan Thaut, but it looks like abandoned.
  5. // @include https://*imagefap.com/*
  6. // @version 1.7.8
  7. // @grant GM_getValue
  8. // @grant GM_setValue
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_addStyle
  11. // @namespace https://greasyfork.org/users/705794
  12. // ==/UserScript==
  13.  
  14.  
  15. /* ===== Integration =====
  16. This is where the initial/basic eventListeners are added.
  17. */
  18. window.addEventListener('load', init, false);
  19. document.addEventListener('DOMContentLoaded', init, false);
  20.  
  21.  
  22. /* ===== Global Variables =====
  23. Changing these is a terrible idea; they are NOT for configuration
  24. */
  25. var fullscreen = false;
  26. var loaded = false;
  27. var images = [];
  28. var imagesMap = new Map();
  29. var head, body, title, addFavLink;
  30. var author = {name: '', url: ''};
  31. var activeImage = (parseInt(getHashParam('image'), 10) - 1) || 0;
  32. var totalImages = 0;
  33. var hotkeys = [
  34. {
  35. action: displayHelp,
  36. codes: [191],
  37. keys: '?',
  38. label: 'Display Help',
  39. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: true}
  40. },
  41. {
  42. action: displayAbout,
  43. codes: [90],
  44. keys: 'z',
  45. label: 'Display About',
  46. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  47. },
  48. {
  49. action: toggleFullScreen,
  50. codes: [70],
  51. keys: 'f',
  52. label: 'Toggle Full Screen',
  53. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  54. },
  55. {
  56. action: switchGalleryMode,
  57. codes: [71],
  58. keys: 'g',
  59. label: 'Switch Gallery Mode',
  60. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  61. },
  62. {
  63. action: changeSettings,
  64. codes: [83],
  65. keys: 's',
  66. label: 'Change Settings',
  67. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  68. },
  69. {
  70. action: toggleThumbs,
  71. codes: [84],
  72. keys: 't',
  73. label: 'Change Thumbnails',
  74. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  75. },
  76. {
  77. action: hideDialog,
  78. codes: [27],
  79. keys: 'esc',
  80. label: 'Hide Dialog Window',
  81. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  82. },
  83. {
  84. action: prevImage,
  85. codes: [37, 38, 65, 87],
  86. keys: '← ↑ a w',
  87. label: 'Previous Image',
  88. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  89. },
  90. {
  91. action: nextImage,
  92. codes: [39, 40, 68, 83],
  93. keys: '→ ↓ d s',
  94. label: 'Next Image',
  95. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  96. },
  97. {
  98. action: toggleAutoplay,
  99. codes: [32],
  100. keys: '[space]',
  101. label: 'Start/Stop Autoplay',
  102. modif: {altKey: false, ctrlKey: false, metaKey: false, shiftKey: false}
  103. }
  104. ];
  105. var settings = {
  106. 'autoplay': {
  107. name: 'autoplayDelay',
  108. label: 'Auto Play Delay',
  109. hint: 'in seconds',
  110. type: 'integer',
  111. size: 3,
  112. min: 0,
  113. max: null,
  114. def: 2
  115. },
  116. 'inifiteScrolling': {
  117. name: 'inifiteScrolling',
  118. label: 'Infinite Scrolling',
  119. type: 'boolean',
  120. def: true
  121. },
  122. 'mode': {
  123. name: 'galleryMode',
  124. label: 'Gallery Mode',
  125. type: 'select',
  126. opts: ['scrolling', 'slideshow'],
  127. def: 'slideshow'
  128. },
  129. 'pagination': {
  130. name: 'imageLimit',
  131. label: 'Pagination Limit',
  132. hint: 'use <b>0</b> to disable',
  133. type: 'integer',
  134. size: 3,
  135. min: 0,
  136. max: null,
  137. def: 0
  138. },
  139. 'preloading': {
  140. name: 'preloadingEnabled',
  141. label: 'Preloading reach',
  142. hint: 'use <b>0</b> to disable preloading',
  143. type: 'integer',
  144. size: 3,
  145. def: 10
  146. },
  147. 'minLoadingTime': {
  148. name: 'autoplayDelay',
  149. label: 'Min loading time',
  150. hint: 'in ms',
  151. type: 'integer',
  152. size: 4,
  153. min: 0,
  154. max: null,
  155. def: 4
  156. },
  157. 'theme': {
  158. name: 'theme',
  159. label: 'Gallery Theme',
  160. type: 'select',
  161. opts: ['default', 'classic', 'green', 'blue'],
  162. def: 'default'
  163. },
  164. 'thumbnails': {
  165. name: 'showThumbs',
  166. label: 'Show Thumbnails',
  167. type: 'boolean',
  168. def: true
  169. },
  170. 'thumbnailsSize': {
  171. name: 'thumbnailsSize',
  172. label: 'Thumbnails Size',
  173. type: 'select',
  174. opts: ['small', 'medium', 'large'],
  175. def: 'medium'
  176. }
  177. };
  178.  
  179. // objects for features that need multiple settings and calculated properties
  180. var autoplay = {
  181. active: false,
  182. count: parseInt(GM_getValue(settings.autoplay.name, settings.autoplay.def), 10),
  183. delay: parseInt(GM_getValue(settings.autoplay.name, settings.autoplay.def), 10),
  184. paused: false,
  185. timer: undefined
  186. };
  187. var pagination = {
  188. append: GM_getValue(settings.inifiteScrolling.name, settings.inifiteScrolling.def),
  189. active: false,
  190. limit: parseInt(GM_getValue(settings.pagination.name, settings.pagination.def), 10),
  191. page: parseInt(getHashParam('page'), 10) || 1
  192. };
  193. var preloading = {
  194. active: GM_getValue(settings.preloading.name, settings.preloading.def) > 0,
  195. pos: activeImage,
  196. reach: parseInt(GM_getValue(settings.preloading.name, settings.preloading.def), 10),
  197. };
  198.  
  199.  
  200. /* ===== Core Functions =====
  201. Where the magic happens...
  202. */
  203.  
  204. /**
  205. * Crawls the normal gallery page and finds all thumbnail images
  206. * @return Array locations of all thumbnail images
  207. */
  208. function findImages() {
  209. var imgs = document.getElementById('gallery').getElementsByTagName('img');
  210. var thumbRegex = /^(.*\/images\/)(thumb)(\/.*\/)(\d+)(\..*)$/i;
  211.  
  212. var count = 0;
  213. var ret = [];
  214. var match;
  215. var image;
  216.  
  217. for (var i = 0; i < imgs.length; i++) {
  218. if (thumbRegex.test(imgs[i].src)) {
  219. match = thumbRegex.exec(imgs[i].src);
  220. image = {
  221. id: match[4],
  222. pos: count++,
  223. thumb: match[0],
  224.  
  225. loadedUrl: null,
  226. singlePageUrl: imgs[i].parentNode.href,
  227. fullUrlRequest: null,
  228. callback: () => {
  229. },
  230. full(callback) {
  231. if (this.loadedUrl) {
  232. callback(this.loadedUrl);
  233. } else {
  234. let old = this.callback;
  235. this.callback = url => {
  236. old(url);
  237. callback(url);
  238. }
  239. fetchFullImageUrl(this);
  240. }
  241. }
  242. };
  243. ret.push(image);
  244. imagesMap.set(image.id, image);
  245. }
  246. }
  247.  
  248. totalImages = ret.length;
  249. return ret;
  250. }
  251.  
  252. function fetchFullImageUrl(image) {
  253. var fullImgRegex = /^(.*\/images\/)(full)(\/.*\/)(\d+)(\..*)$/i;
  254. var xmlHttp = new XMLHttpRequest();
  255. xmlHttp.onreadystatechange = function () {
  256. if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
  257. var el = document.createElement( 'html' );
  258. el.innerHTML = xmlHttp.responseText;
  259. console.log('-1',el);
  260. let srcs = el.querySelectorAll('#_navi_cavi > ul > li > a');
  261. console.log('1',srcs[0]);
  262. console.log('2',el.querySelector('#navigation'));
  263. let requestedReference
  264. for (let i in srcs) {
  265. let href = srcs[i].href;
  266. let match = fullImgRegex.exec(href);
  267. if (match && match.length >= 2) {
  268. let id = match[4];
  269. if (id === image.id) {
  270. requestedReference = href;
  271. }
  272. let otherImage = imagesMap.get(id);
  273. if (otherImage && !otherImage.loadedUrl) {
  274. otherImage.loadedUrl = href;
  275. if (otherImage.callback) {
  276. otherImage.callback(otherImage.loadedUrl);
  277. }
  278. }
  279. }
  280. }
  281. if (requestedReference) {
  282. image.loadedUrl = requestedReference;
  283. if (image.callback) {
  284. image.callback(image.loadedUrl)
  285. }
  286. }
  287. }
  288. };
  289. xmlHttp.open("GET", image.singlePageUrl, true); // true for asynchronous
  290. xmlHttp.send(null);
  291. return xmlHttp;
  292. }
  293.  
  294. function smartUrlLoading() {
  295.  
  296. }
  297.  
  298. /**
  299. * Sets the global variables needed for pagination in other functions
  300. */
  301. function initPagination() {
  302. initSetting('pagination');
  303.  
  304. // reset pagination object properties to default
  305. pagination = {
  306. append: settings.inifiteScrolling.value,
  307. active: (settings.mode.value === 'slideshow'),
  308. limit: parseInt(settings.pagination.value, 10),
  309. page: parseInt(getHashParam('page'), 10) || 1
  310. };
  311.  
  312. if ((pagination.limit <= 0) || (settings.mode.value === 'slideshow')) {
  313. // in slideshow mode pagination is handled as if it is disabled
  314. pagination.active = false;
  315. pagination.limit = totalImages;
  316. pagination.page = 1;
  317. } else {
  318. pagination.active = true;
  319. if (!pagination.page || (pagination.page < 1))
  320. pagination.page = 1;
  321. }
  322.  
  323. // ensure the user is on the correct page
  324. var page = findImagePage();
  325. if (page !== pagination.page) {
  326. setHashParam('page', page);
  327. pagination.page = page;
  328. }
  329. }
  330.  
  331. /**
  332. * Finds the page number that the active image should be on
  333. * @param Int (Optional) The number of the image to find (default: value of activeImage internal variable)
  334. * @return Int The page number containing the active image
  335. */
  336. function findImagePage(pos) {
  337. if ((typeof activeImage === 'undefined') || !activeImage || (activeImage <= 0))
  338. return 1;
  339.  
  340. return parseInt(((activeImage / pagination.limit) + 1), 10);
  341. }
  342.  
  343. /**
  344. * Returns the images that will be used on the current page
  345. * @param Array Objects representing all images from the original gallery
  346. * @return Array Multi-dimensional array of objects representing all images on each page
  347. */
  348. function paginateImages(imgs) {
  349. // if images have been paginated previously, they must first be un-paginated
  350. if (typeof imgs[0] === 'object' && typeof imgs[0][0] === 'object')
  351. imgs = resetImages(imgs);
  352.  
  353. var page = 0;
  354. var ret = [];
  355. for (var i = 0; i < imgs.length; i++) {
  356. if (typeof ret[page] === 'undefined')
  357. ret[page] = [];
  358. ret[page].push(imgs[i]);
  359.  
  360. if (((i + 1) % pagination.limit) === 0)
  361. page++;
  362. }
  363. return ret;
  364. }
  365.  
  366. /** Flattens a paginated multi-dimensional array of images
  367. * @param Array Multi-dimensional array of objects representing all images on multiple page
  368. * @return Array Multi-dimensional array of objects representing all images on one page
  369. */
  370. function resetImages(imgs) {
  371. var ret = [];
  372. for (var i = 0; i < imgs.length; i++) {
  373. for (var j = 0; j < imgs[i].length; j++) {
  374. ret.push(imgs[i][j]);
  375. }
  376. }
  377. // if the supplied array was only 1-dimensional, then the new array will be empty
  378. if (ret.length === 0)
  379. ret = imgs;
  380. return ret;
  381. }
  382.  
  383. /**
  384. * Loads the next "page" of images in Infinite Scrolling mode
  385. * Updates the position text and the pagination links
  386. */
  387. function loadNextPage() {
  388. if (pagination.page < images.length) {
  389. pagination.page++;
  390. showNotification('Loading images from page ' + pagination.page);
  391.  
  392. updatePosition(undefined, (pagination.limit * pagination.page), undefined, undefined);
  393. updatePagination(pagination.page);
  394.  
  395. populateScrollingGallery(images[(pagination.page - 1)], false);
  396. populateThumbnails(images[(pagination.page - 1)], false);
  397.  
  398. setHashParam('page', pagination.page);
  399. } else {
  400. var loader = document.getElementById('loader');
  401. if (loader)
  402. loader.parentNode.removeChild(loader);
  403. }
  404. }
  405.  
  406. /**
  407. * Generates HTML for the help dialog
  408. * @return String HTML to be placed in the dialog
  409. */
  410. function getAbout() {
  411. var about = '';
  412. about += '<p>' + GM_info.script.name + ' v' + GM_info.script.version + '. Automatic script updates are ' + ((GM_info.scriptWillUpdate) ? 'enabled' : 'disabled') + '. </p>';
  413. about += '<p>' + GM_info.script.description + '</p>';
  414.  
  415. return about;
  416. }
  417.  
  418. /**
  419. * Generates HTML for the help dialog
  420. * @return String HTML to be placed in the dialog
  421. */
  422. function getHelp() {
  423. var help = '';
  424. help += '<table>';
  425. help += '<tr><th></th><th>Hotkeys</th></tr>';
  426. for (var h in hotkeys)
  427. help += '<tr><td class="key"><b>' + hotkeys[h].keys + '</b></td><td class="command">' + hotkeys[h].label + '</td></tr>';
  428.  
  429. help += '</table>';
  430.  
  431. return help;
  432. }
  433.  
  434. /**
  435. * Generates the DOM objects needed for the thumbnail images
  436. * @param Array The objects of all images to be displayed
  437. * @param Bool (Optional) If the existing thumbnails should be removed (default: false)
  438. */
  439. function populateThumbnails(imgs, reset) {
  440. reset = (typeof reset === "undefined") ? false : reset;
  441.  
  442. var thumbs = document.getElementById('thumbnails');
  443. if (!thumbs)
  444. return false;
  445.  
  446. if (reset)
  447. thumbs.innerHTML = '';
  448. thumbs.className = settings.thumbnailsSize.value;
  449.  
  450. var img, link;
  451. for (var i = 0; i < imgs.length; i++) {
  452. img = document.createElement('img');
  453. img.src = imgs[i].thumb;
  454.  
  455. link = document.createElement('a');
  456. link.addEventListener('click', clickThumbnail);
  457. link.className = 'thumbnail';
  458. link.id = 'thumb_' + imgs[i].pos;
  459. link.rel = imgs[i].pos;
  460. link.appendChild(img);
  461.  
  462. thumbs.appendChild(link);
  463. }
  464. }
  465.  
  466. /**
  467. * Generates the DOM objects needed for the scrolling mode
  468. * @param Array The objects of all images to be displayed
  469. * @param Bool (Optional) If the existing gallery images should be removed (default: false)
  470. */
  471. function populateScrollingGallery(imgs, reset) {
  472. reset = (typeof reset === "undefined") ? false : reset;
  473.  
  474. var gallery = document.getElementById('gallery');
  475. if (!gallery)
  476. return false;
  477.  
  478. if (reset)
  479. gallery.innerHTML = '';
  480.  
  481. var container, spinner;
  482. for (var i = 0; i < imgs.length; i++) {
  483. const img = document.createElement('img');
  484. img.alt = '';
  485. img.rel = imgs[i].pos;
  486. imgs[i].full(url => img.src = url);
  487.  
  488. spinner = document.createElement('span');
  489. spinner.className = 'spinner';
  490.  
  491. const link = document.createElement('a');
  492. link.className = 'image';
  493. imgs[i].full(url => link.href = url);
  494. link.id = 'full_' + (((pagination.page - 1) * pagination.limit) + i);
  495.  
  496. link.appendChild(img);
  497. link.appendChild(spinner);
  498.  
  499. container = document.createElement('p');
  500. container.appendChild(link);
  501.  
  502. gallery.appendChild(container);
  503. }
  504.  
  505. if (pagination.append && (imgs.length < totalImages)) {
  506. var loader = document.getElementById('loader');
  507. if (!loader) {
  508. loader = document.createElement('a');
  509. loader.id = 'loader'
  510. loader.innerHTML = 'Load Next Page of Images';
  511. loader.addEventListener('click', loadNextPage);
  512. }
  513. gallery.appendChild(loader);
  514. }
  515. }
  516.  
  517. /**
  518. * Generates the DOM objects needed for the slideshow mode
  519. * @param Object The object representing the active image
  520. */
  521. function buildSlideshowGallery(active) {
  522. var gallery = document.getElementById('gallery');
  523. if (!gallery)
  524. return false;
  525.  
  526. gallery.innerHTML = '';
  527.  
  528. var prev = document.createElement('a');
  529. prev.addEventListener('click', prevImage);
  530. prev.id = 'prev';
  531. prev.className = 'nav';
  532. prev.innerHTML = '<span class="arrow"><</span>';
  533.  
  534. var next = document.createElement('a');
  535. next.addEventListener('click', nextImage);
  536. next.id = 'next';
  537. next.className = 'nav';
  538. next.innerHTML = '<span class="arrow">></span>';
  539.  
  540. var img = document.createElement('img');
  541. img.alt = '';
  542. img.id = 'slideshowImage';
  543. active.full(url => img.src = url);
  544.  
  545. var spinner = document.createElement('span');
  546. spinner.className = 'spinner';
  547.  
  548. var link = document.createElement('a');
  549. link.className = 'image';
  550. active.full(url => link.href = url);
  551. link.id = 'slideshowLink';
  552.  
  553. link.appendChild(img);
  554. link.appendChild(spinner);
  555.  
  556. gallery.appendChild(link);
  557. gallery.appendChild(prev);
  558. gallery.appendChild(next);
  559.  
  560. // resize the slideshow area
  561. resizeSlideshowGallery();
  562. }
  563.  
  564. /**
  565. * Resizes the DOM object containing the slideshow image to accomodate non-fixed header and footer sizes
  566. */
  567. function resizeSlideshowGallery() {
  568. var content = document.getElementById('content');
  569. if (!content)
  570. return false;
  571.  
  572. // header
  573. var header = document.getElementById('header');
  574. content.style.top = (header) ? header.offsetHeight + 'px' : '';
  575.  
  576. // footer
  577. var footer = document.getElementById('footer');
  578. content.style.bottom = (footer) ? footer.offsetHeight + 'px' : '';
  579. }
  580.  
  581. /**
  582. * Generates the DOM objects for the header of rebuilt pages
  583. */
  584. function buildHeader() {
  585. var header = document.getElementById('header');
  586. if (!header)
  587. return false;
  588.  
  589. header.innerHTML = '';
  590.  
  591. // logo
  592. var logo = document.createElement('a');
  593. logo.href = window.location.protocol + '//' + window.location.host;
  594. logo.id = 'logo';
  595. logo.innerHTML = '<span class="image">Image</span><span class="fap">Fap</span>';
  596. header.appendChild(logo);
  597.  
  598. // heading
  599. var heading = document.createElement('h2');
  600. heading.id = 'heading';
  601. heading.innerHTML = '"<span class="title">' + stripslashes(title) + '</span>"';
  602. if (author.name && author.url)
  603. heading.innerHTML += ' <small>by</small> <a href="' + author.url + '">' + unescape(stripslashes(author.name)) + '</a>';
  604. header.appendChild(heading);
  605.  
  606. // description
  607. if (desc) {
  608. var description = document.createElement('p');
  609. description.id = 'description';
  610. description.innerHTML = stripslashes(desc);
  611. header.appendChild(description);
  612.  
  613. // if the description spans multiple lines, left-align the text
  614. if (description.offsetHeight > 16)
  615. description.style.textAlign = 'left';
  616. }
  617.  
  618. // sub-heading
  619. var subheading = document.createElement('p');
  620. header.appendChild(subheading);
  621. // sub-heading > position
  622. var position = document.createElement('span');
  623. position.className = settings.mode.value;
  624. position.id = 'position';
  625. subheading.appendChild(position);
  626. // sub-heading > spacer
  627. var separator = document.createElement('span');
  628. separator.innerHTML = ' | ';
  629. subheading.appendChild(separator);
  630. // sub-heading > "toggle thumbnails" link
  631. var toggle = document.createElement('a');
  632. toggle.addEventListener('click', toggleThumbs);
  633. toggle.innerHTML = 'Toggle Thumbnails';
  634. subheading.appendChild(toggle);
  635. // sub-heading > spacer
  636. var separator = document.createElement('span');
  637. separator.innerHTML = ' | ';
  638. subheading.appendChild(separator);
  639. // sub-heading > "change settings" link
  640. var openSettings = document.createElement('a');
  641. openSettings.addEventListener('click', changeSettings);
  642. openSettings.innerHTML = 'Change Settings';
  643. subheading.appendChild(openSettings);
  644.  
  645. // pagination
  646. if (settings.mode.value === 'scrolling' && pagination.active)
  647. buildPagination('header');
  648.  
  649. // search form
  650. var form = document.createElement('form');
  651. form.id = 'search';
  652. form.method = 'POST';
  653. form.action = window.location.protocol + '//' + window.location.host + '/gallery.php';
  654. header.appendChild(form);
  655. // search form > text input
  656. var search = document.createElement('input');
  657. search.type = 'text';
  658. search.value = 'Enter search term(s)...';
  659. search.name = 'search';
  660. search.addEventListener('focus', function () {
  661. if (this.value === 'Enter search term(s)...') this.value = '';
  662. });
  663. search.addEventListener('blur', function () {
  664. if (this.value === '') this.value = 'Enter search term(s)...';
  665. });
  666. form.appendChild(search);
  667. // search form > submit button
  668. var submit = document.createElement('input');
  669. submit.type = 'submit';
  670. submit.value = 'Search';
  671. submit.name = 'submit';
  672. form.appendChild(submit);
  673. }
  674.  
  675. /**
  676. * Generates the DOM objects for the footer of rebuilt pages
  677. */
  678. function buildFooter() {
  679. var footer = document.getElementById('footer');
  680. if (!footer)
  681. return false;
  682.  
  683. footer.innerHTML = '';
  684.  
  685. footer.appendChild(addFavLink);
  686.  
  687. if (settings.mode.value === 'scrolling' && pagination.active)
  688. buildPagination('footer');
  689.  
  690. buildInfo();
  691. buildAutoplay();
  692. }
  693.  
  694. /**
  695. * Updates the HTML for the position of the current image(s) within the gallery
  696. * @param Int (Optional) The lower limit image number (default: use existing value from DOM)
  697. * @param Int (Optional) The upper limit image number (default: use existing value from DOM)
  698. * @param Int (Optional) The total image number (default: use existing value from DOM)
  699. * @param Int (Optional) The active image number (default: use existing value from DOM)
  700. */
  701. function updatePosition(lower, upper, total, active) {
  702. var position = document.getElementById('position');
  703. if (!position)
  704. return false;
  705.  
  706. if (position.className !== settings.mode.value) {
  707. position.className = settings.mode.value;
  708. position.innerHTML = '';
  709. }
  710.  
  711. if (settings.mode.value === 'scrolling') {
  712. if (position.innerHTML === undefined || position.innerHTML === '')
  713. position.innerHTML = 'Viewing image(s) <span id="position_lower">' + lower + '</span>-<span id="position_upper">' + upper + '</span> of <span id="position_total">' + total + '</span>';
  714.  
  715. lower = (typeof lower === "undefined") ? parseInt(document.getElementById('position_lower').innerHTML, 10) : lower;
  716. upper = (typeof upper === "undefined") ? parseInt(document.getElementById('position_upper').innerHTML, 10) : upper;
  717. total = (typeof total === "undefined") ? parseInt(document.getElementById('position_total').innerHTML, 10) : total;
  718. if (upper > totalImages)
  719. upper = totalImages;
  720. document.getElementById('position_lower').innerHTML = lower;
  721. document.getElementById('position_upper').innerHTML = upper;
  722. document.getElementById('position_total').innerHTML = total;
  723. } else if (settings.mode.value === 'slideshow') {
  724. if (position.innerHTML === undefined || position.innerHTML === '')
  725. position.innerHTML = 'Viewing image <span id="position_active">' + lower + '</span> of <span id="position_total">' + total + '</span>';
  726.  
  727. active = (typeof active === "undefined") ? parseInt(document.getElementById('position_active').innerHTML, 10) : active;
  728. total = (typeof total === "undefined") ? parseInt(document.getElementById('position_total').innerHTML, 10) : total;
  729. document.getElementById('position_active').innerHTML = active;
  730. document.getElementById('position_total').innerHTML = total;
  731. }
  732. }
  733.  
  734. /**
  735. * Generates the DOM objects for the pagination of rebuilt gallery pages
  736. * @param String The ID of the DOM object of which to insert the pagination controls
  737. */
  738. function buildPagination(location) {
  739. var container = document.getElementById(location);
  740. if (!container)
  741. return false;
  742.  
  743. var pages = Math.ceil(totalImages / pagination.limit);
  744.  
  745. if (pages <= 1)
  746. return false;
  747.  
  748. var wrapper = document.createElement('div');
  749. wrapper.className = 'pagination';
  750.  
  751. // previous page
  752. var prev = document.createElement('a');
  753. prev.innerHTML = '&laquo; Prev';
  754. if (pagination.page > 1) {
  755. prev.addEventListener('click', clickPagination);
  756. prev.rel = (pagination.page - 1);
  757. } else {
  758. prev.className = 'disabled';
  759. }
  760. wrapper.appendChild(prev);
  761.  
  762. // individual pages
  763. var link, lower, upper;
  764. for (var i = 1; i <= pages; i++) {
  765. link = document.createElement('a');
  766. link.rel = i;
  767.  
  768. lower = (pagination.limit * (i - 1) + 1);
  769. upper = (i < pages) ? (pagination.limit * i) : totalImages;
  770. link.innerHTML = (lower === upper) ? lower : lower + '-' + upper;
  771.  
  772. if (i === pagination.page) {
  773. link.className = 'current';
  774. } else {
  775. link.addEventListener('click', clickPagination);
  776. }
  777. wrapper.appendChild(link);
  778. }
  779.  
  780. // next page
  781. var next = document.createElement('a');
  782. next.innerHTML = 'Next &raquo;';
  783. if (pagination.page < pages) {
  784. next.addEventListener('click', clickPagination);
  785. next.rel = (pagination.page + 1);
  786. } else {
  787. next.className = 'disabled';
  788. }
  789. wrapper.appendChild(next);
  790.  
  791. container.appendChild(wrapper);
  792. }
  793.  
  794. /**
  795. * Updates the pagination controls
  796. * @param Int The number of the current page
  797. * @param Bool (Optional) If all pagination controls should be reset (default: false)
  798. */
  799. function updatePagination(page, reset) {
  800. reset = (typeof reset === "undefined") ? false : reset;
  801.  
  802. var containers = document.getElementsByClassName('pagination');
  803. if (containers.length === 0)
  804. return false;
  805.  
  806. var links, rel;
  807. for (var i = 0; i < containers.length; i++) {
  808. links = containers[i].getElementsByTagName('a');
  809.  
  810. // first handle all of the inner links (i.e. the numbered ones)
  811. if (reset) {
  812. // this is for when a page is loaded by itself
  813. // activate the target link and reset all of the other links to default
  814. for (var j = 1; j < (links.length - 1); j++) {
  815. if (j === page) {
  816. links[j].className = 'current';
  817. links[j].removeEventListener('click', clickPagination);
  818. } else {
  819. links[j].className = '';
  820. links[j].addEventListener('click', clickPagination);
  821. }
  822. }
  823. } else {
  824. // this is for when a page is appended
  825. // simply activate the target link
  826. links[page].className = 'current';
  827. links[page].removeEventListener('click', clickPagination);
  828. }
  829.  
  830. // the first link is the "Prev" link, which needs to point to the page BEFORE the current page
  831. var prev = links[0];
  832. if (prev.nextSibling && prev.nextSibling.className === 'current') {
  833. prev.rel = page;
  834. prev.className = 'disabled';
  835. prev.removeEventListener('click', clickPagination);
  836. } else {
  837. prev.rel = (page - 1);
  838. prev.className = trim(links[0].className.replace('disabled', ''));
  839. prev.addEventListener('click', clickPagination);
  840. }
  841.  
  842. // the last link is the "Next" link, which needs to point to the page AFTER the current page
  843. var next = links[(links.length - 1)];
  844. if (next.previousSibling && next.previousSibling.className === 'current') {
  845. next.rel = page;
  846. next.className = 'disabled';
  847. next.removeEventListener('click', clickPagination);
  848. } else {
  849. next.rel = (page + 1);
  850. next.className = trim(next.className.replace('disabled', ''));
  851. next.addEventListener('click', clickPagination);
  852. }
  853. }
  854. }
  855.  
  856. /**
  857. * Generates the DOM objects for the information text at the bottom of the footer
  858. */
  859. function buildInfo() {
  860. var footer = document.getElementById('footer');
  861. if (!footer)
  862. return false;
  863.  
  864. var info = document.createElement('p');
  865. info.id = 'info';
  866.  
  867. // "about" link
  868. var about = document.createElement('a');
  869. about.addEventListener('click', displayAbout);
  870. about.innerHTML = GM_info.script.name + ' v' + GM_info.script.version;
  871. info.appendChild(about);
  872.  
  873. // spacer
  874. var separator = document.createElement('span');
  875. separator.innerHTML = ' | ';
  876. info.appendChild(separator);
  877.  
  878. // "help" link
  879. var help = document.createElement('a');
  880. help.addEventListener('click', displayHelp);
  881. help.innerHTML = 'Help (?)';
  882. info.appendChild(help);
  883.  
  884. // spacer
  885. var separator = document.createElement('span');
  886. separator.innerHTML = ' | ';
  887. info.appendChild(separator);
  888.  
  889. // "settings" link
  890. var openSettings = document.createElement('a');
  891. openSettings.addEventListener('click', changeSettings);
  892. openSettings.innerHTML = 'Settings';
  893. info.appendChild(openSettings);
  894.  
  895. footer.appendChild(info);
  896. }
  897.  
  898. /**
  899. * Generates the DOM objects for the autoplay indicator in the footer
  900. */
  901. function buildAutoplay() {
  902. var footer = document.getElementById('footer');
  903. if (!footer)
  904. return false;
  905.  
  906. var autoplay = document.createElement('div');
  907. autoplay.id = 'autoplay';
  908. if (settings.mode.value === 'scrolling') {
  909. var disabled = document.createElement('span');
  910. disabled.innerHTML = 'Autoplay is only available in slideshow mode';
  911. autoplay.appendChild(disabled);
  912. } else if (settings.mode.value === 'slideshow') {
  913. // autoplay counter
  914. var counter = document.createElement('span');
  915. counter.id = 'counter';
  916. counter.innerHTML = (autoplay.count > 0) ? 'Advancing image in ' + autoplay.count + ' seconds' : 'Autoplay is disabled';
  917. autoplay.appendChild(counter);
  918.  
  919. // spacer
  920. var separator = document.createElement('span');
  921. separator.innerHTML = ' | ';
  922. autoplay.appendChild(separator);
  923.  
  924. // autplay control link
  925. var control = document.createElement('a');
  926. control.addEventListener('click', toggleAutoplay);
  927. control.id = 'control';
  928. control.innerHTML = 'Start Autoplay';
  929. autoplay.appendChild(control);
  930. }
  931.  
  932. footer.appendChild(autoplay);
  933. }
  934.  
  935. /**
  936. * Clears out and re-initializes the DOM with the basic HTML needed for the gallery
  937. */
  938. function initDOM() {
  939. document.removeChild(document.getElementsByTagName('html')[0]);
  940.  
  941. var html = document.createElement('html');
  942. head = document.createElement('head');
  943. body = document.createElement('body');
  944. html.appendChild(head);
  945. html.appendChild(body);
  946. document.appendChild(html);
  947.  
  948. head.innerHTML = '<title>' + stripslashes(title) + '</title>';
  949.  
  950. // build the basic HTML structure to prevent missing elements
  951. body.innerHTML = '<div id="header"></div><div id="content"><div id="gallery"></div></div><div id="footer"></div>'
  952. }
  953.  
  954. /**
  955. * Registers the GreaseMonkey Menu commands
  956. * Must be run after the gallery page is initially built
  957. */
  958. function initMenuCommands() {
  959. GM_registerMenuCommand('[' + GM_info.script.name + '] Help', displayHelp);
  960. GM_registerMenuCommand('[' + GM_info.script.name + '] About', displayAbout);
  961. GM_registerMenuCommand('[' + GM_info.script.name + '] Settings', changeSettings);
  962. }
  963.  
  964. /**
  965. * Rebuilds the actual gallery page piece by piece
  966. */
  967. function rebuildGalleryPage() {
  968. hideDialog();
  969. // re-initialize all settings and feature packages
  970. initSettings();
  971. initAutoplay();
  972. initPagination();
  973. initPreloading();
  974.  
  975. // manually remove existing CSS and apply the chosen theme's CSS
  976. var styles = head.getElementsByTagName('style');
  977. for (var i = 0; i < styles.length; i++) {
  978. head.removeChild(styles[i]);
  979. }
  980. GM_addStyle(getCSS());
  981.  
  982. // paginate the images using current pagination settings
  983. images = paginateImages(images);
  984.  
  985. // header
  986. var header = document.getElementById('header');
  987. if (!header) {
  988. header = document.createElement('div');
  989. header.id = 'header';
  990. body.appendChild(header);
  991. }
  992. buildHeader();
  993.  
  994. // thumbnails
  995. var thumbs = document.getElementById('thumbnails');
  996. if (!thumbs) {
  997. thumbs = document.createElement('div');
  998. thumbs.id = 'thumbnails';
  999. header.appendChild(thumbs);
  1000. }
  1001.  
  1002. // content (gallery wrapper)
  1003. var content = document.getElementById('content');
  1004. if (!content) {
  1005. content = document.createElement('div');
  1006. content.id = 'content';
  1007. body.appendChild(content);
  1008. }
  1009.  
  1010. // main gallery
  1011. var gallery = document.getElementById('gallery');
  1012. if (!gallery) {
  1013. gallery = document.createElement('div');
  1014. gallery.id = 'gallery';
  1015. content.appendChild(gallery);
  1016. }
  1017.  
  1018. // footer
  1019. var footer = document.getElementById('footer');
  1020. if (!footer) {
  1021. footer = document.createElement('div');
  1022. footer.id = 'footer';
  1023. body.appendChild(footer);
  1024. }
  1025. buildFooter();
  1026.  
  1027. // populate the thumbnails
  1028. if (settings.thumbnails.value === false)
  1029. thumbs.style.display = 'none';
  1030. if (settings.mode.value === 'slideshow')
  1031. thumbs.addEventListener('DOMMouseScroll', scrollThumbs, false);
  1032. populateThumbnails(images[(pagination.page - 1)], true);
  1033.  
  1034. // populate the gallery
  1035. if (settings.mode.value === 'scrolling') {
  1036. gallery.style.marginBottom = (footer.offsetHeight + 10) + 'px';
  1037. body.className = 'scrolling';
  1038. var lower = ((pagination.limit * (pagination.page - 1)) + 1);
  1039. var upper = (pagination.limit * pagination.page);
  1040. updatePosition(lower, upper, totalImages, undefined);
  1041. populateScrollingGallery(images[(pagination.page - 1)], true);
  1042. } else if (settings.mode.value === 'slideshow') {
  1043. body.className = 'slideshow';
  1044. updatePosition(undefined, undefined, totalImages, activeImage);
  1045. buildSlideshowGallery(images[(pagination.page - 1)][activeImage]);
  1046. refreshNavigation();
  1047. }
  1048.  
  1049. // remove the page hash for slideshow mode
  1050. if (settings.mode.value === 'slideshow')
  1051. unsetHashParam('page');
  1052.  
  1053. // show the active image, unless it is the first image of a scrolling gallery
  1054. if ((settings.mode.value === 'slideshow') || (activeImage > 0))
  1055. showImage();
  1056.  
  1057. // start preloading images if preloading is enabled
  1058. if ((settings.mode.value === 'slideshow') && preloading.active)
  1059. preloadImage();
  1060. }
  1061.  
  1062. /**
  1063. * main function; executes functionality based on page (via URL)
  1064. * Redirects to the one-page version of galleries (if not already in one-page mode)
  1065. */
  1066. function init() {
  1067. // prevent the initialization function from running multiple times
  1068. if (loaded) {
  1069. return false;
  1070. } else {
  1071. loaded = true;
  1072. }
  1073.  
  1074. var loc = window.location.href;
  1075. if ((loc.indexOf('/gallery/') !== -1) || (loc.indexOf('/pictures/') !== -1)) {
  1076. // this is a gallery page; make sure it is in "One Page" mode and then go to work
  1077. if (loc.indexOf('view') === -1) {
  1078. window.location.href += ((loc.indexOf('?') === -1) ? '?' : '&') + 'view=2';
  1079. } else {
  1080. // populate the global variables from the original gallery page
  1081. title = document.title;
  1082. if (title)
  1083. title = trim(title.replace('Porn pics of ', '').replace(' (Page 1)', ''));
  1084.  
  1085. var links = document.getElementsByTagName('a');
  1086. if (links) {
  1087. var i = 0;
  1088. for (var i = 0; i < links.length; i++) {
  1089. if (links[i].href.indexOf('profile.php?') !== -1) {
  1090. author.name = links[i].href.split('=')[1];
  1091. author.url = links[i].href;
  1092. break;
  1093. }
  1094. }
  1095. }
  1096.  
  1097. desc = document.getElementById('cnt_description');
  1098. if (desc)
  1099. desc = trim(desc.textContent);
  1100.  
  1101. addFavLink = document.getElementById('favorites_container');
  1102.  
  1103. // FINALLY! grab the images, build the gallery, enable the hotkeys, and listen for changes to settings
  1104. images = findImages();
  1105. initDOM();
  1106. rebuildGalleryPage();
  1107. initMenuCommands();
  1108. checkSettings(); // must run after the gallery page is built (otherwise the notification gets wiped out)
  1109. document.addEventListener('keydown', onKeyDown, false);
  1110. window.addEventListener('focus', onWindowFocus, false);
  1111. window.addEventListener('scroll', onWindowScroll, false);
  1112. window.addEventListener('mozfullscreenchange', onFullScreenChange, false);
  1113. window.addEventListener('webkitfullscreenchange', onFullScreenChange, false);
  1114. }
  1115. } else {
  1116. // this might be a page with links to galleries; change all of the links to galleries to "One Page" mode
  1117. var links = document.getElementsByTagName('a');
  1118. if (links) {
  1119. for (var i = 0; i < links.length; i++) {
  1120. if ((links[i].href.indexOf('gallery') !== -1) || (links[i].href.indexOf('pictures') !== -1)) {
  1121. links[i].href += ((links[i].href.indexOf('?') === -1) ? '?' : '&') + 'view=2';
  1122. }
  1123. }
  1124. }
  1125. }
  1126. }
  1127.  
  1128. /**
  1129. * Toggles visibility of the navigational arrows by adding/removing a "disabled" class
  1130. */
  1131. function refreshNavigation() {
  1132. var nav = document.getElementsByClassName('nav');
  1133. for (var i = 0; i < nav.length; i++) {
  1134. nav[i].className = trim(nav[i].className.replace('disabled', ''));
  1135. }
  1136.  
  1137. if (activeImage === 0) {
  1138. var prev = document.getElementById('prev');
  1139. if (prev)
  1140. prev.className += ' disabled';
  1141. } else if (activeImage === (pagination.limit - 1)) {
  1142. var next = document.getElementById('next');
  1143. if (next)
  1144. next.className += ' disabled';
  1145. }
  1146. }
  1147.  
  1148. /**
  1149. * Handles the click event for the pagination links
  1150. * @param Event The click event
  1151. */
  1152. function clickPagination(evt) {
  1153. if (this.rel) {
  1154. var page = parseInt(this.rel, 10);
  1155. activeImage = (((page - 1) * pagination.limit) + 1);
  1156. unsetHashParam('image');
  1157. updatePagination(page, true);
  1158. goToPage(page);
  1159. }
  1160. }
  1161.  
  1162. /**
  1163. * Sets the values needed to change pages and rebuilds the gallery for the specified page
  1164. * @param Int The number of the page to display
  1165. */
  1166. function goToPage(page) {
  1167. pagination.page = page;
  1168.  
  1169. var lower = ((pagination.limit * (pagination.page - 1)) + 1);
  1170. var upper = (pagination.limit * pagination.page);
  1171.  
  1172. setHashParam('page', page);
  1173. setHashParam('image', lower);
  1174.  
  1175. updatePosition(lower, upper, totalImages, undefined);
  1176.  
  1177. populateScrollingGallery(images[(page - 1)], true);
  1178. populateThumbnails(images[(page - 1)], true);
  1179.  
  1180. window.scrollTo(0, 0);
  1181. }
  1182.  
  1183. /**
  1184. * Captures mouse scrolling on the thumbnails and scrolls the images horizontally
  1185. * @param Event mouse wheel scroll event
  1186. */
  1187. function scrollThumbs(evt) {
  1188. if (!evt)
  1189. evt = this;
  1190. evt.preventDefault(); // prevent vertical scrolling on the page
  1191.  
  1192. var delta = (evt.detail) ? evt.detail : 0;
  1193. window.document.getElementById('thumbnails').scrollLeft += (delta * 10);
  1194.  
  1195. evt.returnValue = false;
  1196. }
  1197.  
  1198. /**
  1199. * Sets the active image based on the thumbnail image that was clicked
  1200. * Expects to be executed as a click event for a DOM object with a "rel" value
  1201. * @param Event The click event
  1202. */
  1203. function clickThumbnail(evt) {
  1204. if (this.rel) {
  1205. activeImage = parseInt(this.rel, 10);
  1206. preloading.pos = activeImage;
  1207. }
  1208. showImage();
  1209. }
  1210.  
  1211. /**
  1212. * Sets the active image to the next image
  1213. * Ensures the active image is not the last one already
  1214. */
  1215. function nextImage() {
  1216. if (settings.mode.value === 'slideshow' && autoplay.active) {
  1217. autoplay.count = autoplay.delay;
  1218. }
  1219.  
  1220. if (activeImage < ((pagination.page * pagination.limit) - 1)) {
  1221. activeImage++;
  1222. showImage();
  1223. }
  1224. }
  1225.  
  1226. /**
  1227. * Sets the active image to the previous image
  1228. * Ensures the active image is not the first one already
  1229. */
  1230. function prevImage() {
  1231. if (settings.mode.value === 'slideshow' && autoplay.active) {
  1232. stopAutoplay();
  1233. }
  1234.  
  1235. if (activeImage > ((pagination.page - 1) * pagination.limit)) {
  1236. activeImage--;
  1237. showImage();
  1238. }
  1239. }
  1240.  
  1241. /**
  1242. * Sets the internal activeImage pointer and sets the image hash parameter
  1243. * Activates the correct thumbnail for the active image
  1244. * @param Int (Optional) The number of the active image (default: value of activeImage internal variable)
  1245. */
  1246. function setActiveImage(pos) {
  1247. pos = (typeof pos === "undefined") ? activeImage : pos;
  1248.  
  1249. setHashParam('page', findImagePage());
  1250. setHashParam('image', (pos + 1));
  1251. showActiveThumb(pos);
  1252. }
  1253.  
  1254. /**
  1255. * Highlights the thumbnail corresponding to the current image
  1256. * and scrolls it into view in slideshow mode
  1257. * @param Int (Optional) The number of the active image (default: value of activeImage internal variable)
  1258. */
  1259. function showActiveThumb(pos) {
  1260. pos = (typeof pos === "undefined") ? activeImage : pos;
  1261.  
  1262. var thumbs = document.getElementsByClassName('thumbnail');
  1263. for (var i = 0; i < thumbs.length; i++) {
  1264. thumbs[i].className = trim(thumbs[i].className.replace('active', ''));
  1265. }
  1266.  
  1267. var thumb = document.getElementById('thumb_' + pos);
  1268. if (thumb) {
  1269. thumb.className += ' active';
  1270. if (settings.mode.value === 'slideshow')
  1271. thumb.scrollIntoView();
  1272. }
  1273. }
  1274.  
  1275. /**
  1276. * Displays/navigates to the "active" image
  1277. * In scrolling mode, this simply scrolls the page up/down to the active image,
  1278. * but in slideshow mode, this shows the active image and hides the others
  1279. * @param Int (Optional) The number of the active image (default: value of activeImage internal variable)
  1280. */
  1281. function showImage(pos) {
  1282. pos = (typeof pos === "undefined") ? activeImage : pos;
  1283.  
  1284. setActiveImage(pos);
  1285.  
  1286. if (settings.mode.value === 'scrolling') {
  1287. var target = document.getElementById('full_' + pos);
  1288. if (target)
  1289. target.scrollIntoView();
  1290. } else if (settings.mode.value === 'slideshow') {
  1291. var link = document.getElementById('slideshowLink');
  1292. var img = document.getElementById('slideshowImage');
  1293.  
  1294. if (link && img) {
  1295. showNotification("Loading image " + (pos + 1), 1000);
  1296. // first blank out the current image and link target from the previous image
  1297. // then use a slight delay to set the next image and link target;
  1298. // this causes the loading animation to be played while the image is loaded;
  1299. // if the delay is removed, the previous image will remain visible until the new image is loaded
  1300. // without any indication that the image has changed and the next image is loading
  1301. img.src = link.href = '';
  1302. setTimeout(function () {
  1303. images[pagination.page - 1][pos].full(url => img.src = link.href = url);
  1304. },
  1305. settings.minLoadingTime.value
  1306. );
  1307.  
  1308. // always remove the autoplay function from load, and then re-add again if autoplay is still enabled
  1309. img.removeEventListener('load', startAutoplay);
  1310. if (autoplay.active && !autoplay.paused) {
  1311. img.addEventListener('load', startAutoplay);
  1312. }
  1313.  
  1314. updatePosition(undefined, undefined, totalImages, (pos + 1));
  1315. refreshNavigation();
  1316. preloadImage();
  1317. if (preloading.active && !preloading.done && (preloading.pos < activeImage))
  1318. preloading.pos = activeImage;
  1319. }
  1320. }
  1321. }
  1322.  
  1323. /**
  1324. * Event handler for keypresses
  1325. * Used to handle hotkeys
  1326. * @param Event The keypress event
  1327. */
  1328. function onKeyDown(evt) {
  1329. if (!evt)
  1330. evt = this;
  1331.  
  1332. if ((evt.target.nodeName === 'INPUT') || (evt.target.nodeName === 'SELECT') || (evt.target.nodeName === 'TEXTAREA'))
  1333. return false;
  1334.  
  1335. var correct = true,
  1336. hotkey;
  1337. for (var h in hotkeys) {
  1338. hotkey = hotkeys[h];
  1339. for (var c in hotkey.codes) {
  1340. if (evt.keyCode === hotkey.codes[c]) {
  1341. for (var m in hotkey.modif) {
  1342. correct = (evt[m] === hotkey.modif[m]) ? correct : false;
  1343. }
  1344. if (correct) {
  1345. evt.preventDefault();
  1346. return (typeof hotkey.action === 'function') ? hotkey.action.call() : eval(hotkey.action);
  1347. }
  1348. }
  1349. }
  1350. }
  1351. }
  1352.  
  1353. /**
  1354. * Event handler for window scroll
  1355. * Used to update the active image when manually scrolling in the scrolling gallery
  1356. * and to determine if the next page of images should be loaded
  1357. * @param Event The scroll event
  1358. */
  1359. function onWindowScroll(evt) {
  1360. if (settings.mode.value === 'scrolling') {
  1361. var imgs = document.getElementById('gallery').getElementsByTagName('img');
  1362. var target = 0;
  1363.  
  1364. // loop backwards through the images until the currently visible image is found
  1365. for (var i = (imgs.length - 1); i >= 0; i--) {
  1366. var current = imgs[i].parentNode.offsetTop;
  1367.  
  1368. if (document.body.scrollTop >= current) {
  1369. target = parseInt(imgs[i].rel, 10);
  1370. break;
  1371. }
  1372. }
  1373.  
  1374. // only update the active image if it changed
  1375. if (target !== activeImage) {
  1376. activeImage = target;
  1377. setActiveImage();
  1378. }
  1379.  
  1380. if (pagination.append) {
  1381. var last = imgs[(imgs.length - 1)];
  1382. if ((last.parentNode.offsetTop - window.innerHeight) <= window.pageYOffset) {
  1383. loadNextPage();
  1384. }
  1385. }
  1386. }
  1387. }
  1388.  
  1389. /**
  1390. * Event handler for window focus
  1391. * Used to monitor setting changes and refresh the gallery when needed
  1392. * @param Event The focus event
  1393. */
  1394. function onWindowFocus(evt) {
  1395. var prevMode = settings.mode.value;
  1396. var prevPagination = settings.pagination.value;
  1397. initSettings();
  1398.  
  1399. var refresh = false;
  1400.  
  1401. var thumbs = document.getElementById('thumbnails');
  1402. if (thumbs) {
  1403. // thumbnails can be toggled without rebuilding the entire gallery page
  1404. thumbs.className = settings.thumbnailsSize.value;
  1405. thumbs.style.display = (settings.thumbnails.value === true) ? 'block' : 'none';
  1406. if (settings.mode.value === 'slideshow')
  1407. resizeSlideshowGallery();
  1408. }
  1409.  
  1410. if (prevMode !== settings.mode.value) {
  1411. // current gallery mode does not match the stored preferences
  1412. refresh = true;
  1413. }
  1414.  
  1415. if (settings.mode.value === 'scrolling') {
  1416. if (prevPagination !== settings.pagination.value) {
  1417. // current gallery pagination does not match the stored preferences
  1418. refresh = true;
  1419. }
  1420. }
  1421.  
  1422. if (refresh)
  1423. rebuildGalleryPage();
  1424. }
  1425.  
  1426. /**
  1427. * Displays the about dialog
  1428. */
  1429. function displayAbout() {
  1430. showDialog(getAbout(), 'About');
  1431. }
  1432.  
  1433. /**
  1434. * Displays the help dialog
  1435. */
  1436. function displayHelp() {
  1437. showDialog(getHelp(), 'Help');
  1438. }
  1439.  
  1440. /**
  1441. * Re-initialize the autoplay settings
  1442. */
  1443. function initAutoplay() {
  1444. if (autoplay.timer)
  1445. window.clearTimeout(autoplay.timer);
  1446.  
  1447. initSetting('autoplay');
  1448.  
  1449. // reset autoplay object properties to default
  1450. autoplay = {
  1451. active: false,
  1452. count: parseInt(settings.autoplay.value, 10),
  1453. delay: parseInt(settings.autoplay.value, 10),
  1454. paused: false,
  1455. timer: undefined
  1456. };
  1457. }
  1458.  
  1459. /**
  1460. * Starts the autoplay used timer for advancing to the next image
  1461. * If the delay is not set correctly, the user is prompted to set it
  1462. */
  1463. function startAutoplay() {
  1464. if (!autoplay.active)
  1465. return false;
  1466.  
  1467. if (autoplay.delay > 1000) {
  1468. // the delay is likely in milliseconds, so convert it to seconds and save
  1469. autoplay.delay /= 1000;
  1470. GM_setValue(settings.autoplay.name, autoplay.delay);
  1471. initAutoplay();
  1472. }
  1473.  
  1474. if (autoplay.delay < 0) {
  1475. // the delay is invalid; inform the user
  1476. var buttons = {0: {text: 'Change Settings', action: changeSettings}};
  1477. showDialog('<p>The current value for the ' + settings.autoplay.label + ' is not valid.</p>', 'Invalid ' + settings.autoplay.label, buttons);
  1478. } else {
  1479. autoplay.count = autoplay.delay;
  1480. if (activeImage < totalImages) {
  1481. autoplayTimer();
  1482. } else {
  1483. stopAutoplay();
  1484. }
  1485. }
  1486. }
  1487.  
  1488. /**
  1489. * Stops the autoplay timer used for advancing to the next image
  1490. */
  1491. function stopAutoplay() {
  1492. window.clearTimeout(autoplay.timer);
  1493. autoplay.active = false;
  1494. autoplay.count = autoplay.delay;
  1495.  
  1496. var counter = document.getElementById('counter');
  1497. if (counter)
  1498. counter.innerHTML = 'Autoplay is disabled';
  1499.  
  1500. var control = document.getElementById('control');
  1501. if (control)
  1502. control.innerHTML = 'Start Autoplay';
  1503. }
  1504.  
  1505. /**
  1506. * Pauses the autoplay timer
  1507. */
  1508. function pauseAutoplay() {
  1509. window.clearTimeout(autoplay.timer);
  1510. autoplay.paused = true;
  1511.  
  1512. var counter = document.getElementById('counter');
  1513. if (counter)
  1514. counter.innerHTML = 'Autoplay is paused';
  1515.  
  1516. var control = document.getElementById('control');
  1517. if (control)
  1518. control.innerHTML = 'Resume Autoplay';
  1519. }
  1520.  
  1521. /**
  1522. * Resumes the autoplay timer
  1523. */
  1524. function resumeAutoplay() {
  1525. var counter = document.getElementById('counter');
  1526. if (counter)
  1527. counter.innerHTML = 'Resuming autoplay...';
  1528.  
  1529. var control = document.getElementById('control');
  1530. if (control)
  1531. control.innerHTML = 'Pause Autoplay';
  1532.  
  1533. autoplay.paused = false;
  1534. autoplayTimer();
  1535. }
  1536.  
  1537. /**
  1538. * Starts the counter indicator for advancing to the next image
  1539. */
  1540. function autoplayTimer() {
  1541. window.clearTimeout(autoplay.timer);
  1542.  
  1543. var counter = document.getElementById('counter');
  1544. if (!counter)
  1545. return false;
  1546.  
  1547. if (activeImage < (totalImages - 1)) {
  1548. if (autoplay.count > 0) {
  1549. autoplay.timer = window.setTimeout(autoplayTimer, 1000);
  1550. counter.innerHTML = 'Advancing image in <b>' + autoplay.count + '</b> seconds';
  1551. } else {
  1552. counter.innerHTML = 'Loading image...';
  1553. nextImage();
  1554. }
  1555. autoplay.count--;
  1556. } else {
  1557. counter.innerHTML = 'End of gallery';
  1558. }
  1559. }
  1560.  
  1561. /**
  1562. * Toggles autoplay (start and pause/resume)
  1563. */
  1564. function toggleAutoplay() {
  1565. autoplay.active = !autoplay.active;
  1566. if (autoplay.active) {
  1567. resumeAutoplay();
  1568. } else {
  1569. pauseAutoplay();
  1570. }
  1571.  
  1572. if (fullscreen)
  1573. showAutoplayIndicator();
  1574. }
  1575.  
  1576. /**
  1577. * Displays an indicator when toggling autoplay
  1578. */
  1579. function showAutoplayIndicator() {
  1580. var gallery = document.getElementById('gallery');
  1581. if (!gallery)
  1582. return false;
  1583.  
  1584. var indicator = document.getElementById('indicator');
  1585. if (indicator)
  1586. hideAutoplayIndicator(0);
  1587.  
  1588. indicator = document.createElement('div');
  1589. indicator.id = 'indicator';
  1590. indicator.style.opacity = 1;
  1591. gallery.appendChild(indicator);
  1592.  
  1593. if (autoplay.active) {
  1594. indicator.innerHTML = '<span class="symbol play">&#9658;</span>';
  1595. } else {
  1596. indicator.innerHTML = '<span class="symbol pause">||</span>';
  1597. }
  1598. setTimeout(function () {
  1599. hideAutoplayIndicator(100);
  1600. }, 1000);
  1601. }
  1602.  
  1603. /**
  1604. * Fades the autoplay indicator
  1605. */
  1606. function hideAutoplayIndicator(duration) {
  1607. duration = (typeof duration === "undefined") ? 100 : duration;
  1608.  
  1609. var indicator = document.getElementById('indicator');
  1610. if (!indicator)
  1611. return false;
  1612.  
  1613. var timer = setInterval(function () {
  1614. if (indicator === null || indicator.parentNode === null) {
  1615. clearInterval(timer);
  1616. } else {
  1617. indicator.style.opacity -= 0.1
  1618. if (indicator.style.opacity <= 0)
  1619. indicator.parentNode.removeChild(indicator);
  1620. }
  1621. }, duration);
  1622. }
  1623.  
  1624. /**
  1625. * Toggles the slideshow mode for gallery pages
  1626. * A dialog is shown after toggling to allow the user to apply the change immediately
  1627. * @param Bool (Optional) If a nofitication should be displayed (default: true)
  1628. */
  1629. function switchGalleryMode(notify) {
  1630. notify = (typeof notify === "undefined") ? true : notify;
  1631.  
  1632. if (settings.mode.value === 'slideshow')
  1633. GM_setValue(settings.mode.name, 'scrolling');
  1634. else if (settings.mode.value === 'scrolling')
  1635. GM_setValue(settings.mode.name, 'slideshow');
  1636. initSetting('mode');
  1637.  
  1638. if (notify) {
  1639. var buttons = {
  1640. 0: {text: 'Apply Change Now', action: rebuildGalleryPage},
  1641. 1: {text: 'Close', action: hideDialog}
  1642. };
  1643. showDialog('<p>Gallery mode has been changed to ' + settings.mode.value + '.</p>', 'Gallery Mode', buttons);
  1644. }
  1645. }
  1646.  
  1647. /**
  1648. * Toggles visibilty on thumbnails and stores the preference
  1649. */
  1650. function toggleThumbs() {
  1651. GM_setValue(settings.thumbnails.name, !GM_getValue(settings.thumbnails.name, settings.thumbnails.def));
  1652. initSetting('thumbnails');
  1653.  
  1654. var visible = settings.thumbnails.value;
  1655.  
  1656. var thumbs = document.getElementById('thumbnails');
  1657. if (thumbs) {
  1658. var thumbsHeight = thumbs.offsetHeight;
  1659. thumbs.style.display = (visible) ? 'block' : 'none';
  1660. showNotification('Thumbnails are now ' + ((visible) ? 'visible' : 'hidden'), 1000);
  1661.  
  1662. if (settings.mode.value === 'slideshow') {
  1663. resizeSlideshowGallery();
  1664. } else if (settings.mode.value === 'scrolling') {
  1665. if (visible) {
  1666. // scroll the window up to the top of the page when thumbnails are visible
  1667. window.scrollTo(0, 0);
  1668. } else {
  1669. // attempt to prevent the page from scrolling too much when thumbnails are hidden
  1670. window.scrollTo(0, (window.pageYOffset - thumbsHeight - 10));
  1671. }
  1672. }
  1673. }
  1674. }
  1675.  
  1676. /**
  1677. * Re-initialize the preloading settings
  1678. */
  1679. function initPreloading() {
  1680. initSetting('preloading');
  1681.  
  1682. // reset autoplay object properties to default
  1683. preloading = {
  1684. active: settings.preloading.value > 0,
  1685. pos: activeImage,
  1686. reach: settings.preloading.value
  1687. };
  1688.  
  1689. }
  1690.  
  1691. function performPreload(imageData) {
  1692. const image = document.createElement('img');
  1693. image.setAttribute('alt', "preloadin...");
  1694. imageData.full(url => image.setAttribute('src', url));
  1695. image.addEventListener('load', preloadImage);
  1696. }
  1697.  
  1698. /**
  1699. * Preloads the next image in slideshow mode
  1700. * Will be called continuously until the last image is loaded
  1701. */
  1702. function preloadImage() {
  1703. if (!preloading.active)
  1704. return false;
  1705.  
  1706. if (preloading.pos < (activeImage + preloading.reach)) {
  1707. showNotification("Preloading image " + (preloading.pos + 1), 1000);
  1708.  
  1709. const imageData = images[pagination.page - 1][preloading.pos];
  1710. if (imageData) {
  1711. performPreload(imageData);
  1712. }
  1713. preloading.pos++;
  1714. }
  1715. }
  1716.  
  1717. /**
  1718. * Toggles full screen view in supported browsers
  1719. * Full screen view is only available in slideshow modepreloading.pos <
  1720. */
  1721. function toggleFullScreen() {
  1722. if (settings.mode.value !== 'slideshow')
  1723. return false;
  1724.  
  1725. if (!document.mozFullScreenElement && !document.webkitFullscreenElement) {
  1726. var target = document.getElementById('content');
  1727. if (target.mozRequestFullScreen) {
  1728. target.mozRequestFullScreen();
  1729. } else if (target.webkitRequestFullscreen) {
  1730. target.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  1731. } else {
  1732. showDialog('Sorry, but it seems your browser does not support customized fullscreen HTML.', 'Fullscreen Error');
  1733. }
  1734. } else {
  1735. if (document.mozCancelFullScreen) {
  1736. document.mozCancelFullScreen();
  1737. } else if (document.webkitCancelFullScreen) {
  1738. document.webkitCancelFullScreen();
  1739. }
  1740. }
  1741. }
  1742.  
  1743. /**
  1744. * Event handler for fullscreen enter/exit
  1745. * Used to move the thumbnails container into the c#ontent container in fullscreen mode
  1746. * and back to the #header container when not in fullscreen mode
  1747. * @param Event fullscreenchange event
  1748. */
  1749. function onFullScreenChange(evt) {
  1750. fullscreen = !(document.mozFullScreenElement === null || document.webkitFullscreenElement === null);
  1751. if (fullscreen) {
  1752. document.getElementById('content').insertBefore(document.getElementById('thumbnails'), document.getElementById('gallery'));
  1753. } else {
  1754. document.getElementById('header').appendChild(document.getElementById('thumbnails'));
  1755. }
  1756. resizeSlideshowGallery();
  1757. }
  1758.  
  1759.  
  1760. /* ===== Dialog Functions =====
  1761. Rather than using boring alert() and prompt() boxes, which are finicky in Greasemonkey,
  1762. this script creates a div, styled with CSS, to replicate that functionality.
  1763. */
  1764.  
  1765. /**
  1766. * Generates the DOM elements for the dialog boxes
  1767. */
  1768. function buildDialog() {
  1769. var dialog = document.getElementById('dialogBox');
  1770. if (dialog) {
  1771. resetDialog();
  1772. return;
  1773. }
  1774.  
  1775. dialog = document.createElement('div');
  1776. dialog.id = 'dialogBox';
  1777. dialog.innerHTML = '<h3 id="dialogTitle"></h3><div id="dialogMessage"></div><div id="dialogButtons"></div>';
  1778.  
  1779. var closeButton = document.createElement('a');
  1780. closeButton.addEventListener('click', hideDialog, false);
  1781. closeButton.id = 'dialogClose';
  1782. closeButton.innerHTML = '&#10006;';
  1783. dialog.appendChild(closeButton);
  1784.  
  1785. var dialogContainer = document.createElement('div');
  1786. dialogContainer.addEventListener('click', function (e) {
  1787. hideDialog(e, true);
  1788. }, false);
  1789. dialogContainer.id = 'dialogContainer';
  1790. dialogContainer.style.display = 'none';
  1791. dialogContainer.appendChild(dialog);
  1792. body.appendChild(dialogContainer);
  1793. }
  1794.  
  1795. /**
  1796. * Hides the dialog box
  1797. * @param Event The click event
  1798. * @param Bool (Optional) If true, will only hide the dialog if the target of the click event is the dialogContainer
  1799. */
  1800. function hideDialog(e, containerOnly) {
  1801. containerOnly = (typeof containerOnly === "undefined") ? false : containerOnly;
  1802. if (containerOnly && (typeof e !== "undefined") && (e.target.id !== 'dialogContainer'))
  1803. return false;
  1804.  
  1805. var dialogContainer = document.getElementById('dialogContainer');
  1806. if (dialogContainer)
  1807. dialogContainer.style.display = 'none';
  1808. }
  1809.  
  1810. /**
  1811. * Vertically centers the dialog box on the screen
  1812. */
  1813. function positionDialog() {
  1814. var dialogBox = document.getElementById('dialogBox');
  1815. var dialogContainer = document.getElementById('dialogContainer');
  1816.  
  1817. dialogContainer.style.display = 'block';
  1818. var top = ((window.innerHeight / 2) - (dialogBox.offsetHeight / 2) - 20);
  1819. dialogBox.style.top = (top < 0) ? '0' : top + 'px';
  1820.  
  1821. dialogContainer.scrollTop = 0;
  1822. }
  1823.  
  1824. /**
  1825. * Clears out the contents of the dialog box
  1826. */
  1827. function resetDialog() {
  1828. document.getElementById('dialogTitle').innerHTML = '';
  1829. document.getElementById('dialogMessage').innerHTML = '';
  1830. document.getElementById('dialogButtons').innerHTML = '';
  1831. }
  1832.  
  1833. /**
  1834. * Preloads the next image in slideshow mode
  1835. * Will be called continuously until the last image is loaded
  1836. * @param String The HTML content of the dialog box
  1837. * @param String (Optional) A title for the dialog box (always prefixed with '[Script Name]')
  1838. * @param Object (Optional) An object representing the buttons to display and their actions (default: close button)
  1839. */
  1840. function showDialog(content, title, buttons) {
  1841. buttons = (typeof buttons === "undefined") ? {0: {text: 'Close', action: hideDialog}} : buttons;
  1842. title = (typeof title === "undefined") ? '' : title;
  1843.  
  1844. buildDialog();
  1845.  
  1846. var dialogContainer = document.getElementById('dialogContainer');
  1847. var titleContainer = document.getElementById('dialogTitle');
  1848. var messageContainer = document.getElementById('dialogMessage');
  1849. var buttonContainer = document.getElementById('dialogButtons');
  1850.  
  1851. titleContainer.innerHTML = '[<b>' + GM_info.script.name + '</b>] ' + title;
  1852. messageContainer.innerHTML = content;
  1853.  
  1854. var btn;
  1855. for (var button in buttons) {
  1856. btn = document.createElement('button');
  1857. btn.addEventListener('click', buttons[button].action, false);
  1858. btn.innerHTML = buttons[button].text;
  1859. if (button == 0)
  1860. btn.className = 'default';
  1861. buttonContainer.appendChild(btn);
  1862. }
  1863.  
  1864. positionDialog();
  1865.  
  1866. buttonContainer.childNodes[0].focus();
  1867. }
  1868.  
  1869.  
  1870. /* ===== Notication Message Functions =====
  1871. The are notification messages that display in the lower right corner and are
  1872. automatically removed after a short duration without needing user interaction.
  1873. */
  1874.  
  1875. /**
  1876. * Generates and displays a notification message
  1877. * @param String The notification message text
  1878. * @param Int (Optional) The delay (in milliseconds) before the message should be hidden (default: 5000)
  1879. * @param Int (Optional) The duration (in milliseconds) over which the message should fade (default: 100)
  1880. * @return Object The DOM object of the created notification
  1881. */
  1882. function showNotification(text, delay, duration) {
  1883. delay = (typeof delay === "undefined") ? 5000 : delay;
  1884.  
  1885. var notificationContainer = document.getElementById('notificationContainer');
  1886. if (!notificationContainer) {
  1887. notificationContainer = document.createElement('div');
  1888. notificationContainer.id = 'notificationContainer';
  1889. document.getElementById('content').appendChild(notificationContainer);
  1890. }
  1891. notificationContainer.style.bottom = (document.getElementById('footer').offsetHeight + 10) + 'px';
  1892.  
  1893. var notifications = notificationContainer.getElementsByClassName('notification');
  1894.  
  1895. var notification = document.createElement('div');
  1896. notification.className = 'notification';
  1897. notification.id = 'notification' + notifications.length;
  1898. notification.innerHTML = text;
  1899.  
  1900. if (notifications.length > 0) {
  1901. notificationContainer.insertBefore(notification, notifications[0]);
  1902. } else {
  1903. notificationContainer.appendChild(notification);
  1904. }
  1905.  
  1906. if (delay > 0)
  1907. setTimeout(function () {
  1908. hideNotification(notification, duration);
  1909. }, delay);
  1910.  
  1911. return notification;
  1912. }
  1913.  
  1914. /**
  1915. * Fades a notification message out and then removes it
  1916. * @param Object The notification message to hide
  1917. * @param Int (Optional) The duration (in milliseconds) over which the message should fade (default: 100)
  1918. */
  1919. function hideNotification(notification, duration) {
  1920. duration = (typeof duration === "undefined") ? 100 : duration;
  1921.  
  1922. if (!notification)
  1923. return false;
  1924.  
  1925. notification.style.opacity = 1;
  1926. var timer = setInterval(function () {
  1927. notification.style.opacity -= 0.1
  1928. if (notification.style.opacity <= 0) {
  1929. notification.parentNode.removeChild(notification);
  1930. clearInterval(timer);
  1931. }
  1932. }, duration);
  1933. }
  1934.  
  1935.  
  1936. /* ===== Setting Functions =====
  1937. Functions used for working with the individual settings in bulk
  1938. */
  1939.  
  1940. /**
  1941. * Saves the values for all settings from the Settings Form Dialog Box
  1942. * Verifies values are valid based on criteria of the individual settings (min, max, etc.)
  1943. */
  1944. function applySettings() {
  1945. var setting, field, value;
  1946. for (s in settings) {
  1947. setting = settings[s];
  1948. if (setting && (typeof setting === 'object')) {
  1949. value = null;
  1950. field = document.getElementById('setting-' + s);
  1951. if (field) {
  1952. switch (setting.type) {
  1953. case 'integer':
  1954. value = parseInt(field.value, 10);
  1955. if (isNaN(value)) {
  1956. var buttons = {0: {text: 'Try Again', action: changeSettings}};
  1957. showDialog('<p>A numeric value must be provided for <b>' + setting.label + '</b>.</p>', 'Error', buttons);
  1958. return false;
  1959. }
  1960. if ((typeof setting.min !== 'undefined') && (setting.min !== null) && (value < setting.min)) {
  1961. var buttons = {0: {text: 'Try Again', action: changeSettings}};
  1962. showDialog('<p>The value provided (<span class="error">' + value + '</span>) for <b>' + setting.label + '</b> must be greater than or equal to <b>' + setting.min + '</b>.</p>', 'Error', buttons);
  1963. return false;
  1964. }
  1965. if ((typeof setting.max !== 'undefined') && (setting.max !== null) && (value > setting.max)) {
  1966. var buttons = {0: {text: 'Try Again', action: changeSettings}};
  1967. showDialog('<p>The value provided for (<span class="error">' + value + '</span>) <b>' + setting.label + '</b> must be less than or equal to <b>' + setting.max + '</b>.</p>', 'Error', buttons);
  1968. return false;
  1969. }
  1970. break;
  1971. case 'select':
  1972. value = field.options[field.selectedIndex].value;
  1973. break;
  1974. case 'boolean':
  1975. value = field.checked;
  1976. break;
  1977. default:
  1978. value = field.value;
  1979. break;
  1980. }
  1981. }
  1982. if (value !== null) {
  1983. settings[s].value = value;
  1984. }
  1985. }
  1986. }
  1987. saveSettings();
  1988. var buttons = {0: {text: 'Apply Changes Now', action: rebuildGalleryPage}, 1: {text: 'Close', action: hideDialog}};
  1989. showDialog('<p>Your changes have been saved successfully!</p>', 'Success', buttons);
  1990. }
  1991.  
  1992. /**
  1993. * Checks if settings have been saved for the current script version.
  1994. * For new installs, a notice is displayed forcing the user to save the settings for the first time;
  1995. * for updates, the user can view the settings or dismiss the notice.
  1996. */
  1997. function checkSettings() {
  1998. var lastSavedVersion = GM_getValue('lastSavedVersion', false);
  1999. if (!lastSavedVersion) {
  2000. var buttons = {0: {text: 'Continue', action: changeSettings}};
  2001. showDialog('<p>Since this is your first time using ' + GM_info.script.name + ', you need to view (and modify) the settings to meet your needs.</p><p>Note that all settings have a default value set already for your convenience; you can simply click the "Save Settings" button on the next dialog box to continue.', 'First Run', buttons);
  2002. } else if (lastSavedVersion !== GM_info.script.version) {
  2003. GM_setValue('lastSavedVersion', GM_info.script.version);
  2004. var buttons = {0: {text: 'Change Settings', action: changeSettings}, 1: {text: 'Close', action: hideDialog}};
  2005. showDialog('<p>The version of this script has changed from ' + lastSavedVersion + ' to ' + GM_info.script.version + '. There may be new settings for you to utilize.', 'Version Change', buttons);
  2006. }
  2007. }
  2008.  
  2009. /**
  2010. * Initializes all settings from saved preferences
  2011. * Uses the default values for settings that have not yet been saved
  2012. */
  2013. function initSettings() {
  2014. for (s in settings) {
  2015. initSetting(s)
  2016. }
  2017. }
  2018.  
  2019. /**
  2020. * Initializes the specified setting from saved preferences
  2021. * Uses the default value for settings that have not yet been saved
  2022. * @param String name (key) of the setting to initialize
  2023. */
  2024. function initSetting(s) {
  2025. if (settings[s] && (typeof settings[s] === 'object')) {
  2026. if (settings[s].name)
  2027. settings[s].value = GM_getValue(settings[s].name, settings[s].def);
  2028.  
  2029. if (settings[s].type === 'integer')
  2030. settings[s].value = parseInt(settings[s].value, 10);
  2031. else if ((settings[s].type === 'select') && (settings[s].opts.indexOf(settings[s].value) === -1))
  2032. settings[s].value = settings[s].def;
  2033. }
  2034. }
  2035.  
  2036. /**
  2037. * Generates the HTML for the Settings Form that will be displayed via dialog box
  2038. */
  2039. function changeSettings() {
  2040. initSettings();
  2041. var setting;
  2042. var html = '<form id="settingsForm">';
  2043. for (s in settings) {
  2044. setting = settings[s];
  2045. if (setting && (typeof setting === 'object')) {
  2046. html += '<fieldset><legend>' + setting.label + '</legend>';
  2047. switch (setting.type) {
  2048. case 'integer':
  2049. case 'text':
  2050. html += '<label for="setting-' + s + '">Enter a value for the ' + setting.label;
  2051. if (setting.hint)
  2052. html += ' (' + setting.hint + ')';
  2053. html += ':<br/><span class="default">Default value: <b>' + setting.def + '</b></span></label>';
  2054. html += '<input type="text" id="setting-' + s + '" name="' + s + '" value="' + setting.value + '" size="' + setting.size + '" maxlength="' + setting.size + '"/>';
  2055. break;
  2056. case 'select':
  2057. html += '<label for="setting-' + s + '">Select a value for the ' + setting.label;
  2058. if (setting.hint)
  2059. html += ' (' + setting.hint + ')';
  2060. html += ':<br/><span class="default">Default value: <b>' + capitalize(setting.def) + '</b></span></label>';
  2061. html += '<select id="setting-' + s + '" name="' + s + '">';
  2062. for (opt in setting.opts) {
  2063. html += '<option value="' + setting.opts[opt] + '"' + ((setting.value === setting.opts[opt]) ? ' selected="selected"' : '') + '>' + capitalize(setting.opts[opt]) + '</option>';
  2064. }
  2065. html += '</select>';
  2066. break;
  2067. case 'boolean':
  2068. html += '<input type="checkbox" id="setting-' + s + '" name="' + s + '" value="true"' + ((setting.value) ? ' checked="checked"' : '') + '/>';
  2069. html += '<label for="setting-' + s + '">Enable ' + setting.label;
  2070. if (setting.hint)
  2071. html += ' (' + setting.hint + ')';
  2072. html += '<br/><span class="default">Default value: <b>' + ((setting.def) ? 'Enabled' : 'Disabled') + '</b></span></label>';
  2073. break;
  2074. }
  2075. html += '</fieldset>';
  2076. }
  2077. }
  2078. showDialog(html, 'Settings', {
  2079. 0: {text: 'Save Settings', action: applySettings},
  2080. 1: {text: 'Cancel', action: hideDialog}
  2081. });
  2082. }
  2083.  
  2084. /**
  2085. * Saves the current value for each setting to local storage
  2086. */
  2087. function saveSettings() {
  2088. var setting;
  2089. for (s in settings) {
  2090. setting = settings[s];
  2091. if (setting && (typeof setting === 'object'))
  2092. GM_setValue(setting.name, setting.value);
  2093. }
  2094. initSettings();
  2095. GM_setValue('lastSavedVersion', GM_info.script.version);
  2096. }
  2097.  
  2098.  
  2099. /* ===== String Functions =====
  2100. Utility functions for manipulating strings
  2101. */
  2102.  
  2103. /**
  2104. * Escapes quotes and double-quotes in a string
  2105. * @param String The string to escape
  2106. * @return String The escaped string
  2107. */
  2108. function addslashes(str) {
  2109. return str.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'').replace(/\"/g, '\\"').replace(/\0/g, '\\0');
  2110. }
  2111.  
  2112. /**
  2113. * Unescapes quotes and double-quotes in a string
  2114. * @param String The escaped string
  2115. * @return String The string to escape
  2116. */
  2117. function stripslashes(str) {
  2118. return str.replace(/\\'/g, '\'').replace(/\\"/g, '"').replace(/\\0/g, '\0').replace(/\\\\/g, '\\');
  2119. }
  2120.  
  2121. /**
  2122. * Removes leading and trailing whitepsace from a string
  2123. * @param String The string to trim
  2124. * @return String The trimmed string
  2125. */
  2126. function trim(str) {
  2127. return str.replace(/^\s+|\s+$/g, '');
  2128. }
  2129.  
  2130. /**
  2131. * Capitalizes the first character of a string
  2132. * @param String The string to capitalize
  2133. * @return String The capitalized string
  2134. */
  2135. function capitalize(str) {
  2136. return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
  2137. }
  2138.  
  2139.  
  2140. /* ===== Hash Parameter Functions =====
  2141. Utility functions for setting and retrieving data from the URL location hash
  2142. */
  2143.  
  2144. /**
  2145. * Returns all key/value pairs stored in the location hash
  2146. * @return Object The location hash parameters indexed by key name
  2147. */
  2148. function getHashParams() {
  2149. var params = window.location.hash.replace('#!', '').split('/');
  2150. var ret = [];
  2151. for (var i = 0; i < params.length; i = i + 2) {
  2152. if (params[(i + 1)] && params[(i + 2)])
  2153. ret[params[(i + 1)]] = params[(i + 2)];
  2154. }
  2155. return ret;
  2156. }
  2157.  
  2158. /**
  2159. * Returns the value of the supplied hash key
  2160. * @param String The name of the hash key
  2161. * @return String The value of the hash key
  2162. */
  2163. function getHashParam(key) {
  2164. var params = getHashParams();
  2165. return params[key] || undefined;
  2166. }
  2167.  
  2168. /**
  2169. * Sets the value of the supplied hash key
  2170. * @param String The name of the hash key
  2171. * @param String The value of the hash key
  2172. */
  2173. function setHashParam(key, val) {
  2174. var hashString = '#!';
  2175. var params = getHashParams();
  2176. params[key] = val;
  2177. for (key in params) {
  2178. hashString += '/' + key + '/' + params[key];
  2179. }
  2180. history.replaceState(null, null, hashString);
  2181. }
  2182.  
  2183. /**
  2184. * Removes a hash key/value pair from the location hash
  2185. * @param String The name of the hash key
  2186. */
  2187. function unsetHashParam(key) {
  2188. var current = getHashParam(key);
  2189. if (typeof current !== 'undefined')
  2190. history.replaceState(null, null, window.location.hash.replace('/' + key + '/' + current, ''));
  2191. }
  2192.  
  2193.  
  2194. /* ===== Gallery CSS =====
  2195. The CSS for the rebuilt gallery page with theme support
  2196. */
  2197.  
  2198. /**
  2199. * Generates the CSS used for the rebuilt gallery page
  2200. * @param String (Optional) Additional CSS
  2201. * @return String The CSS for the rebuilt gallery page
  2202. */
  2203. function getCSS(css) {
  2204. css = (typeof css === "undefined") ? '' : css;
  2205.  
  2206. initSettings('theme');
  2207. switch (getHashParam('theme') || settings.theme.value) {
  2208. case 'blue':
  2209. var bg1 = '#060D1A';
  2210. var bg2 = '#03060D';
  2211. var fg1 = '#557799';
  2212. var fg2 = '#557799';
  2213. var links = '#AABBCC';
  2214. var accent1 = '#6699CC';
  2215. var accent2 = '#6699CC';
  2216. break;
  2217.  
  2218. case 'classic':
  2219. var bg1 = '#FFFFFF';
  2220. var bg2 = '#3366CC';
  2221. var fg1 = '#666666';
  2222. var fg2 = '#AACCEE';
  2223. var links = '#AACCEE';
  2224. var accent1 = '#3366CC';
  2225. var accent2 = '#FFFFFF';
  2226. break;
  2227.  
  2228. case 'green':
  2229. var bg1 = '#FFFFFF';
  2230. var bg2 = '#222222';
  2231. var fg1 = '#888888';
  2232. var fg2 = '#888888';
  2233. var links = '#AAAAAA';
  2234. var accent1 = '#33AA00';
  2235. var accent2 = '#66CC33';
  2236. break;
  2237.  
  2238. case 'default':
  2239. default:
  2240. var bg1 = '#222222';
  2241. var bg2 = '#111111';
  2242. var fg1 = '#888888';
  2243. var fg2 = '#888888';
  2244. var links = '#AAAAAA';
  2245. var accent1 = '#3380CC';
  2246. var accent2 = '#3380CC';
  2247. break;
  2248. }
  2249.  
  2250. /**
  2251. * Darkens a color by a specified amount
  2252. * @param String The RGB hex color code
  2253. * @param Int The amount (decimal-format pertentage) by which to darken the color
  2254. * @return String The color code for the darkened color
  2255. */
  2256. function darken(color, amount) {
  2257. color = splitColor(color);
  2258. var ret = [];
  2259. for (var i = 0; i < color.length; i++) {
  2260. ret[i] = (color[i] - Math.ceil(255 * amount));
  2261.  
  2262. if (ret[i] < 0)
  2263. ret[i] = 0;
  2264. if (ret[i] > 255)
  2265. ret[i] = 255;
  2266.  
  2267. ret[i] = ret[i].toString(16);
  2268. if (ret[i].length < 2)
  2269. ret[i] = '0' + ret[i];
  2270. }
  2271. return '#' + ret.join('').toUpperCase();
  2272. }
  2273.  
  2274. /**
  2275. * Lightens a color by a specified amount
  2276. * @param String The RGB hex color code
  2277. * @param Int The amount (decimal-format pertentage) by which to lighten the color
  2278. * @return String The color code for the lightened color
  2279. */
  2280. function lighten(color, amount) {
  2281. color = splitColor(color);
  2282. var ret = [];
  2283. for (var i = 0; i < color.length; i++) {
  2284. ret[i] = (color[i] + Math.ceil(255 * amount));
  2285.  
  2286. if (ret[i] < 0)
  2287. ret[i] = 0;
  2288. if (ret[i] > 255)
  2289. ret[i] = 255;
  2290.  
  2291. ret[i] = ret[i].toString(16);
  2292. if (ret[i].length < 2)
  2293. ret[i] = '0' + ret[i];
  2294. }
  2295. return '#' + ret.join('').toUpperCase();
  2296. }
  2297.  
  2298. /**
  2299. * Converts a color code into a usable array for math-based functions
  2300. * @param String The RGB hex color code
  2301. * @return Int[] The array containing the decimal values of each color
  2302. */
  2303. function splitColor(color) {
  2304. color = color.replace('#', '');
  2305.  
  2306. var offset = Math.floor(color.length / 3);
  2307. var ret = [];
  2308. for (var i = 0; i < color.length; i += offset) {
  2309. ret.push(parseInt(color.substring(i, (i + offset)), 16));
  2310. }
  2311. return ret;
  2312. }
  2313.  
  2314. /**
  2315. * Returns the CSS for a vertical background gradient (with vendor-specific prefixes)
  2316. * @param String The RGB hex color code of the top color
  2317. * @param String The RGB hex color code of the bottom color
  2318. * @return String The CSS for the background gradient
  2319. */
  2320. function gradient(top, bottom) {
  2321. var ret = '';
  2322. ret += 'background: -moz-linear-gradient(top, ' + top + ' 0%, ' + bottom + ' 100%);';
  2323. ret += 'background: -webkit-linear-gradient(top, ' + top + ' 0%, ' + bottom + ' 100%);';
  2324. return ret;
  2325. }
  2326.  
  2327. /**
  2328. * Returns the CSS for rounded corners (with vendor-specific prefixes)
  2329. * @param String The radius value (syntax: '#px' or '#px #px #px #px')
  2330. * @return String The CSS for the rounded corners
  2331. */
  2332. function borderRadius(radius) {
  2333. radius = 'border-radius: ' + radius;
  2334. // returns: -moz-border-radius: <radius>; -webkit-border-radius: <radius>; border-radius: <radius>;
  2335. return '-moz-' + radius + '; ' + '-webkit-' + radius + '; ' + radius + ';';
  2336. }
  2337.  
  2338. /**
  2339. * Returns the CSS for box shadows (with vendor-specific prefixes)
  2340. * @param String The shadow value (syntax: '#px #px [#px] [#px] color [inset]')
  2341. * @return String The CSS for the box shadows
  2342. */
  2343. function boxShadow(shadow) {
  2344. shadow = 'box-shadow: ' + shadow;
  2345. // returns: -moz-box-shadow: <shadow>; -webkit-box-shadow: <shadow>; box-shadow: <shadow>;
  2346. return '-moz-' + shadow + '; ' + '-webkit-' + shadow + '; ' + shadow + ';';
  2347. }
  2348.  
  2349. // basics
  2350. css += '* { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; margin: 0; padding: 0; }';
  2351. css += 'body { background-color: ' + bg1 + '; color: ' + fg1 + '; font: 13px Helvetica, Arial, sans-serif; }';
  2352. css += 'a { color: ' + links + '; cursor: pointer; text-decoration: underline; }';
  2353. css += 'a:hover, a:hover { color: ' + lighten(links, 0.133) + '; text-decoration: none; }';
  2354. css += 'p { margin: 0 0 10px; }';
  2355. css += 'table { font: 13px Helvetica, Arial, sans-serif; margin: auto; width: 100%; }';
  2356.  
  2357. // layout
  2358. css += '#header { background-color: ' + bg2 + '; border-bottom: 1px solid ' + lighten(bg1, 0.066) + '; color: ' + fg2 + '; padding: 10px 0 0; text-align: center; }';
  2359. css += '.scrolling #header { margin-bottom: 10px; }';
  2360. css += '.slideshow #header { min-height: 60px; position: fixed; top: 0; width: 100%; z-index: 2; }';
  2361. css += '#header .title { color: ' + accent2 + '; }';
  2362. css += '#header small { font-variant: small-caps; }';
  2363. css += '#header p { margin: 10px 0; }';
  2364. css += '#header #description { margin: 10px auto; max-width: 60%; text-align: center; }';
  2365. css += '#search { position: absolute; right: 10px; top: 10px; }';
  2366. css += '#footer { background-color: ' + bg2 + '; border-top: 1px solid ' + lighten(bg1, 0.066) + '; color: ' + fg2 + '; min-height: 60px; padding: 10px 0; position: relative; text-align: center; }';
  2367. css += '.scrolling #footer { margin-top: 10px; position: fixed; bottom: 0; left: 0; right: 0; }';
  2368. css += '.slideshow #footer { bottom: 0; height: 60px; position: fixed; width: 100%; z-index: 2; }';
  2369. css += '#favorites_container { height: 40px; line-height: 40px; margin-bottom: 0 !important; position: absolute; left: 25px; bottom: 10px; }';
  2370. css += '#autoplay { height: 20px; line-height: 20px; position: absolute; right: 25px; bottom: 20px; }';
  2371. css += '#info { font-size: 11px; margin: 10px 0 0; }';
  2372.  
  2373. // logo
  2374. css += '#logo { text-decoration: none; position: absolute; left: 10px; top: 10px; }';
  2375. css += '#logo span { font: 18px "Comic Sans MS"; padding: 0 2px; }';
  2376. css += '#logo .image { background-color: ' + bg1 + '; color: ' + accent1 + '; }';
  2377. css += '#logo:hover .image { background-color: ' + accent1 + '; color: ' + bg1 + '; }';
  2378. css += '#logo .fap { background-color: ' + accent1 + '; color: ' + bg1 + '; }';
  2379. css += '#logo:hover .fap { background-color: ' + bg1 + '; color: ' + accent1 + '; }';
  2380.  
  2381. // forms
  2382. css += 'form { margin: 0 0 10px; }';
  2383. css += 'input[type="text"], select { background: ' + bg1 + '; border: 1px solid ' + fg1 + '; color: ' + fg1 + '; margin-right: 5px; padding: 5px; }';
  2384. css += 'input[type="text"]:focus, select:focus { border-color: ' + accent1 + '; color: ' + fg1 + '; }';
  2385. css += 'button, input[type="button"], input[type="submit"] { background: ' + lighten(bg2, 0.066) + '; ' + gradient(lighten(bg2, 0.133), lighten(bg2, 0.066)) + '; border: 1px solid ' + lighten(bg2, 0.200) + '; ' + borderRadius('5px') + ' color: ' + links + '; cursor: pointer; margin-left: 5px; padding: 5px; ' + boxShadow('0 0 0 1px ' + bg2) + '; }';
  2386. css += 'button:hover, input[type="button"]:hover, input[type="submit"]:hover { ' + gradient(lighten(bg2, 0.200), lighten(bg2, 0.066)) + '; border: 1px solid ' + lighten(bg2, 0.266) + '; color: ' + lighten(links, 0.066) + '; }';
  2387. css += 'button:focus, input[type="button"]:focus, input[type="submit"]:focus { ' + gradient(lighten(bg2, 0.266), lighten(bg2, 0.066)) + '; border: 2px solid ' + lighten(bg2, 0.333) + '; color: ' + lighten(links, 0.133) + '; padding: 4px; }';
  2388.  
  2389. css += '#favorites_container input[type="button"], button.default, input[type="submit"] { border: 1px solid ' + accent1 + '; color: ' + lighten(links, 0.133) + '; }';
  2390. css += '#favorites_container input[type="button"]:hover, button.default:hover, input[type="submit"]:hover { border: 1px solid ' + lighten(accent1, 0.066) + '; color: ' + lighten(links, 0.200) + '; }';
  2391. css += '#favorites_container input[type="button"]:focus, button.default:focus, input[type="submit"]:focus { border: 2px solid ' + lighten(accent1, 0.066) + '; color: ' + lighten(links, 0.266) + '; padding: 4px; }';
  2392.  
  2393. // pagination
  2394. css += '.pagination { margin: 0; }';
  2395. css += '.pagination a { border: 1px solid ' + darken(links, 0.133) + '; color: ' + darken(links, 0.133) + '; display: inline-block; margin: 0 2px 10px; padding: 2px 6px; text-decoration: none; }';
  2396. css += '.pagination a:hover { border-color: ' + links + '; color: ' + links + '; display: inline-block; margin: 0 2px; padding: 2px 6px; text-decoration: none; }';
  2397. css += '.pagination a.current { border: 1px solid ' + accent2 + '; color: ' + accent2 + '; }';
  2398. css += '.pagination a.disabled { border: 1px solid ' + darken(fg2, 0.266) + '; color: ' + darken(fg2, 0.133) + '; cursor: default; display: inline-block; margin: 0 2px; padding: 2px 6px; }';
  2399.  
  2400. // thumbnails
  2401. css += '#thumbnails { margin-bottom: 10px; text-align: center; z-index: 2; }';
  2402. css += '#thumbnails .thumbnail { border: 1px solid ' + darken(links, 0.133) + '; display: inline-block; margin: 2px; padding: 4px; vertical-align: middle; }';
  2403. css += '#thumbnails .thumbnail:hover { border-color: ' + links + '; }';
  2404. css += '#thumbnails .thumbnail.active { border: 2px solid ' + accent2 + '; padding: 3px; }';
  2405. css += '#thumbnails.small img { max-height: 100px; }';
  2406. css += '#thumbnails.medium img { max-height: 150px; }';
  2407. css += '#thumbnails.large img { max-height: 200px; }';
  2408. css += '.slideshow #thumbnails { background-color: ' + bg2 + '; padding-bottom: 10px; overflow-y: hidden; width: 100%; white-space: nowrap; }';
  2409. css += '.slideshow #thumbnails .thumbnail { margin: 0 5px; }';
  2410.  
  2411. // gallery basics
  2412. css += '#gallery { position: relative; text-align: center; }';
  2413. css += '#gallery .image img { border: 1px solid ' + darken(links, 0.133) + '; padding: 4px; min-height: 100px; min-width: 100px; }';
  2414. css += '#gallery .image:hover img { border-color: ' + accent1 + '; }';
  2415. css += '#gallery .image .spinner { border: 10px solid ' + darken(fg2, 0.266) + '; border-left-color: ' + accent1 + '; position: absolute; left: calc(100% / 2 - 80px / 2); top: calc(100% / 2 - 80px / 2); -webkit-animation: spinning 1s infinite linear; animation: spinning 1s infinite linear; }';
  2416. css += '#gallery .image .spinner, #gallery .image .spinner:after { ' + borderRadius('50%') + '; width: 80px; height: 80px; z-index: -1; }';
  2417. css += '@-webkit-keyframes spinning { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }';
  2418. css += '@keyframes spinning { 0% { -webkit-transform: rotate(0deg); transform: rotate(0deg); } 100% { -webkit-transform: rotate(360deg); transform: rotate(360deg); } }';
  2419.  
  2420. // scrolling gallery
  2421. css += '.scrolling #gallery { max-width: 100%; }';
  2422. css += '.scrolling #gallery .image { clear: both; display: inline-block; max-width: 98%; position: relative; }';
  2423. css += '.scrolling #gallery .image img { display: inline-block; max-width: 100%; }';
  2424.  
  2425. css += '#loader { background: ' + lighten(bg2, 0.066) + '; ' + gradient(lighten(bg2, 0.133), lighten(bg2, 0.066)) + '; border: 1px solid ' + accent1 + '; border-radius: 5px; ' + boxShadow('0 0 0 1px ' + bg2) + '; color: ' + lighten(links, 0.133) + '; display: inline-block; margin-top: 10px; padding: 8px 16px; text-decoration: none; }';
  2426. css += '#loader:hover { ' + gradient(lighten(bg2, 0.200), lighten(bg2, 0.066)) + '; border-color: ' + lighten(accent1, 0.066) + '; color: ' + lighten(links, 0.200) + '; }';
  2427.  
  2428. // slideshow gallery
  2429. css += '.slideshow #content { bottom: 81px; left: 0; padding: 10px; position: absolute; right: 0; top: 81px; }';
  2430. css += '.slideshow #gallery { height: 100%; width: 100%; overflow: hidden; }';
  2431. css += '.slideshow #gallery .image img { bottom: 0; left: 0; margin: auto; max-height: 100%; max-width: 100%; position: absolute; right: 0; top: 0; }';
  2432. css += '.slideshow #gallery .nav { color: #FFFFFF; display: block; text-decoration: none; opacity: 0.5; position: absolute; top: 5px; bottom: 5px; height: 100%; width: 160px; z-index: 1; }';
  2433. css += '.slideshow #gallery .nav.disabled { display: none; }';
  2434. css += '.slideshow #gallery #next { min-height: 60px; min-width: 60px; right: 5px; }';
  2435. css += '.slideshow #gallery #prev { min-height: 60px; min-width: 60px; left: 5px; }';
  2436. css += '.slideshow #gallery .arrow { display: block; font-size: 120px; height: 160px; line-height: 160px; margin-top: -80px; position: relative; text-align: center; text-shadow: 0 0 5px #000000, 0 0 20px #FFFFFF; top: 50%; }';
  2437. css += '.slideshow #gallery .nav:hover { background-color: rgba(0,0,0,0.5); opacity: 1.0; }';
  2438. css += '.slideshow #gallery #indicator { background-color: rgba(0,0,0,0.5); ' + borderRadius('40px') + '; display: block; position: absolute; top: 50%; left: 50%; height: 160px; margin-top: -80px; margin-left: -80px; width: 160px; z-index: 1; }';
  2439. css += '.slideshow #gallery #indicator .symbol { color: #FFFFFF; font-size: 120px; position: relative; text-align: center; text-shadow: 0 0 5px #000000, 0 0 20px #FFFFFF; }';
  2440. css += '.slideshow #gallery #indicator .symbol.pause { font-weight: bold; }';
  2441. css += '.slideshow #gallery #indicator .symbol.play { line-height: 160px; }';
  2442.  
  2443. // full screen:: WebKit
  2444. css += ':-webkit-full-screen { background-color: #000000; }';
  2445. css += '#content:-webkit-full-screen { padding: 0; top: 0 !important; bottom: 0 !important; height: 100%; width: 100%; }';
  2446. css += '#content:-webkit-full-screen .nav { ' + borderRadius('40px') + '; height: 160px; margin-top: -80px; top: 50%; }';
  2447. css += '#content:-webkit-full-screen .image img { border: 0; padding: 0; }';
  2448. css += '#content:-webkit-full-screen #thumbnails { background-color: #000000; position: absolute; top: 0; }';
  2449. // full screen:: Mozilla
  2450. css += '#content:-moz-full-screen { padding: 0; top: 0 !important; bottom: 0 !important; height: 100%; width: 100%; }';
  2451. css += '#content:-moz-full-screen .nav { ' + borderRadius('40px') + '; height: 160px; margin-top: -80px; top: 50%; }';
  2452. css += '#content:-moz-full-screen .image img { border: 0; padding: 0; }';
  2453. css += '#content:-moz-full-screen #thumbnails { background-color: #000000; position: absolute; top: 0; }';
  2454.  
  2455. // add to favorites
  2456. css += '#favorites_container table { width: auto; }';
  2457.  
  2458. // notification messages
  2459. css += '#notificationContainer { bottom: 70px; position: fixed; right: 10px; }';
  2460. css += '.notification { background: rgba(0, 0, 0, 0.8); border: 1px solid rgba(255, 255, 255, 0.2); ' + borderRadius('5px') + '; color: rgba(255, 255, 255, 0.5); display: block; font-size: 11px; margin-top: 10px; padding: 9px; }';
  2461.  
  2462. // dialog box layout
  2463. css += '#dialogContainer { background: rgba(0,0,0,0.8); display: block; text-align: center; position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 3; overflow-y: auto; }';
  2464. css += '#dialogBox { background: ' + bg1 + '; border: 1px solid ' + lighten(bg2, 0.200) + '; ' + boxShadow('0 0 20px 0 #000000') + '; color: ' + fg1 + '; display: inline-block; margin: 20px auto; min-width: 300px; text-align: left; position: relative; z-index: 10; }';
  2465. css += '#dialogClose { border-left: 1px solid ' + lighten(bg2, 0.200) + '; color: ' + links + '; font-size: 14px; line-height: 28px; position: absolute; right: 0; text-align: center; text-decoration: none; top: 0; width: 30px; }';
  2466. css += '#dialogClose:hover { color: ' + lighten(links, 0.200) + '; }';
  2467. css += '#dialogTitle { background: ' + bg1 + '; ' + gradient(lighten(bg2, 0.066), bg2) + '; border-bottom: 1px solid ' + lighten(bg2, 0.200) + '; color: ' + links + '; display: block; margin: 0; padding: 5px 10px; }';
  2468. css += '#dialogTitle b { color: ' + accent2 + '; }';
  2469. css += '#dialogMessage { display: block; padding: 10px; }';
  2470. css += '#dialogButtons { clear: both; display: block; padding: 10px; text-align: right; }';
  2471.  
  2472. // dialog box content
  2473. css += '#dialogMessage table { margin: 0; }';
  2474. css += '#dialogMessage table th { color: ' + accent1 + '; font-size: 14px; text-align: left; }';
  2475. css += '#dialogMessage table b { color: ' + lighten(accent1, 0.066) + '; }';
  2476. css += '#dialogMessage table td.name { padding-right: 5%; text-align: right; width: 20%; }';
  2477. css += '#dialogMessage table td.key { text-align: center; width: 25%; }';
  2478.  
  2479. // dialog box form elements
  2480. css += '#dialogBox #settingsForm { width: 820px }';
  2481. css += '#dialogBox .error { color: #C43131; font-weight: bold; }';
  2482. css += '#dialogBox button { margin-right: 5px; }';
  2483. css += '#dialogBox fieldset { border: 0; border-bottom: 1px solid ' + lighten(bg2, 0.200) + '; margin: 0 0 15px; padding-bottom: 10px; min-width: 400px; }';
  2484. css += '#dialogBox fieldset:nth-child(1n) { float: left; }';
  2485. css += '#dialogBox fieldset:nth-child(2n) { float: right; }';
  2486. css += '#dialogBox legend { color: ' + accent1 + '; font-weight: bold; margin-bottom: 10px; }';
  2487. css += '#dialogBox label { float: left; line-height: 20px; }';
  2488. css += '#dialogBox label b { color: ' + accent1 + '; }';
  2489. css += '#dialogBox label span.default { font-size: 11px; font-variant: small-caps; }';
  2490. css += '#dialogBox input[type="text"], #dialogBox select { float: right; }';
  2491. css += '#dialogBox input[type="checkbox"] { float: left; margin: 4px 10px 0 ; }';
  2492.  
  2493. // insert the line breaks automatically before returning
  2494. css = css.replace(/}/g, "}\n");
  2495.  
  2496. css += `
  2497. ::-webkit-scrollbar {
  2498. width: 2px;
  2499. height: 2px;
  2500. background-color: transparent;
  2501. }
  2502. ::-webkit-scrollbar-button {
  2503. width: 20px;
  2504. height: 20px;
  2505. }
  2506. ::-webkit-scrollbar-thumb {
  2507. background: rgba(255,255,255,0.6);
  2508. opacity: 0.5;
  2509. border: none;
  2510. }
  2511. ::-webkit-scrollbar-track {
  2512. background: transparent;
  2513. border-radius: 5px;
  2514. }
  2515. ::-webkit-scrollbar-corner {
  2516. background: transparent;
  2517. }
  2518. #content{
  2519. padding: 0;
  2520. top: 0;
  2521. bottom: 0;
  2522. }
  2523. img, .thumbnail, .slideshow, body, #header, #heading, #thumbnails, #logo, h2,p,form{
  2524. border: none !important;
  2525. padding: 0 ;
  2526. margin: 0;
  2527. }
  2528. #logo{
  2529. filter: grayscale(100%);
  2530. opacity: 0.3;
  2531. }
  2532. p{
  2533. opacity: 0.7;
  2534. }
  2535. #thumbnails{
  2536. background-color: transparent;
  2537. }
  2538. a.image{
  2539. max-height: 50px;
  2540. }
  2541. a.thumbnail{
  2542. top: 10;
  2543. margin-right: 1px;
  2544. }
  2545. input, button {
  2546. border: 0px;
  2547. color: grey ;
  2548. }
  2549. #footer {
  2550. padding: 0;
  2551. margin:0;
  2552. height: 20px;
  2553. position: fixed;
  2554. bottom: 0;
  2555. width:150px;
  2556. right: 0;
  2557. background-color: transparent;
  2558. opacity: 0.3;
  2559. border: 0;
  2560. }
  2561. #autoplay{
  2562. background-color: rgba(0,0,0,1);
  2563. height: 20px;
  2564. width: 150px;
  2565. padding: 0;
  2566. margin: 0;
  2567. bottom:10;
  2568. right: 0px;
  2569. color: rgba(255,255,255,0);
  2570. }
  2571. a#control{
  2572. color: rgba(255,255,255,1);
  2573. }
  2574. #autoplay > #counter{
  2575. display: none;
  2576. }
  2577. #info{
  2578. display: none;
  2579. }
  2580. #favorites_container{
  2581. display: none;
  2582. }`
  2583.  
  2584. console.log(css)
  2585.  
  2586. return css
  2587.  
  2588. }