MassiveFap

A complete ImageFap.com gallery conversion script featuring customization options and multiple viewing modes.

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