Booru Downloader + Viewer

The original fullsize images downloader, and viewer for more than 20 booru imageboards

  1. // ==UserScript==
  2. // @name Booru Downloader + Viewer
  3. // @description The original fullsize images downloader, and viewer for more than 20 booru imageboards
  4. // @namespace https://greasyfork.org/users/155308
  5. // @author se7en
  6. // @version 1.1.0
  7. // -------- INCLUDE
  8. // @include *://gelbooru.com/*
  9. // @include *://rule34.xxx/*
  10. // @include *://yande.re/*
  11. // @include *://*.donmai.us/*
  12. // @include *://*.sankakucomplex.com/*
  13. // @include *://behoimi.org/*
  14. // @include *://youhate.us/*
  15. // @include *://safebooru.org/*
  16. // @include *://uberbooru.com/*
  17. // @include *://bronibooru.com/*
  18. // @include *://www.bronibooru.com/*
  19. // @include *://mspabooru.com/*
  20. // @include *://e926.net/*
  21. // @include *://e621.net/*
  22. // @include *://*.booru.org/*
  23. // @include *://atfbooru.ninja/*
  24. // @include *://lolibooru.moe/*
  25. // @include *://hypnohub.net/*
  26. // @include *://tbib.org/*
  27. // @include *://konachan.net/*
  28. // @include *://konachan.com/*
  29. // @include *://rule34.paheal.net/*
  30. // -------- EXCLUDE
  31. // @exclude *://simg3.gelbooru.com*//images/*
  32. // @exclude *://img.rule34.xxx*//images/*
  33. // @exclude *://files.yande.re*/images/*
  34. // @exclude *://files.yande.re*/jpeg/*
  35. // @exclude *://*.donmai.us*/data/*
  36. // @exclude *://*s.sankakucomplex.com*/data/*
  37. // @exclude *://behoimi.org*/data/*
  38. // @exclude *://safebooru.org*//images/*
  39. // @exclude *://uberbooru.com*/data/*
  40. // @exclude *://s3.amazonaws.com*/bronibooru/*
  41. // @exclude *://mspabooru.com*//images/*
  42. // @exclude *://static1.e926.net*/data/*
  43. // @exclude *://static1.e621.net*/data/*
  44. // @exclude *://img.booru.org*/*//images/*
  45. // @exclude *://atfbooru.ninja*/data/*
  46. // @exclude *://lolibooru.moe*/image/*
  47. // @exclude *://hypnohub.net*//data/image/*
  48. // @exclude *://tbib.org*//images/*
  49. // @exclude *://konachan.net*/images/*
  50. // @exclude *://konachan.net*/jpeg/*
  51. // @exclude *://konachan.com*/images/*
  52. // @exclude *://konachan.com*/jpeg/*
  53. // @exclude *://*.paheal.net*/_images/*
  54. // -------- CONNECT
  55. // @connect gelbooru.com
  56. // @connect rule34.xxx
  57. // @connect yande.re
  58. // @connect donmai.us
  59. // @connect sankakucomplex.com
  60. // @connect behoimi.org
  61. // @connect safebooru.org
  62. // @connect uberbooru.com
  63. // @connect s3.amazonaws.com
  64. // @connect bronibooru.com
  65. // @connect mspabooru.org
  66. // @connect e926.net
  67. // @connect e621.net
  68. // @connect booru.org
  69. // @connect atfbooru.ninja
  70. // @connect lolibooru.moe
  71. // @connect hypnohub.net
  72. // @connect tbib.org
  73. // @connect konachan.net
  74. // @connect konachan.com
  75. // @connect paheal.net
  76. // -------- GREASEMONKEY API
  77. // @grant GM_xmlhttpRequest
  78. // @grant GM_setValue
  79. // @grant GM_getValue
  80. // @grant GM_listValues
  81. // @grant GM_deleteValue
  82. // @grant GM_download
  83. // @grant GM_info
  84. // ------ GREASEMONKEY 4.0+ COMPATIBILITY
  85. // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js
  86. // @grant GM.xmlHttpRequest
  87. // @grant GM.setValue
  88. // @grant GM.getValue
  89. // @grant GM.listValues
  90. // @grant GM.deleteValue
  91. // @grant GM.download
  92. // @grant GM.info
  93. // ==/UserScript==
  94.  
  95. /*
  96. 1.1.0
  97. + added "close" button to viewer's navigation bar
  98. * fix userOptions loader (TamperMonkey bug?)
  99. * fix redirect on gelbooru's favorite pages
  100. * disable video preloader
  101. 1.0.6
  102. * fix viewer colors on safebooru.org
  103. * fix url match on donmai.us post page
  104. * fix sankaku viewer
  105. 1.0.4
  106. * fix 'ignoredTags' option: trimmed them
  107. 1.0.3
  108. * fix include's list
  109. 1.0.2
  110. * fix cofiguration data on tampermonkey
  111. 1.0.1
  112. * fix preloader, it fell into infinite recursion, when total amount of images was equal to 1
  113. 1.0.0
  114. + added tabs to user menu:
  115. + General: autoRun, createViewer, downloadJPEG, animateProgress
  116. + Filename: maxTagsInName, tagsOrder, ignoredTags, tagsDelim, addImgBrdName, prefixedName, imgIdAtNameEnd
  117. + Viewer: viewOriginal, viewJPEG, viewFirst, holdCtrl, fixedThumbs, fixedTags
  118. + new user options:
  119. + ignored tag names [''], in the Filename tab:
  120. * underscored tag names, that are not included into file name
  121. + fixed thumb/tag list [false]/[true], in the Viewer tab
  122. * if false, then fade out the thumb/tag list, when mouse is out, and fade in, when mouse is over them, otherwise fixe them
  123. + view original images [false], in the Viewer tab:
  124. * the old option called viewSample is replaced by the viewOriginal keeping backward compatibility
  125. + show progress/status bar [true], in the Viewer tab
  126. + auto hide the viewer's navigation bar
  127. + auto focus on the viewer's thumb/tag list when the mouse cursor is over them, thus, making much easier to start scrolling them
  128. + simple image preloader
  129. * change the default value of the 'holdCtrl' option to [false]
  130. * horizontally, and vertically centered images on the viewer window
  131. * set controls, and loop attributes on the video elements
  132. * restore browser history URL after viewer exit (older versions remain history URL unchanged after manipulations)
  133. * this is sankaku hack: simply replaces the current history URL by the image's post URL to enable the valid image loading
  134. * hide main page scroll bar when viewer activated
  135. * fixed viewer's 1st image thumbnail (post image thumb) on post page
  136. * little refactoring (for further changes)
  137. 0.7.0
  138. + compatibility with Greasemonkey 4.0+
  139. 0.6.0
  140. + advanced viewer:
  141. + tag list on right sidebar
  142. + thumbs on left sidebar
  143. * removed user options: maxWidth, maxHeight
  144. 0.5.0
  145. + image status/progress bar
  146. + user option:
  147. + animate initialization/downloading progress [true]
  148. * fix main button events
  149. 0.4.2
  150. + hotkey:
  151. + 'Shift+A' - download all available images
  152. * fix image source getter
  153. - known issues:
  154. - 'Download Mode', and 'Download All' buttons don't work on post page of rule34.xxx, use hotkeys instead
  155. 0.4.1
  156. * fix exclude-list typo
  157. * fix konachan jpeg images extension
  158. 0.4.0
  159. + supported imageboards:
  160. + atfbooru
  161. + lolibooru
  162. + hypnohub
  163. + tbib
  164. + konachan
  165. + paheal
  166. * change name 'rule34' to 'rule34.xxx'
  167. * fix bug on post page due to an empty viewer div
  168. 0.3.2
  169. + user option:
  170. + tag-types order in file name ['character', 'copyright', 'artist', 'species', 'model', 'idol', 'photo_set', 'circle', 'medium', 'metadata', 'general', 'faults']
  171. 0.3.1
  172. + user options:
  173. + hold Ctrl key to left/right navigate when viewing [true]
  174. + maximum width of image, px [1000]
  175. + maximum height of image, px [700]
  176. * little changes
  177. 0.3.0
  178. + simple image viewer
  179. + user options:
  180. + create image viewer - [true]
  181. + view image sample - [true]
  182. + view jpeg image (yande.re option) - [false]
  183. + view 1st image on viewer activation - [true]
  184. + hotkeys:
  185. + 'Shift+V' - switch viewer on/off
  186. + 'Ctrl+left/right' arrows - view previous/next image
  187. + viewer buttons:
  188. + Prev
  189. + Source - open image file in a new tab
  190. + Number - index of the current image
  191. + Download
  192. + Next
  193. + @connect meta-data (to silence tampermonkey)
  194. * fix wrong image hostname for uberbooru
  195. 0.2.7
  196. * scrollable content of user menu window
  197. * user menu window's size fitted to client's size
  198. * move user menu 'close' button to the top right of the menu window (x sign)
  199. * other little change
  200. 0.2.5
  201. * fix typos
  202. 0.2.4
  203. + user option:
  204. + Image ID, and ImageBoard name at the end of the file name [true]
  205. + dynamically rename images on user options change
  206. 0.2.3
  207. * fix image extensions on tampermonkey
  208. 0.2.2
  209. * bugfixes
  210. * little changes
  211. 0.2.0
  212. + image downloader for imageboards:
  213. + youhate.us
  214. + safebooru
  215. + uberbooru
  216. + bronibooru
  217. + mspabooru
  218. + e926/e621
  219. + *.booru.org
  220. + user option:
  221. + prefixed imageboard name [false]
  222. 0.1.1
  223. + user option:
  224. + auto initialize the script [true]
  225. + hotkey:
  226. + 'Shift+M' - open/close user menu dialog
  227. * little changes
  228. 0.1.0
  229. + user menu
  230. 0.0.13
  231. * refactoring
  232. * fix button events
  233. 0.0.10
  234. + behoimi downloader
  235. 0.0.9
  236. + hotkey:
  237. + 'Shift+I' - (re)initializes imageBoard (usefull for the imageboards with auto paging)
  238. * fix yande.re jpeg image extension
  239. 0.0.8
  240. + sankaku downloader
  241. + chan.sankakucomplex.com
  242. + idol.sankakucomplex.com
  243. 0.0.7
  244. + hotkey:
  245. + 'Shift+D' - toggle the Download Mode on/off
  246. + donmai downloader
  247. + safebooru.donmai.us
  248. + danbooru.donmai.us
  249. + sonohara.donmai.us
  250. + hijiribe.donmai.us
  251. 0.0.6
  252. + yande.re downloader
  253. + user option:
  254. + download jpeg image on yande.re [false]
  255. 0.0.5
  256. + rule34 downloader
  257. + user option:
  258. + add the imageboard name to the image name [true]
  259. 0.0.3
  260. + gelbooru downloader
  261. + user options:
  262. + maximum tags in the image name [10]
  263. + tags delimeter in the image name ['-']
  264. */
  265. if( window.self !== window.top )
  266. return;
  267. var RANDOM = '1681238';//Math.floor(Math.random()*1e6 + 1e6);
  268. var DEBUG = false;
  269. console.log('start ' + GM.info.script.name + ' v' + GM.info.script.version + '..');
  270. (async function(){
  271. function consoleLog(){window.console.log.apply(this, arguments);}
  272. function blank(){}
  273. var clog = (DEBUG ? consoleLog : blank);
  274. var userOptions = await initOptions(),
  275. methodsObject = initMethodsObject(),
  276. imageBoard = initImageBoard();
  277. newCssClasses();
  278. //------------------------------------------------------------------------------------//
  279. //------------------------------------ IMAGE BOARD -----------------------------------//
  280. function initImageBoard( d )
  281. {
  282. /*
  283. var elmClass = initImageBoardClasses(d),
  284. elmData = initImageBoardDataset(d),
  285. siteList = initSiteList(),
  286. download = initImageBoardDownloader(d),
  287. userMenu = initUserMenu(),
  288. view = initImageBoardViewer(d),
  289. state = {'viewMode': false, 'userMenu': false, 'downloadMode': false},
  290. divID = 'image-board-div-' + RANDOM;
  291. */
  292. var imgBrdCl = initImageBoardClasses(d),
  293. imgBrdDt = initImageBoardDataset(d),
  294. siteList = initSiteList(),
  295. imgBrdDw = initImageBoardDownloader(d),
  296. userMenu = initUserMenu(),
  297. imgBrdVw = initImageBoardViewer(d),
  298. imgBrdSt = {'viewMode': false, 'userMenu': false, 'downloadMode': false},
  299. imgBrdId = 'image-board-div-' + RANDOM;
  300. var retVal = {
  301. get siteList(){return siteList;},
  302. get imgBrdCl(){return imgBrdCl;},
  303. get imgBrdDt(){return imgBrdDt;},
  304. get imgBrdId(){return imgBrdId;},
  305. get imgBrdDw(){return imgBrdDw;},
  306. get userMenu(){return userMenu;},
  307. get imgBrdVw(){return imgBrdVw;},
  308. get imgBrdSt(){return imgBrdSt;},
  309. get images(){return this.data.images;},
  310. get downloader(){return this.data.downloader;},
  311. get viewer(){return this.data.viewer;},
  312. data: {
  313. 'images': {
  314. list: null,
  315. init: function( doc, type ){
  316. clog("imageBoard init..");
  317. siteList.init(type);
  318. imgBrdDt.init(doc);
  319. imgBrdCl.init(doc);
  320. this.list = this.list || [];
  321. this.doc = doc || document;
  322. var siteObj = siteList.val(type),
  323. isPost = siteObj.isPost(),
  324. imgD;
  325. if( isPost )
  326. {
  327. var img = siteObj.getPostImage();
  328. if( img && !imgBrdCl.hasClass( img, 'counted') )
  329. imgD = this.addNewImage( img, isPost, siteObj );
  330. }
  331. var thumbs = siteObj.getImageThumbs( this.doc ),
  332. _3ParentTypes = ['yande.re', 'lolibooru', 'hypnohub', 'konachan'],
  333. name = siteObj.name,
  334. num = (_3ParentTypes.indexOf(name) != -1 ? 3 : 2);
  335. clog("thumbs.length: ", thumbs.length);
  336. for( var i = 0, len = thumbs.length, thumb, par, h; i < len; ++i )
  337. {
  338. thumb = thumbs[i];
  339. if( imgBrdCl.hasClass( thumb, 'counted' ) )
  340. continue;
  341. imgD = this.addNewImage( thumb, false, siteObj );
  342. par = parent( thumb, num );
  343. par.appendChild( this.createProgressBar(imgD.index) );
  344. if( par.tagName === 'ARTICLE' )
  345. {
  346. try{
  347. h = par.style.height;
  348. h = parseInt(h.match(/\d+/)[0], 10);
  349. h += 15;
  350. h += 'px';
  351. }catch(er){
  352. console.error(er);
  353. h = null;
  354. }
  355. par.style.height = h || '170px';
  356. }
  357. }
  358. },
  359. addNewImage: function( img, isPost, siteObj ){
  360. this.list.push({});
  361. var imgD = last(this.list), pdiv;
  362. imgD.state = 'empty';
  363. imgD.index = this.list.length - 1;
  364. imgD.type = siteObj.name;
  365. if( isPost )
  366. {
  367. imgD.postId = siteObj.getPostId();
  368. imgD.postUrl = window.location.href;
  369. siteObj.setImageDataDoc(imgD);
  370. pdiv = this.createProgressBar(imgD.index);
  371. if( img.parentNode.tagName != 'A' )
  372. img.parentNode.insertBefore(pdiv, img.nextSibling);
  373. else
  374. img.parentNode.parentNode.appendChild(pdiv);
  375. }else
  376. siteObj.setImageDataThumb( imgD, img );
  377. imgBrdDt.val( img, 'index', imgD.index);
  378. imgBrdCl.addClass( img, 'counted' );
  379. if( imgD.state === 'ready' )
  380. {
  381. siteObj.createDiv( imgBrdId, this.doc);
  382. imgBrdDw.init(imgBrdId, this.doc);
  383. setReadyImage( imgD, imgBrdCl, imgBrdDt, imgBrdDw, imgBrdVw );
  384. }
  385. return imgD;
  386. },
  387. createProgressBar: function(index ){
  388. var div = document.createElement('div'),
  389. html = '<div id="progress-stripe-' + index + '" ' +
  390. 'class="progress-stripe progress-counted"></div>';
  391. div.setAttribute('class', 'progress-bar');
  392. div.insertAdjacentHTML('beforeend', html);
  393. return div;
  394. },
  395. getEmpty: function(){
  396. var empty = [];
  397. for( var i = 0; i < this.list.length; ++i )
  398. {
  399. if( this.list[i].state === 'empty' )
  400. empty.push(i);
  401. }
  402. return empty;
  403. },
  404. fix: function()
  405. {
  406. var empty = this.getEmpty(), animate = userOptions.val('animateProgress');
  407. clog("fix start..", empty.length);
  408. for( var i = 0, idx, imgD; i < empty.length; ++i )
  409. {
  410. idx = empty[i];
  411. imgD = this.list[idx];
  412. imgD.state = 'busy';
  413. this.getImageData(imgD, animate);
  414. }
  415. },
  416. getImageData: function(imgD, animate)
  417. {
  418. if( siteList.needXHR(imgD.type) )
  419. {
  420. if( animate )
  421. addClass(document.querySelectorAll('#progress-stripe-' + imgD.index), 'progress-animated');
  422. GM.xmlHttpRequest({
  423. url: imgD.postUrl,
  424. method: 'GET',
  425. context: {
  426. 'index': imgD.index,
  427. 'url': imgD.postUrl,
  428. },
  429. onload: xhrImageData,
  430. });
  431. }else{
  432. console.log("TODO :D");
  433. var siteObj = siteList.val(imgD.type);
  434. //siteObj.setImageDataFull(imgD);// TODO (yande.re, donmai)
  435. }
  436. },
  437. },
  438. 'downloader': {
  439. init: function(doc, type){
  440. clog("downloader init..");
  441. siteList.init(type);
  442. var siteObj = siteList.val(type);
  443. siteObj.createDiv( imgBrdId, doc);
  444. imgBrdDw.init(imgBrdId, doc);
  445. },
  446. isActive: function(){
  447. //return imgBrdDw && imgBrdDw.isActive() || false;
  448. return imgBrdSt.downloadMode;
  449. },
  450. activateImage: function(thumb){
  451. if( !thumb )
  452. return;
  453. var a = thumb.parentNode;
  454. if( !imgBrdCl.hasClass(thumb, 'ready' ) )
  455. return;
  456. else if( !imgBrdCl.hasClass( a, 'downloadAttach' ) )
  457. {
  458. a.addEventListener('click', handleDownloadEvent, false);
  459. imgBrdCl.addClass( a, 'downloadAttach' );
  460. }
  461. imgBrdCl.addClass( a, 'downloadActive' );
  462. },
  463. activate: function(doc){
  464. clog("[downloader] activate");
  465. doc = doc || document;
  466. imgBrdCl.init(doc);
  467. var thumbs = imgBrdCl.queryAll('counted');
  468. for( var i = 0, len = thumbs.length; i < len; ++i )
  469. this.activateImage( thumbs[i] );
  470. imgBrdDw.downloadOn();
  471. imgBrdSt.downloadMode = true;
  472. },
  473. deactivate: function(doc){
  474. clog("[downloader] deactivate");
  475. doc = doc || document;
  476. imgBrdCl.init(doc);
  477. var activ = imgBrdCl.queryAll('downloadActive');
  478. clog("active.length: ", activ.length);
  479. for( var i = 0, len = activ.length; i < len; ++i )
  480. imgBrdCl.removeClass( activ[i], 'downloadActive' );
  481. imgBrdDw.downloadOff();
  482. imgBrdSt.downloadMode = false;
  483. },
  484. downloadAll: function(){
  485. imgBrdDw.downloadAll.click();// =)
  486. },
  487. },
  488. 'userMenu': {
  489. init: function(doc, type){
  490. clog("userMenu init..");
  491. siteList.init(type);
  492. var siteObj = siteList.val(type);
  493. siteObj.createDiv( imgBrdId, doc);
  494. userMenu.init(imgBrdId, doc);
  495. },
  496. },
  497. 'keyboard': {
  498. val: null,
  499. init: function(){
  500. if( !this.isActive )
  501. this.activate();
  502. },
  503. get isActive(){ return !!this.val;},
  504. activate: function(){
  505. activateKeyboard();
  506. this.val = true;
  507. },
  508. deactivate: function(){
  509. deactivateKeyboard();
  510. this.val = false;
  511. },
  512. },
  513. 'viewer': {
  514. init: function(doc, type){
  515. clog("viewer init..");
  516. siteList.init(type);
  517. var siteObj = siteList.val(type);
  518. siteObj.createDiv( imgBrdId, doc);
  519. imgBrdVw.init(imgBrdId, doc, siteObj.viewDivInsertionPlace);
  520. },
  521. activateImage: function( thumb ){
  522. if( !thumb )
  523. return;
  524. var a = thumb.parentNode;
  525. if( !imgBrdCl.hasClass(thumb, 'ready' ) )
  526. return;
  527. else if( !imgBrdCl.hasClass( a, 'viewAttach' ) )
  528. {
  529. a.addEventListener('click', handleViewerEvent, false);
  530. imgBrdCl.addClass( a, 'viewAttach' );
  531. }
  532. imgBrdCl.addClass( a, 'viewActive' );
  533. },
  534. activate: function(doc){
  535. clog("viewer activate");
  536. doc = doc || document;
  537. imgBrdCl.init(doc);
  538. var thumbs = imgBrdCl.queryAll('counted');
  539. for( var i = 0, len = thumbs.length; i < len; ++i )
  540. this.activateImage( thumbs[i] );
  541. imgBrdVw.viewerOn();
  542. imgBrdSt.viewMode = true;
  543. },
  544. deactivate: function(doc){
  545. clog("viewer deactivate");
  546. doc = doc || document;
  547. imgBrdCl.init(doc);
  548. var activ = imgBrdCl.queryAll('viewActive');
  549. clog("active.length: ", activ.length);
  550. for( var i = 0, len = activ.length; i < len; ++i )
  551. imgBrdCl.removeClass( activ[i], 'viewActive' );
  552. imgBrdVw.viewerOff();
  553. imgBrdSt.viewMode = false;
  554. },
  555. isActive: function(){
  556. //return imgBrdVw.isActive();
  557. return imgBrdSt.viewMode;
  558. },
  559. },
  560. },
  561. init: function(doc){
  562. for( var key in this.data )
  563. this.data[key].init(doc);
  564. },
  565. fix: function(){
  566. this.data.images.fix();
  567. },
  568. initDiv: function(doc){
  569. doc = doc || document;
  570. var div = doc.querySelector('#' + imgBrdId),
  571. siteObj = siteList.val();
  572. if( !div )
  573. div = siteObj.createDiv(imgBrdId);
  574. if( !hasClass(div, 'image-board-div-activated') )
  575. {
  576. div.addEventListener('click', handleImageBoardEvent, false);
  577. addClass(div, 'image-board-div-activated');
  578. }
  579. },
  580. };
  581. retVal.init(d);
  582. setTimeout(function(){retVal.initDiv(d);}, 100);
  583. if( userOptions.val('autoRun') )
  584. retVal.fix();
  585. return retVal;
  586. }
  587. function handleImageBoardEvent(event)
  588. {
  589. var t = event.target,
  590. dId = 'image-board-download-switch-' + RANDOM,
  591. aId = 'image-board-download-all-' + RANDOM,
  592. vId = 'image-board-viewer-button-' + RANDOM,
  593. mId = 'image-board-user-menu-id-' + RANDOM;
  594. if( t.tagName === 'SPAN' )
  595. t = t.parentNode;
  596. if( t.tagName !== 'BUTTON' )
  597. return;
  598. else if( t.id === dId )
  599. {
  600. handleDownloadSwitchEvent();
  601. }
  602. else if( t.id === aId )
  603. {
  604. handleDownloadAllEvent();
  605. }
  606. else if( t.id === vId )
  607. {
  608. handleViewerSwitchEvent();
  609. }
  610. else if( t.id === mId )
  611. {
  612. handleUserMenuEvent();
  613. }else
  614. console.error("unknown element: ", t);
  615. }
  616. //------------------------------------ IMAGE BOARD -----------------------------------//
  617. //------------------------------------------------------------------------------------//
  618. //----------------------------------- XRH IMAGE DATA ---------------------------------//
  619. function xhrImageData(xhr)
  620. {
  621. var imgD = imageBoard.images.list[xhr.context.index];
  622. if( xhr.status !== 200 )
  623. {
  624. var context = xhr.context;
  625. console.error("xhr.status: ", xhr.status, xhr.statusText );
  626. console.error("index: ", context ? context.index : null);
  627. console.error("postUrl: ", context && context.url || null );
  628. if( imgD.state !== 'ready' )
  629. imgD.state = 'empty';
  630. removeClass( document.querySelectorAll('#progress-stripe-' + context.index), 'progress-animated' );
  631. return;
  632. }
  633. if( !imgD || imgD.state === 'ready' )
  634. {
  635. console.error("invalid context: ", imgD);
  636. return;
  637. }
  638. var siteObj = imageBoard.siteList.val(imgD.type);
  639. if( !siteObj )
  640. {
  641. console.error("invalid site type: ", imgD.type);
  642. return;
  643. }
  644. var doc = document.implementation.createHTMLDocument("");
  645. doc.documentElement.innerHTML = xhr.response;
  646. siteObj.setImageDataDoc(imgD, doc);
  647. clog("xhrImageData[" + imgD.index + "].state : " + imgD.state);
  648. if( imgD.state === 'ready' )
  649. {
  650. setReadyImage( imgD );
  651. }
  652. }
  653. function setReadyImage( imgD, imgBrdCl, imgBrdDt, imgBrdDw, imgBrdVw )
  654. {
  655. if( (!imgBrdCl || !imgBrdDt || !imgBrdDw || !imgBrdVw) && imageBoard )
  656. {
  657. imgBrdCl = imageBoard.imgBrdCl;
  658. imgBrdDt = imageBoard.imgBrdDt;
  659. imgBrdDw = imageBoard.imgBrdDw;
  660. imgBrdVw = imageBoard.imgBrdVw;
  661. }
  662. var thumb = imgBrdDt.query('index', imgD.index + ''),
  663. stripe = document.querySelectorAll('#progress-stripe-' + imgD.index);
  664. addClass(stripe, 'image-ready');
  665. removeClass(stripe, 'progress-animated');
  666. imgBrdCl.addClass( thumb, 'ready' );
  667. imgBrdDt.val( thumb, 'source', imgD.source );
  668. if( imgD.bytes ) imgBrdDt.val( thumb, 'bytes', imgD.bytes );
  669. imgBrdDw.total += 1;
  670. imgBrdVw.total += 1;
  671. clog("name: " + imgD.name);
  672. if( imageBoard )
  673. {
  674. if( imageBoard.downloader.isActive() )
  675. imageBoard.downloader.activateImage( thumb );
  676. if( imageBoard.viewer.isActive() )
  677. imageBoard.viewer.activateImage( thumb );
  678. }
  679. }
  680. //----------------------------------- XRH IMAGE DATA ---------------------------------//
  681. //------------------------------------------------------------------------------------//
  682. //------------------------------------- SITE LIST ------------------------------------//
  683. function initSiteList()
  684. {
  685. var retVal = {
  686. settings: {
  687. 'gelbooru': getGelbooruSettings,
  688. 'rule34.xxx': getRule34Settings,
  689. 'yande.re': getYandereSettings,
  690. 'donmai': getDonmaiSettings,
  691. 'sankaku': getSankakuSettings,
  692. 'behoimi': getBehoimiSettings,
  693. 'youhate': getGelbooruSettings,
  694. 'safebooru': getSafebooruSettings,
  695. 'uberbooru': getUberbooruSettings,
  696. 'bronibooru': getBronibooruSettings,
  697. 'mspabooru': getMspabooruSettings,
  698. 'e926.net': getE926netSettings,
  699. 'e621.net': getE621netSettings,
  700. '.booru.org': getBooruorgSettings,
  701. 'atfbooru': getAtfbooruSettings,
  702. 'lolibooru': getLolibooruSettings,
  703. 'hypnohub': getHypnohubSettings,
  704. 'tbib': getTbibSettings,
  705. 'konachan': getKonachanSettings,
  706. 'paheal.net': getPahealSettings,
  707. },
  708. data: null,
  709. get: function( type, prop1, prop2 ){
  710. var obj;
  711. if( !type )
  712. obj = this.currentObj;
  713. else{
  714. this.data[type].init();
  715. obj = this.data[type];
  716. }
  717. return nodeWalk.call( obj, prop1, prop2 );
  718. },
  719. style: function(type){
  720. return this.get( type, 'style' );
  721. },
  722. val: function(type){
  723. return this.get( type, 'val' );
  724. },
  725. needXHR: function(type){
  726. return this.get( type, 'needXHR' );
  727. },
  728. init: function(type, prefix){
  729. if( !this.data )
  730. {
  731. this.data = {};
  732. for( var key in this.settings )
  733. this.data[key] = getSiteObject( key, this.settings[key], prefix );
  734. }
  735. if( !type )
  736. this.initCurrent();
  737. else if( this.data[type] )
  738. this.data[type].init();
  739. },
  740. getSiteType: function(url){
  741. url = url || window.location.href;
  742. for( var key in this.data )
  743. {
  744. if( this.data[key].regexp.test(url) )
  745. return key;
  746. }
  747. console.error("no site object found for this host");
  748. return null;
  749. },
  750. initCurrent: function(){
  751. if( !this.currentObj )
  752. {
  753. var type = this.getSiteType();
  754. if( !type )
  755. return;
  756. this.currentObj = this.data[type];
  757. }
  758. this.currentObj.init();
  759. },
  760. };
  761. retVal.init();
  762. clog("siteList.current: ", retVal.val());
  763. return retVal;
  764. }
  765. //------------------------------------- SITE LIST ------------------------------------//
  766. //------------------------------------------------------------------------------------//
  767. //------------------------------------- SITE OBJECT ----------------------------------//
  768. function getSiteObject( siteName, getSiteSettings, prefix )
  769. {
  770. return {
  771. val: null,
  772. name: siteName,
  773. regexp: new RegExp( siteName ),
  774. get needXHR(){return this.val.needXHR;},
  775. get style(){return this.val.style;},
  776. get settings(){
  777. var s = ( typeof getSiteSettings === 'function' ? getSiteSettings(prefix) : null);
  778. Object.defineProperty( this, 'settings', {
  779. get: function(){return s;},
  780. enumerable: true,
  781. configurable: true,
  782. });
  783. return s;
  784. },
  785. init: function(){
  786. this.val = this.val || initSiteObject( this.settings );
  787. },
  788. };
  789. }
  790. function initSiteObject( siteSettings )
  791. {
  792. var retVal = {
  793. data: null,
  794. get name(){ return this.data.name; },
  795. get prefixedName(){
  796. var prefix = this.prefix,
  797. name = this.shortName;
  798. if( prefix )
  799. name = prefix + name;
  800. Object.defineProperty( this, 'prefixedName', {
  801. get: function(){return name;},
  802. enumerable: true,
  803. configurable: true,
  804. });
  805. return name;
  806. },
  807. get prefix(){return this.data.prefix; },
  808. get shortName(){
  809. var name = this.name.replace(/^\./, '');
  810. Object.defineProperty( this, 'shortName', {
  811. get: function(){return name;},
  812. enumerable: true,
  813. configurable: true,
  814. });
  815. return name;
  816. },
  817. get hostname(){return this.data.hostname; },
  818. get imageHostname(){return this.data.imageHostname;},
  819. get imageDir(){return this.data.imageDir; },
  820. get style(){return this.data.style;},
  821. get postDivInsertionPlace(){return this.data.postDivInsertionPlace;},
  822. get listDivInsertionPlace(){return this.data.listDivInsertionPlace;},
  823. get viewDivInsertionPlace(){return this.data.viewDivInsertionPlace;},
  824. get methodsMap(){return this.data.methodsMap;},
  825. get needXHR(){return (typeof this.data.needXHR === 'boolean' ? this.data.needXHR : true);},
  826. init: function( settings ){
  827. this.data = this.data || settings;
  828. if( !this.data )
  829. {
  830. console.error("[initSiteObject] can't init siteObject, invalid data: ", this.data);
  831. return;
  832. }
  833. for( var i = 0, len = methodsObject.list.length, name, type, map = this.methodsMap || {}; i < len; ++i )
  834. {
  835. name = methodsObject.list[i];
  836. type = map[name] || 'booru';
  837. if( typeof methodsObject.method(type, name) === 'function' )
  838. this[name] = methodsObject.method(type, name);
  839. }
  840. },
  841. };
  842. retVal.init( siteSettings );
  843. return retVal;
  844. }
  845. //------------------------------------------------------------------------------------//
  846. //-------------------------------------- GELBOORU ------------------------------------//
  847. function getGelbooruSettings()
  848. {
  849. return {
  850. name: 'gelbooru',
  851. hostname: 'gelbooru.com',
  852. imageDir: '/images',
  853. imageHostname: 'simg3.gelbooru.com',
  854. postDivInsertionPlace: '#image',
  855. listDivInsertionPlace: '.contain-push',
  856. viewDivInsertionPlace: '.padding15',
  857. style: {
  858. color: '#fff',
  859. width: '180px',
  860. background: '#0773fb',
  861. backgroundHover: '#fbb307',
  862. colorHover: '#fff',
  863. backgroundView: '#fff',
  864. },
  865. methodsMap: {
  866. isPost: 'gelbooru',
  867. getPostId: 'gelbooru',
  868. getPostUrl: 'gelbooru',
  869. },
  870. needXHR: true,
  871. };
  872. }
  873. //------------------------------------------------------------------------------------//
  874. //--------------------------------------- RULE34 -------------------------------------//
  875. function getRule34Settings()
  876. {
  877. return {
  878. name: 'rule34.xxx',
  879. hostname: 'rule34.xxx',
  880. imageDir: '/images',
  881. imageHostname: 'img.rule34.xxx',
  882. postDivInsertionPlace: '#image',
  883. listDivInsertionPlace: 'div.content',
  884. viewDivInsertionPlace: 'div#post-list',
  885. style: {
  886. color: '#fff',
  887. width: '180px',
  888. background: '#84AE83',
  889. backgroundHover: '#A4CEA3',
  890. colorHover: '#fff',
  891. },
  892. methodsMap: {
  893. isPost: 'gelbooru',
  894. getPostId: 'gelbooru',
  895. getPostUrl: 'gelbooru',
  896. },
  897. needXHR: true,
  898. };
  899. }
  900. //------------------------------------------------------------------------------------//
  901. //------------------------------------- YANDE.RE -------------------------------------//
  902. function getYandereSettings()
  903. {
  904. return {
  905. name: 'yande.re',
  906. hostname: 'yande.re',
  907. imageDir: 'image',
  908. imageHostname: 'files.yande.re',
  909. postDivInsertionPlace: '#image',
  910. listDivInsertionPlace: 'div.content',
  911. viewDivInsertionPlace: 'div#post-list',
  912. style: {
  913. color: '#ee8887',
  914. width: '180px',
  915. background: '#222',
  916. backgroundHover: '#444',
  917. colorHover: '#ee8887',
  918. },
  919. methodsMap: null,
  920. needXHR: true,
  921. };
  922. }
  923. //------------------------------------------------------------------------------------//
  924. //-------------------------------------- DONMAI --------------------------------------//
  925. function getDonmaiSettings( prefix )
  926. {
  927. var prefixList = ['safebooru.', 'danbooru.', 'sonohara.', 'hijiribe.'],
  928. hostnameSuffix = 'donmai.us';
  929. prefix = getHostnamePrefix( hostnameSuffix, prefixList, prefix );
  930. var hostname = prefix + hostnameSuffix;
  931. return {
  932. name: 'donmai',
  933. prefix: prefix,
  934. hostname: hostname,
  935. imageHostname: hostname,
  936. imageDir: 'data',
  937. postDivInsertionPlace: '#image',
  938. listDivInsertionPlace: '#posts',
  939. viewDivInsertionPlace: '#page', //'#c-posts',
  940. style: {
  941. color: '#0073ff',
  942. width: '180px',
  943. background: '#f5f5ff',
  944. backgroundHover: '#f5f5ff',
  945. colorHover: '#80b9ff',
  946. },
  947. methodsMap: {
  948. isPost: 'donmai',
  949. getPostId: 'donmai',
  950. getPostUrl: 'donmai',
  951. },
  952. needXHR: true,
  953. };
  954. }
  955. //------------------------------------------------------------------------------------//
  956. //-------------------------------------- SANKAKU -------------------------------------//
  957. function getSankakuSettings(prefix)
  958. {
  959. var prefixList = ['chan.', 'idol.'],
  960. hostnameSuffix = 'sankakucomplex.com';
  961. prefix = getHostnamePrefix( hostnameSuffix, prefixList, prefix );
  962. var hostname = prefix + hostnameSuffix,
  963. imageHostnamePrefix = (prefix ? prefix[0] + 's.' : '');
  964. return {
  965. name: 'sankaku',
  966. prefix: prefix,
  967. hostname: hostname,
  968. imageHostname: imageHostnamePrefix + hostnameSuffix,
  969. imageDir: 'data',
  970. postDivInsertionPlace: '#image',
  971. listDivInsertionPlace: '#content',
  972. viewDivInsertionPlace: '#content',
  973. style: {
  974. color: '#ff761c',
  975. width: '180px',
  976. background: '',
  977. backgroundHover: '',
  978. colorHover: '#666',
  979. },
  980. methodsMap: null,
  981. needXHR: true,
  982. };
  983. }
  984. //------------------------------------------------------------------------------------//
  985. //-------------------------------------- BEHOIMI -------------------------------------//
  986. function getBehoimiSettings()
  987. {
  988. return {
  989. name: 'behoimi',
  990. hostname: 'behoimi.org',
  991. imageHostname: 'behoimi.org',
  992. imageDir: 'data',
  993. postDivInsertionPlace: '#image',
  994. listDivInsertionPlace: 'div.content',
  995. viewDivInsertionPlace: 'div#post-list',
  996. style: {
  997. color: '#43333f',
  998. width: '180px',
  999. background: '',
  1000. backgroundHover: '',
  1001. colorHover: '#354d99',
  1002. },
  1003. methodsMap: null,
  1004. needXHR: true,
  1005. };
  1006. }
  1007. //------------------------------------------------------------------------------------//
  1008. //-------------------------------------- SAFEBOORU -----------------------------------//
  1009. function getSafebooruSettings()
  1010. {
  1011. return {
  1012. name: 'safebooru',
  1013. hostname: 'safebooru.org',
  1014. imageHostname: 'safebooru.org',
  1015. imageDir: '/images',
  1016. postDivInsertionPlace: '#image',
  1017. listDivInsertionPlace: 'div.content',
  1018. viewDivInsertionPlace: 'div#post-list',
  1019. style: {
  1020. color: '#fff',
  1021. width: '180px',
  1022. background: '#006ffa',
  1023. backgroundHover: '#006ffa',
  1024. colorHover: '#33cfff',
  1025. },
  1026. methodsMap: {
  1027. isPost: 'gelbooru',
  1028. getPostId: 'gelbooru',
  1029. getPostUrl: 'gelbooru',
  1030. },
  1031. needXHR: true,
  1032. };
  1033. }
  1034. //------------------------------------------------------------------------------------//
  1035. //-------------------------------------- UBERBOORU -----------------------------------//
  1036. function getUberbooruSettings()
  1037. {
  1038. return {
  1039. name: 'uberbooru',
  1040. hostname: 'uberbooru.com',
  1041. imageHostname: 'uberbooru.com',
  1042. imageDir: 'data',
  1043. postDivInsertionPlace: '#image',
  1044. listDivInsertionPlace: '#posts',
  1045. viewDivInsertionPlace: 'div#page', // 'div#c-posts',
  1046. style: {
  1047. color: '#000',
  1048. width: '180px',
  1049. background: '#e6e6e6',
  1050. backgroundHover: '#e6e6e6',
  1051. colorHover: '#008',
  1052. },
  1053. methodsMap: {
  1054. isPost: 'donmai',
  1055. getPostId: 'donmai',
  1056. getPostUrl: 'donmai',
  1057. },
  1058. needXHR: true,
  1059. };
  1060. }
  1061. //------------------------------------------------------------------------------------//
  1062. //------------------------------------- BRONIBOORU -----------------------------------//
  1063. function getBronibooruSettings()
  1064. {
  1065. return {
  1066. name: 'bronibooru',
  1067. hostname: 'bronibooru.com',
  1068. imageHostname: 's3.amazonaws.com',
  1069. imageDir: 'bronibooru',
  1070. postDivInsertionPlace: '#image',
  1071. listDivInsertionPlace: '#posts',
  1072. viewDivInsertionPlace: 'div#page', // 'div#c-posts',
  1073. style: {
  1074. color: '#0073ff',
  1075. width: '180px',
  1076. background: '#f7f7ff',
  1077. backgroundHover: '#f7f7ff',
  1078. colorHover: '#80b9ff',
  1079. },
  1080. methodsMap: {
  1081. isPost: 'donmai',
  1082. getPostId: 'donmai',
  1083. getPostUrl: 'donmai',
  1084. },
  1085. needXHR: true,
  1086. };
  1087. }
  1088. //------------------------------------------------------------------------------------//
  1089. //-------------------------------------- MSPABOORU -----------------------------------//
  1090. function getMspabooruSettings()
  1091. {
  1092. return {
  1093. name: 'mspabooru',
  1094. hostname: 'mspabooru.com',
  1095. imageHostname: 'mspabooru.com',
  1096. imageDir: '/images',
  1097. postDivInsertionPlace: '#image',
  1098. listDivInsertionPlace: 'div.content',
  1099. viewDivInsertionPlace: 'div#post-list', // 'div#content',
  1100. style: {
  1101. color: '#fff',
  1102. width: '180px',
  1103. background: '#006ffa',
  1104. backgroundHover: '#006ffa',
  1105. colorHover: '#33cfff',
  1106. },
  1107. methodsMap: {
  1108. isPost: 'gelbooru',
  1109. getPostId: 'gelbooru',
  1110. getPostUrl: 'gelbooru',
  1111. },
  1112. needXHR: true,
  1113. };
  1114. }
  1115. //------------------------------------------------------------------------------------//
  1116. //--------------------------------------- E926NET ------------------------------------//
  1117. function getE926netSettings()
  1118. {
  1119. return {
  1120. name: 'e926.net',
  1121. hostname: 'e926.net',
  1122. imageHostname: 'static1.e926.net',
  1123. imageDir: 'data',
  1124. postDivInsertionPlace: '#image',
  1125. listDivInsertionPlace: 'div.content-post',
  1126. viewDivInsertionPlace: 'div#content', // 'div#post-list',
  1127. style: {
  1128. color: '#fff',
  1129. width: '180px',
  1130. background: '#152f56',
  1131. backgroundHover: '#152f56',
  1132. colorHover: '#2e76b4',
  1133. },
  1134. methodsMap: null,
  1135. needXHR: true,
  1136. };
  1137. }
  1138. //------------------------------------------------------------------------------------//
  1139. //--------------------------------------- E621NET ------------------------------------//
  1140. function getE621netSettings()
  1141. {
  1142. return {
  1143. name: 'e621.net',
  1144. hostname: 'e621.net',
  1145. imageHostname: 'static1.e621.net',
  1146. imageDir: 'data',
  1147. postDivInsertionPlace: '#image',
  1148. listDivInsertionPlace: 'div.content-post',
  1149. viewDivInsertionPlace: 'div#content', // 'div#post-list',
  1150. style: {
  1151. color: '#fff',
  1152. width: '180px',
  1153. background: '#152f56',
  1154. backgroundHover: '#152f56',
  1155. colorHover: '#2e76b4',
  1156. },
  1157. methodsMap: null,
  1158. needXHR: true,
  1159. };
  1160. }
  1161. //------------------------------------------------------------------------------------//
  1162. //--------------------------------------- *.BOORU ------------------------------------//
  1163. function getBooruorgSettings(prefix)
  1164. {
  1165. var prefixList = [], hostnameSuffix = 'booru.org';
  1166. prefix = getHostnamePrefix( hostnameSuffix, prefixList, prefix );
  1167. var hostname = prefix + hostnameSuffix;
  1168. return {
  1169. name: '.booru.org',
  1170. prefix: prefix,
  1171. hostname: hostname,
  1172. imageHostname: 'img.booru.org',
  1173. imageDir: prefix + '//images',
  1174. postDivInsertionPlace: '#image',
  1175. listDivInsertionPlace: 'div.content',
  1176. viewDivInsertionPlace: 'div#content', // 'div#post-list',
  1177. style: {
  1178. color: '#fff',
  1179. width: '180px',
  1180. background: '#0773fb',
  1181. backgroundHover: '#fbb307',
  1182. colorHover: '#fff',
  1183. },
  1184. methodsMap: {
  1185. isPost: 'gelbooru',
  1186. getPostId: 'gelbooru',
  1187. getPostUrl: 'gelbooru',
  1188. },
  1189. needXHR: true,
  1190. };
  1191. }
  1192. //------------------------------------------------------------------------------------//
  1193. //--------------------------------------- ATFBOORU -----------------------------------//
  1194. function getAtfbooruSettings()
  1195. {
  1196. return {
  1197. name: 'atfbooru',
  1198. hostname: 'atfbooru.ninja',
  1199. imageHostname: 'atfbooru.ninja',
  1200. imageDir: 'data',
  1201. postDivInsertionPlace: '#image',
  1202. listDivInsertionPlace: '#posts',
  1203. viewDivInsertionPlace: '#page', //'#c-posts',
  1204. style: {
  1205. color: '#0073ff',
  1206. width: '180px',
  1207. background: '#f5f5ff',
  1208. backgroundHover: '#f5f5ff',
  1209. colorHover: '#80b9ff',
  1210. },
  1211. methodsMap: {
  1212. isPost: 'donmai',
  1213. getPostId: 'donmai',
  1214. getPostUrl: 'donmai',
  1215. },
  1216. needXHR: true,
  1217. };// donmai like
  1218. }
  1219. //------------------------------------------------------------------------------------//
  1220. //------------------------------------- LOLIBOORU ------------------------------------//
  1221. function getLolibooruSettings()
  1222. {
  1223. return {
  1224. name: 'lolibooru',
  1225. hostname: 'lolibooru.moe',
  1226. imageDir: 'image',
  1227. imageHostname: 'lolibooru.moe',
  1228. postDivInsertionPlace: '#image',
  1229. listDivInsertionPlace: 'div.content',
  1230. viewDivInsertionPlace: 'div#post-list',
  1231. style: {
  1232. color: '#ee8887',
  1233. width: '180px',
  1234. background: '#222',
  1235. backgroundHover: '#444',
  1236. colorHover: '#ee8887',
  1237. },
  1238. methodsMap: null,
  1239. needXHR: true,
  1240. };// yande.re like
  1241. }
  1242. //------------------------------------------------------------------------------------//
  1243. //------------------------------------- HYPNOHUB -------------------------------------//
  1244. function getHypnohubSettings()
  1245. {
  1246. return {
  1247. name: 'hypnohub',
  1248. hostname: 'hypnohub.net',
  1249. imageDir: '/data/image',
  1250. imageHostname: 'hypnohub.net',
  1251. postDivInsertionPlace: '#image',
  1252. listDivInsertionPlace: 'div.content',
  1253. viewDivInsertionPlace: 'div#post-list',
  1254. style: {
  1255. color: '#ee8887',
  1256. width: '180px',
  1257. background: '#222',
  1258. backgroundHover: '#444',
  1259. colorHover: '#ee8887',
  1260. },
  1261. methodsMap: null,
  1262. needXHR: true,
  1263. };// yande.re like
  1264. }
  1265. //------------------------------------------------------------------------------------//
  1266. //---------------------------------------- TBIB --------------------------------------//
  1267. function getTbibSettings()
  1268. {
  1269. return {
  1270. name: 'tbib',
  1271. hostname: 'tbib.org',
  1272. imageDir: '/images',
  1273. imageHostname: 'tbib.org',
  1274. postDivInsertionPlace: '#image',
  1275. listDivInsertionPlace: 'div.content',
  1276. viewDivInsertionPlace: 'div#post-list',
  1277. style: {
  1278. color: '#fff',
  1279. width: '180px',
  1280. background: '#0773fb',
  1281. backgroundHover: '#fbb307',
  1282. colorHover: '#fff',
  1283. },
  1284. methodsMap: {
  1285. isPost: 'gelbooru',
  1286. getPostId: 'gelbooru',
  1287. getPostUrl: 'gelbooru',
  1288. },
  1289. needXHR: true,
  1290. };// gelbooru like
  1291. }
  1292. //------------------------------------------------------------------------------------//
  1293. //------------------------------------- KONACHAN -------------------------------------//
  1294. function getKonachanSettings()
  1295. {
  1296. var hostname = window.location.hostname;
  1297. return {
  1298. name: 'konachan',
  1299. hostname: hostname,
  1300. imageDir: 'image',
  1301. imageHostname: hostname,
  1302. postDivInsertionPlace: '#image',
  1303. listDivInsertionPlace: 'div.content',
  1304. viewDivInsertionPlace: 'div#post-list',
  1305. style: {
  1306. color: '#ee8887',
  1307. width: '180px',
  1308. background: '#222',
  1309. backgroundHover: '#444',
  1310. colorHover: '#ee8887',
  1311. },
  1312. methodsMap: null,
  1313. needXHR: true,
  1314. };// yande.re like
  1315. }
  1316. //------------------------------------------------------------------------------------//
  1317. //--------------------------------------- PAHEAL -------------------------------------//
  1318. function getPahealSettings()
  1319. {
  1320. return {
  1321. name: 'paheal.net',
  1322. prefix: 'rule34.',
  1323. hostname: 'rule34.paheal.net',
  1324. imageDir: '_images',
  1325. imageHostname: '.paheal.net',
  1326. postDivInsertionPlace: '#main_image',
  1327. listDivInsertionPlace: '#imagelist',
  1328. viewDivInsertionPlace: '#imagelist',
  1329. style: {
  1330. color: '#fff',
  1331. width: '180px',
  1332. background: '#84AE83',
  1333. backgroundHover: '#A4CEA3',
  1334. colorHover: '#fff',
  1335. },
  1336. methodsMap: {
  1337. isPost: 'paheal',
  1338. getPostId: 'paheal',
  1339. getPostUrl: 'paheal',
  1340. },
  1341. needXHR: true,
  1342. };
  1343. }
  1344. //------------------------------------------------------------------------------------//
  1345. //------------------------------------- HOST PREFIX ----------------------------------//
  1346. function getHostnamePrefix( hostnameSuffix, prefixList, prefix )
  1347. {
  1348. var hostname,
  1349. errorMessage = "[getHostnamePrefix](hostnameSuffix='" + hostnameSuffix +
  1350. "', prefixList=[" + prefixList.join(',') + "]" + (prefix ? ", prefix='" + prefix + "'" : "") + ") ",
  1351. regExp;
  1352. if( prefix )
  1353. {
  1354. if( prefixList.indexOf(prefix) == -1 )
  1355. {
  1356. console.error(errorMessage + "\nnot supported prefix");
  1357. return '';
  1358. }
  1359. }else{
  1360. hostname = window.location.hostname;
  1361. if( hostname.indexOf(hostnameSuffix) == -1 )
  1362. {
  1363. console.error(errorMessage + "\ninvalid hostname: " + hostname );
  1364. return '';
  1365. }
  1366. for( var i = 0, len = prefixList.length; i < len; ++i )
  1367. {
  1368. if( hostname.indexOf(prefixList[i]) == -1 )
  1369. continue;
  1370. prefix = prefixList[i];
  1371. break;
  1372. }
  1373. }
  1374. if( !prefix )
  1375. {
  1376. try{
  1377. regExp = new RegExp('([^\\.]+\\.)(' + hostnameSuffix + ')' );
  1378. prefix = hostname.match(regExp)[1];
  1379. }catch(e){
  1380. console.error(e);
  1381. console.error(errorMessage + "\nno valid prefix for hostname: " + hostname );
  1382. }
  1383. }
  1384. return prefix || '';
  1385. }
  1386. //------------------------------------------------------------------------------------//
  1387. //----------------------------------- METHODS OBJECT ---------------------------------//
  1388. function initMethodsObject()
  1389. {
  1390. var retVal = {
  1391. get list(){return this.map.list;},
  1392. map: {
  1393. list: [
  1394. 'isPost',
  1395. 'getPostId',// get post id from href
  1396. 'getPostUrl',// get post url by postId
  1397. // method of thumbnail data grabbing
  1398. 'getImageThumbs',
  1399. 'setImageDataThumb',
  1400. // methods of image data getting from image post page
  1401. 'getPostImage',
  1402. 'setImageOriginalResolution',
  1403. 'setImageDataSize',
  1404. 'setImageDataSourceLowres',
  1405. 'setImageDataSourceHighres',
  1406. 'setImageDataTags',
  1407. 'setImageDataName',
  1408. 'setImageDataExtension',
  1409. 'setImageDataBytes',
  1410. 'setImageDataDoc',
  1411. // create place for buttons insertion
  1412. 'getPostDivInsertionPlace',
  1413. 'getListDivInsertionPlace',
  1414. 'createDiv',
  1415. ],
  1416. },
  1417. data: {
  1418. 'booru': {
  1419. val: null,
  1420. init: function(){
  1421. this.val = this.val || getBooruMethodsObject();
  1422. },
  1423. },
  1424. 'gelbooru': {
  1425. val: null,
  1426. init: function(){
  1427. this.val = this.val || getGelbooruMethodsObject();
  1428. },
  1429. },
  1430. 'donmai': {
  1431. val: null,
  1432. init: function(){
  1433. this.val = this.val || getDonmaiMethodsObject();
  1434. },
  1435. },
  1436. 'paheal': {
  1437. val: null,
  1438. init: function(){
  1439. this.val = this.val || getPahealMethodsObject();
  1440. },
  1441. },
  1442. },
  1443. init: function(){
  1444. for( var type in this.data )
  1445. this.data[type].init();
  1446. },
  1447. method: function( type, name ){
  1448. if( this.data[type] )
  1449. {
  1450. if( name )
  1451. return this.data[type].val[name];
  1452. return this.data[type].val;
  1453. }
  1454. return null;
  1455. },
  1456. };
  1457. retVal.init();
  1458. return retVal;
  1459. }
  1460. //----------------------------------- METHODS OBJECT ---------------------------------//
  1461. //------------------------------------------------------------------------------------//
  1462. //-------------------------------- BOORU METHODS OBJECT ------------------------------//
  1463. function getBooruMethodsObject()
  1464. {
  1465. var retVal = {
  1466. isPost: function(url){
  1467. url = url || window.location.pathname || window.location.href;
  1468. return /\/post\/show\/\d+/.test(url);
  1469. },
  1470. getPostId: function(url){
  1471. url = url || window.location.href;
  1472. if( this.isPost(url) )
  1473. return getLocation(url, 'pathname').match(/\d+/)[0];
  1474. return null;
  1475. },
  1476. getPostUrl: function(postId){
  1477. return window.location.protocol + '//' + this.hostname + '/post/show/' + postId;
  1478. },
  1479. getPostDivInsertionPlace: function(doc){
  1480. doc = doc || document;
  1481. var insertPlace = doc.querySelector( this.postDivInsertionPlace );
  1482. if( insertPlace )
  1483. {
  1484. var parent = insertPlace.parentNode;
  1485. if( parent.tagName === 'A' )
  1486. return parent.nextSibling || parent;
  1487. return insertPlace.nextSibling || insertPlace;
  1488. }
  1489. return null;
  1490. },
  1491. getListDivInsertionPlace: function(doc){
  1492. doc = doc || document;
  1493. var insertPlace = doc.querySelector(this.listDivInsertionPlace);
  1494. if( insertPlace )
  1495. return insertPlace.firstChild || insertPlace;
  1496. return null;
  1497. },
  1498. getPostImage: function(doc){
  1499. doc = doc || document;
  1500. return doc.querySelector('#image') || doc.querySelector('#main_image');//paheal
  1501. },
  1502. getImageThumbs: function( doc ){
  1503. doc = doc || document;
  1504. var thumbs = doc.querySelectorAll('img.preview');
  1505. if( thumbs && thumbs.length === 0 )
  1506. thumbs = doc.querySelectorAll('article > a > img');// donmai, uberbooru
  1507. if( thumbs && thumbs.length === 0 )
  1508. thumbs = doc.querySelectorAll('img[itemprop="thumbnailUrl"]');// donmai
  1509. if( thumbs && thumbs.length === 0 )
  1510. thumbs = doc.querySelectorAll('span.thumb > a > img');// *.booru.org
  1511. if( thumbs && thumbs.length === 0 )
  1512. thumbs = doc.querySelectorAll('a > img[id*="thumb_"]');// rule34.paheal.net
  1513. return thumbs;
  1514. },
  1515. setImageDataThumb: function( imgD, thumb ){
  1516. if( thumb && imgD )
  1517. {
  1518. if( thumb.dataset && thumb.dataset.original )
  1519. imgD.thumbSource = thumb.dataset.original;
  1520. else
  1521. imgD.thumbSource = thumb.src;
  1522. imgD.postUrl = thumb.parentNode.href;
  1523. if( thumb.parentNode.id && /\d+/.test(thumb.parentNode.id) )
  1524. imgD.postId = thumb.parentNode.id.match(/\d+/)[0];
  1525. else
  1526. imgD.postId = this.getPostId( imgD.postUrl );
  1527. if( thumb.title )
  1528. imgD.thumbTitle = thumb.title;
  1529. }
  1530. },
  1531. setImageDataSourceLowres: function( imgD, doc ){
  1532. var img = this.getPostImage(doc);
  1533. if( img )
  1534. imgD.lowresSource = img.src;
  1535. else
  1536. return 1;
  1537. return 0;
  1538. },
  1539. setImageOriginalResolution: function( imgD, img ){
  1540. if( !img )
  1541. return false;
  1542. var width, height;
  1543. width = img.getAttribute('large_width');
  1544. height = img.getAttribute('large_height');
  1545. if( !width || !height )
  1546. {
  1547. width = img.getAttribute('data-original-width');
  1548. height = img.getAttribute('data-original-height');
  1549. }
  1550. if( !width || !height )
  1551. {
  1552. // sankaku
  1553. width = img.getAttribute('orig_width');
  1554. height = img.getAttribute('orig_height');
  1555. }
  1556. if( !width || !height )
  1557. {
  1558. // e926.net, e621.net
  1559. width = img.getAttribute('data-orig_width');
  1560. height = img.getAttribute('data-orig_height');
  1561. }
  1562. if( (!width || !height) && this.name === 'paheal.net' )
  1563. {
  1564. // paheal.net
  1565. width = img.getAttribute('data-width');
  1566. height = img.getAttribute('data-height');
  1567. }
  1568. if( width && height )
  1569. {
  1570. imgD.width = width;
  1571. imgD.height = height;
  1572. return true;
  1573. }
  1574. return false;
  1575. },
  1576. setImageDataSize: function( imgD, doc ){
  1577. doc = doc || document;
  1578. var img = this.getPostImage(doc), res;
  1579. if( this.setImageOriginalResolution )
  1580. res = this.setImageOriginalResolution( imgD, img );
  1581. if( res )
  1582. return;
  1583. var lis = doc.querySelectorAll('li'), i, li, len = lis.length;
  1584. for( i = 0; i < len; ++i )
  1585. {
  1586. li = lis[i];
  1587. if( li.innerHTML.indexOf('Size:') != -1 )
  1588. break;
  1589. }
  1590. var match = li.innerHTML.match(/(\d+)x(\d+)/);
  1591. if( i < len && match )
  1592. {
  1593. imgD.width = match[1];
  1594. imgD.height = match[2];
  1595. }else
  1596. console.error("[setImageDataSize] can't find image size (width x height)");
  1597. },
  1598. setImageDataSourceHighres: function( imgD, doc ){
  1599. doc = doc || document;
  1600. var imgHost = this.imageHostname || this.hostname,
  1601. i, l, href,
  1602. link = doc.querySelectorAll('li > a[href*="' + imgHost + '/' + this.imageDir + '/"]');
  1603. if( link.length === 0 )// same origin imageboards
  1604. link = doc.querySelectorAll('li > a[href*="/' + this.imageDir + '/"]');
  1605. if( link.length > 0 )
  1606. {
  1607. for( i = 0, href = null; i < link.length; ++i )
  1608. {
  1609. l = link[i];
  1610. if( l.href.indexOf('sample') == -1 )
  1611. {
  1612. href = l.href;
  1613. break;
  1614. }
  1615. }
  1616. imgD.source = href ? href : last(link).href;
  1617. }
  1618. else if( imgD.lowresSource )
  1619. imgD.source = imgD.lowresSource;
  1620. else{
  1621. console.error("[setImageDataSourceHighres] no image source found");
  1622. return 1;
  1623. }
  1624. // jpeg image for yande.re like imageboards
  1625. var jpeg = doc.querySelector('li > a[href*="' + imgHost + '/jpeg/"]');
  1626. if( jpeg )
  1627. imgD.jpegSource = jpeg.href;
  1628. clog("imgD.source: " + imgD.source);
  1629. this.setType = this.setType || function( _type, _source, _imgD )
  1630. {
  1631. _imgD[_type + '-source'] = _source;
  1632. _imgD[_type + '-extension'] = getFileExt(_source);
  1633. };
  1634. this.setType( 'thumnail', imgD.thumbSource, imgD );
  1635. if( /mp4|webm|ogv|ogg/.test(getFileExt(imgD.source)) )
  1636. {
  1637. this.setType( 'vid_file', imgD.source, imgD );
  1638. imgD.viewType = 'vid_file';
  1639. }else{
  1640. this.setType( 'orig_img', imgD.source, imgD );
  1641. imgD.viewType = 'orig_img';
  1642. if( imgD.jpegSource )
  1643. {
  1644. this.setType( 'jpeg_img', imgD.jpegSource, imgD );
  1645. imgD.viewType = 'jpeg_img';
  1646. }
  1647. if( !isSameLink(imgD.source, imgD.lowresSource) )
  1648. {
  1649. this.setType( 'samp_img', imgD.lowresSource, imgD );
  1650. imgD.viewType = 'samp_img';
  1651. }
  1652. }
  1653. return 0;
  1654. },
  1655. setImageDataTags: function( imgD, doc ){
  1656. doc = doc || document;
  1657. this.getTagName = this.getTagName || function( tagElm, fl)
  1658. {
  1659. if( tagElm.querySelectorAll('a').length === 0 )
  1660. return '';
  1661. if( fl )
  1662. return tagElm.querySelectorAll('a')[0].innerText.trim().replace(/\s+/g, '_');// sankaku, safebooru.org
  1663. return last(tagElm.querySelectorAll('a')).innerText.trim().replace(/\s+/g, '_');
  1664. };
  1665. this.tagsId = this.tagsId || {
  1666. 'general' : '0',
  1667. 'artist' : '1',
  1668. 'copyright': '3',
  1669. 'character': '4',
  1670. 'metadata' : '5',
  1671. // 3dbooru tags
  1672. 'species' : '-1',
  1673. 'model' : '-1',
  1674. 'idol' : '-1',
  1675. 'photo_set': '-1',
  1676. 'circle' : '-1',
  1677. 'medium' : '-1',
  1678. 'faults' : '-1',
  1679. };
  1680. this.createTagObj = this.createTagObj || function( tagElm, tagsClass, fl )
  1681. {
  1682. try{
  1683. var links = tagElm.querySelectorAll('a'),
  1684. post_count = tagElm.querySelector('span.post-count') || tagElm.querySelector('span[style]'),
  1685. searchLink = null,
  1686. obj = {};
  1687. if( tagsClass === 'tag-type' )
  1688. {
  1689. if( fl )
  1690. {
  1691. obj.href = links[0].href;
  1692. obj.wiki = links[1].href;
  1693. searchLink = links[0];
  1694. }else{
  1695. obj.href = last(links).href;
  1696. obj.wiki = (links.length == 1 ? null : links[0].href);
  1697. }
  1698. obj.category = tagElm.className.match(/tag-type-([^\s]+)/)[1];
  1699. }
  1700. else if( tagsClass === 'category' )
  1701. {
  1702. obj.href = last(links).href;
  1703. obj.wiki = (links.length == 1 ? null : links[0].href);
  1704. obj.category = tagElm.className.match(/category-([^\s]+)/)[1];
  1705. }
  1706. else if( tagsClass === 'tag_name_cell' )
  1707. {
  1708. obj.href = links[1].href;
  1709. obj.wiki = links[0].href;
  1710. post_count = tagElm.querySelector('span.tag_count');
  1711. obj.category = null;
  1712. }else{
  1713. obj.href = links[0].href;
  1714. obj.wiki = null;
  1715. post_count = links[0].nextSibling;
  1716. obj.category = null;
  1717. }
  1718. if( obj.category )
  1719. obj.class = tagsClass;
  1720. searchLink = searchLink || last(links);
  1721. obj.count = parseInt(post_count.textContent, 10);
  1722. obj.name = searchLink.textContent;
  1723. return obj;
  1724. }catch(e){console.error(e);}
  1725. };
  1726. /*
  1727. --------------> n1 [ n2 ]
  1728. n1 - number of links in tag element
  1729. n2 - index 0..(n1-1) of search tag link (here, tag link)
  1730. --------------> 2 1
  1731. --> gelbooru:
  1732. ul#tag-list
  1733. div
  1734. div[style] -- tag category name
  1735. li.tag-type-
  1736. a[href] -- wiki link
  1737. a[href] -- tag link
  1738. span[style]
  1739. --------------> 1
  1740. --> rule34.xxx:
  1741. ul#tag-sidebar
  1742. li -- tag category name
  1743. li.tag-type- [class="tag"]
  1744. a[href] -- tag
  1745. span[style]
  1746. --------------> 2 1
  1747. --> yande.re,
  1748. --> 3dbooru (behoimi):
  1749. ul#tag-sidebar
  1750. li.tag-type-
  1751. a[href] -- wiki link
  1752. a[href] -- tag link
  1753. span.post-count
  1754. --------------> 2 0
  1755. --> sankaku:
  1756. ul#tag-sidebar
  1757. li.tag-type-
  1758. a[href, title] -- tag link
  1759. span.tag-extra-info
  1760. a[href] -- wiki link
  1761. span.post-count
  1762. --------------> 1
  1763. --> safebooru,
  1764. --> mspabooru,
  1765. --> tbib:
  1766. ul#tag-sidebar
  1767. li.tag-type-
  1768. a[href] -- tag link
  1769. span[style]
  1770. --------------> 2 1
  1771. --> e621.net,
  1772. --> e926.net:
  1773. ul#tag-sidebar
  1774. li.category- -- tag category name
  1775. li.tag-type-
  1776. a[style, href] -- wiki
  1777. a[href] -- tag
  1778. span.post-count
  1779. --------------> 1
  1780. --> booru.org:
  1781. div#tag_list
  1782. ul
  1783. li
  1784. span[style]
  1785. ?
  1786. a[href] -- tag
  1787. number -- post count
  1788. --------------> 2 1
  1789. --> uberbooru,
  1790. --> bronibooru:
  1791. section#tag-list
  1792. h2 -- tag category name
  1793. ul
  1794. li.category-
  1795. a.wiki-link [href]
  1796. a.search-tag [href]
  1797. span.post-count
  1798. --------------> 2 1
  1799. --> donmai,
  1800. --> atfbooru:
  1801. section#tag-list
  1802. h2.copyright-tag-list -- tag category name
  1803. ul.copyright-tag-list
  1804. li.category-
  1805. a.wiki-link [href]
  1806. a.search-tag [href]
  1807. span.post-count
  1808. --------------> 2 1
  1809. --> lolibooru,
  1810. --> hypnohub,
  1811. --> konachan:
  1812. ul#tag-sidebar
  1813. li.tag-type- [class="tag-link", data-name="name of tag", data-type="tag category"]
  1814. a[href] -- wiki
  1815. a[href] -- tag
  1816. span.post-count
  1817. --------------> 2 1
  1818. --> rule34.paheal.net:
  1819. table.tag_list
  1820. tbody tr
  1821. td.tag_info_link_cell
  1822. a.tag_info_link [href] -- wiki
  1823. td.tag_name_cell
  1824. a.tag_name [href] -- tag
  1825. td.tag_count_cell
  1826. span.tag_count
  1827. */
  1828. var getTagName = this.getTagName,
  1829. tagsId = this.tagsId,
  1830. createTagObj = this.createTagObj,
  1831. tagsClass = '';
  1832. var nameList = ['sankaku'],
  1833. tagsOrder = userOptions.val('tagsOrder'),
  1834. iter = 0, _fl = null, i, k, tags, tagType;
  1835. imgD.tags = imgD.tags || [];
  1836. imgD.tags.length = 0;
  1837. if( doc.querySelectorAll('li[class*="tag-type-"]').length > 0 )
  1838. tagsClass = 'tag-type';
  1839. else if( doc.querySelectorAll('li[class*="category-"]').length > 0 )
  1840. tagsClass = 'category';
  1841. for( i = 0, _fl = (nameList.indexOf(this.name) != -1); i < tagsOrder.length; ++i )
  1842. {
  1843. tagType = tagsOrder[i];
  1844. if( tagsClass === 'tag-type' )
  1845. tags = doc.querySelectorAll('li.tag-type-' + tagType);
  1846. else if( tagsClass === 'category' )
  1847. tags = doc.querySelectorAll('li.category-' + tagsId[tagType]);// donmai like
  1848. for( k = 0; tags && k < tags.length; ++k, ++iter )
  1849. {
  1850. imgD.tags.push( getTagName(tags[k], _fl) );
  1851. }
  1852. }
  1853. if( iter === 0 )
  1854. {
  1855. // not categorized tags
  1856. tagsClass = '';
  1857. tags = doc.querySelectorAll('div#tag_list li');// *.booru.org
  1858. if( !tags || tags.length === 0 )
  1859. {
  1860. tags = doc.querySelectorAll('.tag_name_cell');// paheal.net
  1861. tagsClass = 'tag_name_cell';
  1862. }
  1863. for( k = 0, _fl = (nameList.indexOf(this.name) != -1); tags && k < tags.length; ++k )
  1864. {
  1865. imgD.tags.push( getTagName(tags[k], _fl) );
  1866. }
  1867. }
  1868. // tag object
  1869. imgD.tagList = imgD.tagList || [];
  1870. imgD.tagList.length = 0;
  1871. if( tagsClass === 'tag-type' )
  1872. tags = doc.querySelectorAll('li[class*="tag-type-"]');
  1873. else if( tagsClass === 'category' )
  1874. tags = doc.querySelectorAll('ul li[class*="category-"]');
  1875. else if( tagsClass === 'tag_name_cell' )
  1876. tags = doc.querySelectorAll('table.tag_list > tbody tr');// paheal.net
  1877. else
  1878. tags = doc.querySelectorAll('div#tag_list ul li span');// .booru.org
  1879. for( i = 0; i < tags.length; ++i )
  1880. {
  1881. imgD.tagList.push( createTagObj(tags[i], tagsClass, _fl) );
  1882. }
  1883. },
  1884. createDiv: function(id, doc){
  1885. doc = doc || document;
  1886. var div = doc.querySelector('#' + id);
  1887. clog("[createDiv] div#" + id + ": ", div);
  1888. if( div )
  1889. return div;
  1890. div = document.createElement('div');
  1891. var insertPlace;
  1892. if( this.isPost() )
  1893. insertPlace = this.getPostDivInsertionPlace(doc);
  1894. else
  1895. insertPlace = this.getListDivInsertionPlace(doc) || doc.querySelector(".thumb") || doc.body.firstChild;
  1896. if( !insertPlace )
  1897. return null;
  1898. div.setAttribute('id', id);
  1899. if( insertPlace.tagName !== 'IMG' )
  1900. div = insertPlace.parentNode.insertBefore( div, insertPlace.nextSibling);// check_it_out
  1901. else
  1902. div = insertPlace.parentNode.appendChild(div);
  1903. if( typeof this.keyboardDiv === 'function' )
  1904. this.keyboardDiv( id, doc );
  1905. return div;
  1906. },
  1907. setImageDataDoc: function( imgD, doc ){
  1908. if( !imgD || imgD.state === 'ready' )
  1909. return 1;
  1910. doc = doc || document;
  1911. // size
  1912. this.setImageDataSize( imgD, doc );
  1913. // lowres
  1914. var errN = this.setImageDataSourceLowres( imgD, doc );
  1915. // highres
  1916. errN += this.setImageDataSourceHighres( imgD, doc );
  1917. if( errN > 1 )
  1918. return errN;
  1919. if( !imgD.lowresSource )
  1920. imgD.lowresSource = (imgD.jpegSource || imgD.source);
  1921. // tags
  1922. this.setImageDataTags( imgD, doc );
  1923. // name
  1924. this.setImageDataName( imgD );
  1925. // extension
  1926. this.setImageDataExtension( imgD );
  1927. imgD.state = 'ready';
  1928. return 0;
  1929. },
  1930. setImageDataName: function( imgD ){
  1931. if( !imgD || !imgD.tags )
  1932. return;
  1933. var tagsLen = imgD.tags.length,
  1934. uLen = userOptions.val('maxTagsInName'),
  1935. tagsDelim = userOptions.val('tagsDelim'),
  1936. imageId = imgD.postId,
  1937. boardName = '', name = '',
  1938. ignoredTags = userOptions.val('ignoredTags'),
  1939. tagName;
  1940. if( userOptions.val('addImgBrdName') )
  1941. {
  1942. boardName = (userOptions.val('prefixedName') ? this.prefixedName : this.shortName);
  1943. imageId = boardName + tagsDelim + imgD.postId;
  1944. }
  1945. for( var i = 0; i < tagsLen && i < uLen; ++i )
  1946. {
  1947. tagName = imgD.tags[i];
  1948. if( tagName.length > 0 && ignoredTags.indexOf(tagName) == -1 )
  1949. name += tagName + tagsDelim;
  1950. }
  1951. if( userOptions.val('imgIdAtNameEnd') )
  1952. imgD.name = name + imageId;
  1953. else
  1954. imgD.name = imageId + tagsDelim + name.slice(0, -tagsDelim.length);
  1955. },
  1956. setImageDataExtension: function( imgD ){
  1957. imgD.extension = getFileExt( imgD.source );
  1958. if( imgD.jpegSource )
  1959. imgD.jpegExtension = getFileExt( imgD.jpegSource );
  1960. },
  1961. };
  1962. return retVal;
  1963. }
  1964. //-------------------------------- BOORU METHODS OBJECT ------------------------------//
  1965. //------------------------------------------------------------------------------------//
  1966. //------------------------------- GELBOORU METHODS OBJECT ----------------------------//
  1967. function getGelbooruMethodsObject()
  1968. {
  1969. var retVal = {
  1970. isPost: function( url ){
  1971. url = url || window.location.href;
  1972. if( this.getPostId(url) )
  1973. return true;
  1974. return false;
  1975. },
  1976. getPostId: function( postUrl ){
  1977. postUrl = postUrl || window.location.href;
  1978. var srch = getLocation( postUrl, 'search' ),
  1979. keys = getSearchObject( srch );
  1980. if( keys.s === 'view' && keys.page === 'post' )
  1981. return keys.id;
  1982. else
  1983. return null;
  1984. },
  1985. getPostUrl: function( postId ){
  1986. return window.location.protocol + this.hostname + '/index.php?page=post&s=view&id=' + postId;
  1987. },
  1988. };
  1989. return retVal;
  1990. }
  1991. //------------------------------------------------------------------------------------//
  1992. //-------------------------------- DONMAI METHODS OBJECT -----------------------------//
  1993. function getDonmaiMethodsObject()
  1994. {
  1995. var retVal = {
  1996. isPost: function(url){
  1997. url = url || window.location.href;
  1998. return /\/posts\/\d+/.test(url);
  1999. },
  2000. getPostId: function(url){
  2001. url = url || window.location.href;
  2002. if( this.isPost(url) )
  2003. return getLocation(url, 'pathname').match(/(\/posts\/)?(\d+)?/)[2];
  2004. return null;
  2005. },
  2006. getPostUrl: function(postId){
  2007. return window.location.protocol + '//' + this.hostname + '/posts/' + postId;
  2008. },
  2009. };
  2010. return retVal;
  2011. }
  2012. //------------------------------------------------------------------------------------//
  2013. //-------------------------------- PAHEAL METHODS OBJECT -----------------------------//
  2014. function getPahealMethodsObject()
  2015. {
  2016. var retVal = {
  2017. isPost: function(url){
  2018. url = url || window.location.href;
  2019. return /\/post\/view\/\d+/.test(url);
  2020. },
  2021. getPostId: function(url){
  2022. if( this.isPost(url) )
  2023. return getLocation(url, 'pathname').match(/(\/post\/view\/)?(\d+)?/)[2];
  2024. return null;
  2025. },
  2026. getPostUrl: function(postId){
  2027. return window.location.protocol + '//' + this.hostname + '/post/view/' + postId;
  2028. },
  2029. };
  2030. return retVal;
  2031. }
  2032. //------------------------------------------------------------------------------------//
  2033. //-------------------------------------- DATASET -------------------------------------//
  2034. function initImageBoardDataset(d)
  2035. {
  2036. var retVal = {
  2037. data: {
  2038. source: 'data-image-board-source',
  2039. index: 'data-image-board-index',
  2040. extension: 'data-image-board-extension',
  2041. bytes: 'data-image-board-bytes',
  2042. },
  2043. val: function(elm, propName, v){
  2044. if( this.data[propName] && elm )
  2045. {
  2046. if( v !== undefined )
  2047. elm.setAttribute(this.data[propName], v);
  2048. else
  2049. return elm.getAttribute(this.data[propName]);
  2050. }
  2051. return null;
  2052. },
  2053. init: function(doc){
  2054. this.doc = doc || document;
  2055. },
  2056. getSelector: function(propName, v){
  2057. var sel = this.data[propName];
  2058. if( sel )
  2059. {
  2060. if( v !== undefined )
  2061. {
  2062. var pos = v.indexOf('=');// &=, *=, ^=
  2063. if( pos > -1 && pos < 2 )
  2064. sel += v;
  2065. else
  2066. sel += '="' + v + '"';
  2067. }
  2068. return '[' + sel + ']';
  2069. }
  2070. return null;
  2071. },
  2072. query: function(propName, v){
  2073. var sel = this.getSelector(propName, v);
  2074. if( sel )
  2075. return this.doc.querySelector(sel);
  2076. return null;
  2077. },
  2078. queryAll: function(propName, v){
  2079. var sel = this.getSelector(propName, v);
  2080. if( sel )
  2081. return this.doc.querySelectorAll(sel);
  2082. return null;
  2083. },
  2084. };
  2085. retVal.init(d);
  2086. return retVal;
  2087. }
  2088. //-------------------------------------- DATASET -------------------------------------//
  2089. //------------------------------------------------------------------------------------//
  2090. //-------------------------------------- CLASSES -------------------------------------//
  2091. function initImageBoardClasses(d)
  2092. {
  2093. var retVal = {
  2094. get counted(){return this.data.counted;},
  2095. get viewActive(){return this.data.viewActive;},
  2096. get viewAttach(){return this.data.viewAttach;},
  2097. get ready(){return this.data.ready;},
  2098. get downloaded(){return this.data.downloaded;},
  2099. get downloadAttach(){ return this.data.downloadAttach;},
  2100. get downloadActive(){ return this.data.downloadActive;},
  2101. data: {
  2102. counted: 'image-board-counted',
  2103. viewAttach: 'image-board-attach-view-event',
  2104. viewActive: 'image-board-active-for-view',
  2105. ready: 'image-board-has-original-source',
  2106. downloaded: 'image-board-downloaded-original',
  2107. downloadAttach: 'image-board-attach-download-event',
  2108. downloadActive: 'image-board-active-for-download',
  2109. },
  2110. hasClass: function(elm, propName){
  2111. if( this.data[propName] )
  2112. return hasClass(elm, this.data[propName]);
  2113. return false;
  2114. },
  2115. addClass: function(elm, propName){
  2116. if( this.data[propName] )
  2117. addClass(elm, this.data[propName]);
  2118. },
  2119. removeClass: function(elm, propName){
  2120. if( this.data[propName] )
  2121. removeClass(elm, this.data[propName]);
  2122. },
  2123. toggleClass: function(elm, newPropName, oldPropName){
  2124. if( oldPropName && !this.data[oldPropName] )
  2125. return;
  2126. else if( !newPropName || !this.data[newPropName] )
  2127. return;
  2128. toggleClass( elm, this.data[newPropName], this.data[oldPropName] );
  2129. },
  2130. queryAll: function(propName){
  2131. if( this.data[propName] )
  2132. return this.doc.querySelectorAll('.' + this.data[propName]);
  2133. return null;
  2134. },
  2135. init: function(doc){this.doc = doc || document;},
  2136. };
  2137. retVal.init(d);
  2138. return retVal;
  2139. }
  2140. //-------------------------------------- CLASSES -------------------------------------//
  2141. //------------------------------------------------------------------------------------//
  2142. //------------------------------------- DOWNLOADER -----------------------------------//
  2143. function initImageBoardDownloader(d)
  2144. {
  2145. var iter = {
  2146. total : 0,
  2147. done: 0,
  2148. };
  2149. var retVal = {
  2150. get total(){return iter.total;},
  2151. set total(n){
  2152. iter.total = n;
  2153. this.downloadAllHtml(iter.total, iter.done);
  2154. },
  2155. get done(){return iter.done;},
  2156. set done(n){
  2157. iter.done = n;
  2158. this.downloadAllHtml(iter.total, iter.done);
  2159. },
  2160. data: {
  2161. downloaderId: 'image-board-downloader-' + RANDOM,
  2162. downloadAllId: 'image-board-download-all-' + RANDOM,
  2163. downloadSwitchId: 'image-board-download-switch-' + RANDOM,
  2164. classBtn: 'image-board-downloader-button',
  2165. classOn: 'image-board-downloader-on',
  2166. classOff: 'image-board-downloader-off',
  2167. classActive: 'image-board-downloader-active',
  2168. },
  2169. get downloaderId(){return this.data.downloaderId;},
  2170. get downloadAllId(){return this.data.downloadAllId;},
  2171. get downloadSwitchId(){return this.data.downloadSwitchId;},
  2172. get classBtn(){return this.data.classBtn;},
  2173. get classOn(){return this.data.classOn;},
  2174. get classOff(){return this.data.classOff;},
  2175. get classActive(){return this.data.classActive;},
  2176. init: function(id, doc){
  2177. doc = doc || document;
  2178. clog("[initImageBoardDownloader] init, doc: ", doc);
  2179. var div = doc.querySelector('div#' + id), html = '', btn;
  2180. clog("div: ", div, id);
  2181. if( !div )
  2182. {
  2183. console.error("[initImageBoardDownloader] can't find div#" + id);
  2184. return;
  2185. }
  2186. var downloadSwitch = doc.querySelector('#' + this.downloadSwitchId);
  2187. if( !downloadSwitch )
  2188. {
  2189. btn = document.createElement('button');
  2190. btn.setAttribute('id', this.downloadSwitchId);
  2191. btn.setAttribute('class', this.classOff + ' ' + this.classBtn );
  2192. btn.setAttribute('title', 'Press \'Shift+D\' to switch download mode on/off');
  2193. btn.appendChild(document.createTextNode('Donwload Mode'));
  2194. downloadSwitch = div.appendChild( btn );
  2195. }
  2196. var downloadAll = doc.querySelector('#' + this.downloadAllId);
  2197. if( !downloadAll )
  2198. {
  2199. btn = document.createElement('button');
  2200. btn.setAttribute('id', this.downloadAllId );
  2201. btn.setAttribute('class', this.classBtn );
  2202. btn.appendChild(document.createTextNode('Donwload All (0)'));
  2203. downloadAll = div.appendChild( btn );
  2204. }
  2205. return div;
  2206. },
  2207. downloadAllHtml: function( total, loaded, elm ){
  2208. if( !elm ) elm = document.querySelector('#' + this.downloadAllId );
  2209. elm.textContent = 'Download All (' + (loaded ? loaded + ' / ': '') + (total ? total : 0) + ')';
  2210. },
  2211. downloadOn: function(elm){
  2212. if( !elm ) elm = document.querySelector('#' + this.downloadSwitchId);
  2213. if( elm )
  2214. toggleClass( elm, this.classOn, this.classOff );
  2215. else
  2216. console.error("[downloadOn] empty elm: ", elm );
  2217. },
  2218. downloadOff: function(elm){
  2219. if( !elm ) elm = document.querySelector('#' + this.downloadSwitchId);
  2220. if( elm )
  2221. toggleClass( elm, this.classOff, this.classOn );
  2222. else
  2223. console.error("[downloadOff] empty elm: ", elm );
  2224. },
  2225. isActive: function(elm){
  2226. if( !elm ) elm = document.querySelector('#' + this.downloadSwitchId);
  2227. return hasClass(elm, this.classOn);
  2228. },
  2229. };
  2230. return retVal;
  2231. }
  2232. //------------------------------------- DOWNLOADER -----------------------------------//
  2233. //------------------------------------------------------------------------------------//
  2234. //------------------------------------ DOWNLOADER-2 ----------------------------------//
  2235. function handleDownloadEvent(event)
  2236. {
  2237. if( !imageBoard.imgBrdCl.hasClass( this, 'downloadActive' ) )
  2238. return;
  2239. event.preventDefault();
  2240. var thumb = event.target,
  2241. index = imageBoard.imgBrdDt.val( thumb, 'index' ),
  2242. imgD = imageBoard.images.list[index];
  2243. downloadFile( imgD );
  2244. }
  2245. function downloadFile( imgD )
  2246. {
  2247. if( imgD.state !== 'ready' || imgD.downloadState === 'downloaded' || imgD.downloadState === 'inProgress' )
  2248. return;
  2249. imgD.downloadState = 'inProgress';
  2250. var hostname = getLocation(imgD.source, 'hostname'), source, ext, stripe;
  2251. if( userOptions.val('downloadJPEG') && imgD.jpegSource )
  2252. source = imgD.jpegSource;
  2253. else
  2254. source = imgD.source;
  2255. ext = getFileExt(source);
  2256. stripe = document.querySelectorAll('#progress-stripe-' + imgD.index);
  2257. addClass( stripe, 'download-in-progress' );
  2258. if( userOptions.val('animateProgress') )
  2259. addClass( stripe, 'progress-animated' );
  2260. attr( stripe, 'style', 'width:0%;' );
  2261. if( hostname === window.location.hostname )
  2262. {
  2263. imageBoardDownloader( imgD, source, ext );
  2264. return;
  2265. }
  2266. GM.xmlHttpRequest({
  2267. url: source,
  2268. method: 'GET',
  2269. context: {
  2270. 'index': imgD.index,
  2271. 'url': source,
  2272. 'ext': ext,
  2273. 'stripe': stripe,
  2274. },
  2275. responseType: 'blob',
  2276. onload: blibBlobDownloader,
  2277. onprogress: downloadProgress,
  2278. });
  2279. }
  2280. function downloadProgress( xhr )
  2281. {
  2282. try{
  2283. if( !xhr.lengthComputable )
  2284. return;
  2285. var stripe = xhr.context.stripe || document.querySelectorAll('#progress-stripe-' + xhr.context.index),
  2286. width = Math.floor(xhr.loaded/xhr.total*100);
  2287. attr(stripe, 'style', 'width:' + width + '%');
  2288. }catch(e){console.error(e);}
  2289. }
  2290. function blibBlobDownloader( xhr )
  2291. {
  2292. var imgD = imageBoard.images.list[xhr.context.index];
  2293. if( xhr.status !== 200 )
  2294. {
  2295. console.error("xhr.status: ", xhr.status, xhr.statusText);
  2296. console.error("url: " + xhr.context.url);
  2297. if( imgD && imgD.downloadState === 'inProgress' )
  2298. imgD.downloadState = '';
  2299. return;
  2300. }
  2301. var wndURL = window.webkitURL || window.URL,
  2302. resource = wndURL.createObjectURL(xhr.response);
  2303. imageBoardDownloader( imgD, resource, xhr.context.ext );
  2304. wndURL.revokeObjectURL( resource );
  2305. }
  2306. function imageBoardDownloader( imgD, resource, extension )
  2307. {
  2308. var name = imgD.name + '.' + (extension || imgD.extension);
  2309. fileDownloader( name, resource );
  2310. var thumb = imageBoard.imgBrdDt.query('index', imgD.index + ''),
  2311. stripe = document.querySelectorAll('#progress-stripe-' + imgD.index);
  2312. imageBoard.imgBrdCl.addClass( thumb, 'downloaded' );
  2313. if( imgD.downloadState !== 'downloaded' )
  2314. imageBoard.imgBrdDw.done += 1;
  2315. imgD.downloadState = 'downloaded';
  2316. attr(stripe, 'style', 'width:100%');
  2317. //setTimeout(function(){
  2318. removeClass( stripe, 'download-in-progress' );
  2319. removeClass( stripe, 'progress-animated' );
  2320. addClass( stripe, 'progress-complete' );
  2321. //}, 50 );
  2322. }
  2323. function fileDownloader( name, resource )
  2324. {
  2325. var a = document.createElement('a'),
  2326. body = document.body || document.getElementsByTagName('body')[0];
  2327. a.setAttribute('download', name);
  2328. a.href = resource;
  2329. body.appendChild(a);
  2330. a.click();
  2331. body.removeChild(a);
  2332. }
  2333. function handleDownloadAllEvent(event)
  2334. {
  2335. var list = imageBoard.images.list;
  2336. for( var i = 0, len = list.length, imgD; i < len; ++i )
  2337. {
  2338. imgD = list[i];
  2339. downloadFile( imgD );
  2340. }
  2341. }
  2342. function handleDownloadSwitchEvent(event)
  2343. {
  2344. if( imageBoard.imgBrdDw.isActive() )
  2345. {
  2346. imageBoard.downloader.deactivate();
  2347. }else{
  2348. imageBoard.downloader.activate();
  2349. }
  2350. }
  2351. //------------------------------------ DOWNLOADER-2 ----------------------------------//
  2352. //------------------------------------------------------------------------------------//
  2353. //-------------------------------------- KEYBOARD ------------------------------------//
  2354. function activateKeyboard()
  2355. {
  2356. window.addEventListener('keydown', handleKeyboardEvent, false);
  2357. clog("--------> keyboard activated");
  2358. }
  2359. function deactivateKeyboard()
  2360. {
  2361. window.removeEventListener('keydown', handleKeyboardEvent, false);
  2362. clog("--------> keyboard deactivated");
  2363. }
  2364. function handleKeyboardEvent(event)
  2365. {
  2366. var charCode = event.keyCode || event.which,
  2367. str = String.fromCharCode(charCode).toLowerCase();
  2368. if( !event.shiftKey || event.ctrlKey || event.altKey )
  2369. return;
  2370. else if( str === 'a' )
  2371. {
  2372. handleDownloadAllEvent();
  2373. }
  2374. else if( str === 'd' )
  2375. {
  2376. handleDownloadSwitchEvent();
  2377. }
  2378. else if( str === 'i' )
  2379. {
  2380. if( imageBoard )
  2381. {
  2382. imageBoard.init();
  2383. imageBoard.fix();
  2384. }
  2385. }
  2386. else if( str === 'm' )
  2387. {
  2388. handleUserMenuEvent();
  2389. }
  2390. else if( str === 'v' )
  2391. {
  2392. handleViewerSwitchEvent();
  2393. }
  2394. }
  2395. //-------------------------------------- KEYBOARD ------------------------------------//
  2396. //------------------------------------------------------------------------------------//
  2397. //--------------------------------------- VIEWER -------------------------------------//
  2398. function initImageBoardViewer(d)
  2399. {
  2400. var iter = {
  2401. curr: 0,
  2402. total: 0,
  2403. };
  2404. var retVal = {
  2405. get curr(){return iter.curr;},
  2406. set curr(n){
  2407. n = parseInt(n, 10);
  2408. var elm = (this.doc || document).querySelector('#' + this.currentId);
  2409. if( elm )
  2410. elm.textContent = '' + (n + 1);
  2411. iter.curr = n;
  2412. },
  2413. set total(n){iter.total = parseInt(n, 10);},
  2414. get total(){return iter.total;},
  2415. data: {
  2416. buttonId: 'image-board-viewer-button-' + RANDOM,
  2417. containerId: 'image-board-viewer-container-' + RANDOM,
  2418. tagsId: 'image-board-viewer-tags-' + RANDOM,
  2419. listId: 'image-board-viewer-list-' + RANDOM,
  2420. bottomId: 'image-board-viewer-bottom-' + RANDOM,
  2421. thumbsId: 'image-board-viewer-thumbs-' + RANDOM,
  2422. // bottom div panel
  2423. prevId: 'image-board-viewer-show-prev-' + RANDOM,
  2424. nextId: 'image-board-viewer-show-next-' + RANDOM,
  2425. downloadId: 'image-board-viewer-downlaod-' + RANDOM,
  2426. closeId: 'image-board-viewer-close-' + RANDOM,
  2427. sourceId: 'image-board-viewer-source-' + RANDOM,
  2428. currentId: 'image-board-viewer-current-' + RANDOM,
  2429. // classes
  2430. classActive: 'image-board-viewer-active',
  2431. classOn: 'image-board-viewer-on',
  2432. classOff: 'image-board-viewer-off',
  2433. classBtn: 'image-board-viewer-btn',
  2434. classBottom: 'image-board-viewer-bottom-class',
  2435. },
  2436. get buttonId(){return this.data.buttonId;},
  2437. get containerId(){return this.data.containerId;},
  2438. get tagsId(){return this.data.tagsId;},
  2439. get listId(){return this.data.listId;},
  2440. get bottomId(){return this.data.bottomId;},
  2441. get thumbsId(){return this.data.thumbsId;},
  2442. get prevId(){return this.data.prevId;},
  2443. get nextId(){return this.data.nextId;},
  2444. get downloadId(){return this.data.downloadId;},
  2445. get closeId(){return this.data.closeId;},
  2446. get sourceId(){return this.data.sourceId;},
  2447. get currentId(){return this.data.currentId;},
  2448. get classActive(){return this.data.classActive;},
  2449. get classOn(){return this.data.classOn;},
  2450. get classOff(){return this.data.classOff;},
  2451. get classBtn(){return this.data.classBtn;},
  2452. get classBottom(){return this.data.classBottom;},
  2453. init: function(id, doc, selector){
  2454. if( !userOptions.val('createViewer') )
  2455. return;
  2456. doc = doc || document;
  2457. this.doc = doc;
  2458. var div = doc.querySelector('#' + id), viewDiv, html;
  2459. if( !div )
  2460. {
  2461. console.error("[initImageBoardViewer] imageBoard div not found, id: " + id);
  2462. return;
  2463. }
  2464. var btn = doc.querySelector('#' + this.buttonId);
  2465. if( !btn )
  2466. {
  2467. btn = document.createElement('button');
  2468. btn.setAttribute('id', this.buttonId);
  2469. btn.setAttribute('class', this.classOff);
  2470. btn.appendChild(document.createTextNode('Viewer'));
  2471. btn = div.insertBefore( btn, div.firstChild );
  2472. }
  2473. var cont = doc.querySelector('#' + this.containerId);
  2474. if( !cont )
  2475. {
  2476. cont = document.createElement('div');
  2477. var obj = {
  2478. 'id': this.containerId,
  2479. 'class': this.classOff + ' image-board-viewer-container',
  2480. 'data-class-button': this.classBtn,
  2481. 'data-prev-id': this.prevId,
  2482. 'data-next-id': this.nextId,
  2483. 'data-download-id': this.downloadId,
  2484. 'data-current-id': this.currentId,
  2485. 'data-source-id': this.sourceId,
  2486. 'data-close-id': this.closeId,
  2487. 'data-list-id': this.listId,
  2488. };
  2489. for( var key in obj )
  2490. cont.setAttribute( key, obj[key] );
  2491. html = '' +
  2492. '<div id="' + this.tagsId + '" class="viewer-tag-list" tabindex="1000"></div>' +
  2493. '<div id="' + this.thumbsId + '" class="viewer-thumb-list" tabindex="1001"></div>' +
  2494. '<div id="' + this.listId + '" class="viewer-img-list" style="text-align:center;"></div>' +
  2495. '<div id="' + this.bottomId + '" class="' + this.classBottom + ' viewer-bottom">' +
  2496. '<button id="' + this.prevId + '" class="' + this.classBtn + ' viewer-navigation-bar">Prev</button>' +
  2497. '<button id="' + this.sourceId + '" class="' + this.classBtn + ' viewer-navigation-bar">Source</button>' +
  2498. '<button id="' + this.closeId + '" class="' + this.classBtn + ' viewer-navigation-bar">Close</button>' +
  2499. '<button id="' + this.currentId + '" class="viewer-navigation-bar" style="width:40px;">' + '-' + '</button>' +
  2500. '<button id="' + this.downloadId + '" class="' + this.classBtn + ' viewer-navigation-bar">Download</button>' +
  2501. '<button id="' + this.nextId + '" class="' + this.classBtn + ' viewer-navigation-bar">Next</button>' +
  2502. '</div>';
  2503. cont.insertAdjacentHTML('beforeend', html);
  2504. if( userOptions.val('fixedThumbs') )
  2505. addClass( cont, 'viewer-thumb-list-fixed' );
  2506. if( userOptions.val('fixedTags') )
  2507. addClass( cont, 'viewer-tag-list-fixed' );
  2508. doc.body.appendChild(cont);
  2509. }
  2510. if( !cont.classList.contains(this.classActive) )
  2511. {
  2512. cont.addEventListener('click', handleViewerNavigationEvent, false);
  2513. cont.classList.add(this.classActive);
  2514. activateViewerKeyboard();
  2515. var activateSidebar = function(elm)
  2516. {
  2517. if( elm )
  2518. {
  2519. elm.addEventListener('mouseover', function(){this.focus();}, false);
  2520. elm.addEventListener('mouseout', function(){this.blur();}, false);
  2521. }
  2522. };
  2523. activateSidebar(doc.querySelector('#' + this.tagsId ));
  2524. activateSidebar(doc.querySelector('#' + this.thumbsId ));
  2525. }
  2526. },
  2527. showNext: function(){
  2528. if( !this.isActive() )
  2529. return;
  2530. try{
  2531. var idx = (this.curr + this.total + 1)%this.total;
  2532. viewImage(idx);
  2533. }catch(e){console.error(e);}
  2534. },
  2535. showPrev: function(){
  2536. if( !this.isActive() )
  2537. return;
  2538. try{
  2539. var idx = (this.curr + this.total - 1)%this.total;
  2540. viewImage(idx);
  2541. }catch(e){console.error(e);}
  2542. },
  2543. isActive: function(){
  2544. if( !this.btn ) this.btn = document.querySelector('#' + this.buttonId);
  2545. return hasClass( this.btn, this.classOn );
  2546. },
  2547. viewerOn: function(){
  2548. if( !this.btn ) this.btn = document.querySelector('#' + this.buttonId);
  2549. if( !this.cont ) this.cont = document.querySelector('#' + this.containerId);
  2550. toggleClass(this.btn, this.classOn, this.classOff);
  2551. toggleClass(this.cont, this.classOn, this.classOff);
  2552. this.setOverflow = this.setOverflow || function(elm, val){if(elm) elm.style.overflow = val;};
  2553. try{
  2554. var html = this.cont.querySelector('#' + this.currentId).textContent;
  2555. if( userOptions.val('viewFirst') && html === '-' )
  2556. viewImage(0);
  2557. else
  2558. viewImage('-');
  2559. this.setOverflow( document.body || document.getElementsByTagName('body')[0], 'hidden' );
  2560. this.setOverflow( document.getElementsByTagName('html')[0], 'hidden' );
  2561. }catch(e){console.error(e);}
  2562. resumeVideo(this.curr);
  2563. },
  2564. viewerOff: function(){
  2565. this.setOverflow = this.setOverflow || function(elm, val){if(elm) elm.style.overflow = val;};
  2566. this.setOverflow( document.body || document.getElementsByTagName('body')[0], 'auto' );
  2567. this.setOverflow( document.getElementsByTagName('html')[0], 'auto' );
  2568. toggleClass(this.btn, this.classOff, this.classOn);
  2569. toggleClass(this.cont, this.classOff, this.classOn);
  2570. historyChange( null );
  2571. pauseVideo(this.curr);
  2572. },
  2573. };
  2574. return retVal;
  2575. }
  2576. //--------------------------------------- VIEWER -------------------------------------//
  2577. //------------------------------------------------------------------------------------//
  2578. //-------------------------------------- VIEWER-2 ------------------------------------//
  2579. function activateViewerKeyboard()
  2580. {
  2581. window.addEventListener('keydown', handleViewerKeyboardEvent, false);
  2582. }
  2583. function deactivateViewerKeyboard()
  2584. {
  2585. window.removeEventListener('keydown', handleViewerKeyboardEvent, false);
  2586. }
  2587. function handleViewerKeyboardEvent(event)
  2588. {
  2589. var charCode = event.keyCode || event.which,
  2590. useCtrl = userOptions.val('holdCtrl') || window.location.hostname.indexOf('donmai.us') != -1,
  2591. condition1 = event.shiftKey || !event.ctrlKey || event.altKey,
  2592. condition2 = event.shiftKey || event.ctrlKey || event.altKey;
  2593. if( (useCtrl && condition1) || (!useCtrl && condition2) )
  2594. return;
  2595. var viewer = imageBoard.imgBrdVw;
  2596. if( charCode == 37 )
  2597. viewer.showPrev();
  2598. else if( charCode == 39 )
  2599. viewer.showNext();
  2600. }
  2601. function handleViewerEvent(event)
  2602. {
  2603. if( !imageBoard.imgBrdCl.hasClass( this, 'viewActive' ) )
  2604. return;
  2605. event.preventDefault();
  2606. var t = event.target;
  2607. if( t.tagName !== 'IMG' )
  2608. t = t.firstChild;
  2609. if( t.tagName !== 'IMG' )
  2610. return;
  2611. var idx = imageBoard.imgBrdDt.val(t, 'index');
  2612. clog("[handleViewerEvent] index: " + idx);
  2613. if( idx !== null && idx !== undefined )
  2614. viewImage( idx );
  2615. else
  2616. console.error("image index not found, img: ", t.src);
  2617. }
  2618. function viewImage( idx )
  2619. {
  2620. if( !imageBoard )
  2621. return;
  2622. var viewer = imageBoard.imgBrdVw,
  2623. hostname = window.location.hostname,
  2624. div, imgD, dwSource;
  2625. if( !viewer || !viewer.isActive() )
  2626. return;
  2627. idx = parseInt(idx, 10);
  2628. idx = (viewer.total + idx)%viewer.total;
  2629. makeThumbListHTML();
  2630. makeImageListHTML();
  2631. imgD = imageBoard.images.list[idx];
  2632. if( !imgD || imgD.state !== 'ready' )
  2633. return;
  2634. setImageList(true);// loop over all .viewer-img-list > div
  2635. if( hostname.indexOf('sankakucomplex') != -1 )
  2636. historyChange( imgD.postUrl );
  2637. div = document.querySelector('div[data-image-index="' + imgD.index + '"]');
  2638. loadImage(imgD.index, div);
  2639. if( userOptions.val('downloadJPEG') && imgD.jpegSource )
  2640. dwSource = imgD.jpegSource;
  2641. else
  2642. dwSource = imgD.source;
  2643. removeClass( document.querySelectorAll('div.viewer-img-div'), 'img-show' );
  2644. addClass( div, 'img-show' );
  2645. makeTagListHTML(idx);
  2646. setViewerBottom( viewer, dwSource, imgD.name );
  2647. pauseVideo(viewer.curr);
  2648. viewer.curr = idx;
  2649. }
  2650. //------------------------------------------------------------------------------------//
  2651. //--------------------------------- VIEWER-THUMB-LIST --------------------------------//
  2652. function makeThumbListHTML()
  2653. {
  2654. var thumbListDiv = document.querySelector('.viewer-thumb-list');
  2655. if( !thumbListDiv )
  2656. {
  2657. console.error("[makeThumbListHTML] can't find div");
  2658. return;
  2659. }
  2660. var imageList = imageBoard.images.list,
  2661. thumbTitle = '', html = '',
  2662. oldLen = thumbListDiv.getAttribute('data-viewer-thumb-length') || 0,
  2663. animate = userOptions.val('animateProgress'),
  2664. showProgress = userOptions.val('showProgress'),
  2665. i, len, imgD, img;
  2666. oldLen = parseInt(oldLen, 10);
  2667. for( i = oldLen, len = imageList.length; i < len; ++i )
  2668. {
  2669. imgD = imageList[i];
  2670. if( imgD.tags )
  2671. thumbTitle = imgD.tags.join(' ');
  2672. else if( imgD.thumbTitle )
  2673. thumbTitle = imgD.thumbTitle;
  2674. else
  2675. thumbTitle = '';
  2676. html += '<div class="viewer-thumb-div" data-viewer-thumb-div-index="' + imgD.index + '">';
  2677. html += '<span class="viewer-thumb-span">';
  2678. html += '<img data-viewer-thumb-index="' + imgD.index + '" ' +
  2679. 'class="viewer-thumb" src="' + (imgD.thumbSource || imgD.lowresSource) + '" ' +
  2680. 'title="' + thumbTitle + '"/>';
  2681. html += '</span>';
  2682. html += (showProgress ? makeProgressBarHTML( imgD, animate) : '');
  2683. html += '</div>';
  2684. }
  2685. thumbListDiv.setAttribute('data-viewer-thumb-length', len);
  2686. thumbListDiv.insertAdjacentHTML('beforeend', html);
  2687. if( hasClass(thumbListDiv, 'viewer-thumb-list-activated') )
  2688. return;
  2689. thumbListDiv.addEventListener('click', handleViewerThumbEvent, false);
  2690. addClass(thumbListDiv, 'viewer-thumb-list-activated');
  2691. }
  2692. function makeProgressBarHTML( imgD, animate )
  2693. {
  2694. return '<div class="progress-bar">' +
  2695. '<div id="progress-stripe-' + imgD.index + '" class="progress-stripe progress-counted' +
  2696. (imgD.state === 'ready' ? ' image-ready' : '') + '' +
  2697. (imgD.state === 'busy' && !!animate ? ' animate-progess' : '') + '' +
  2698. (imgD.downloadState === 'inProgress' ? ' download-in-progress' : '') + '' +
  2699. (imgD.downloadState === 'downloaded' ? ' progress-complete': '') + '' +
  2700. '"></div></div>';
  2701. }
  2702. function showViewerProgressBar( showProgress )
  2703. {
  2704. var animate = userOptions.val('animateProgress'),
  2705. divList = document.querySelectorAll('.viewer-thumb-div'),
  2706. imageList = imageBoard.images.list,
  2707. animate = userOptions.val('animateProgress'),
  2708. html, i, len, div, index, imgD, stripe;
  2709. for( i = 0, len = divList.length; i < len; ++i )
  2710. {
  2711. div = divList[i];
  2712. index = div.getAttribute('data-viewer-thumb-div-index');
  2713. stripe = div.querySelector('#progress-stripe-' + index);
  2714. if( !showProgress )
  2715. hide(stripe);
  2716. else if( !stripe )
  2717. {
  2718. imgD = imageList[index];
  2719. html = makeProgressBarHTML(imgD, animate);
  2720. div.insertAdjacentHTML('beforeend', html);
  2721. }else
  2722. show(stripe);
  2723. }
  2724. }
  2725. function handleViewerThumbEvent(event)
  2726. {
  2727. var t = event.target;
  2728. if( t.tagName !== 'IMG' )
  2729. return;
  2730. var idx = t.getAttribute('data-viewer-thumb-index');
  2731. viewImage(idx);
  2732. }
  2733. //--------------------------------- VIEWER-THUMB-LIST --------------------------------//
  2734. //------------------------------------------------------------------------------------//
  2735. //--------------------------------- VIEWER-IMAGE-LIST --------------------------------//
  2736. function makeImageListHTML()
  2737. {
  2738. var imgListDiv = document.querySelector('.viewer-img-list');
  2739. if( !imgListDiv )
  2740. return;
  2741. var imageList = imageBoard.images.list,
  2742. imgLen = imgListDiv.getAttribute('data-image-list-length') || 0,
  2743. html = '', i, len;
  2744. imgLen = parseInt(imgLen, 10);
  2745. for( i = imgLen, len = imageList.length; i < len; ++i )
  2746. {
  2747. html += '<div data-image-index="' + i + '" class="viewer-img-div">';
  2748. html += '<video class="vid_file" controls loop></video>';
  2749. html += '<img class="orig_img"></img>';
  2750. html += '<img class="jpeg_img"></img>';
  2751. html += '<img class="samp_img"></img>';
  2752. html += '</div>';
  2753. }
  2754. imgListDiv.setAttribute('data-image-list-length', len);
  2755. imgListDiv.insertAdjacentHTML('beforeend', html);
  2756. }
  2757. function setImageList( reset )
  2758. {
  2759. var imgListDiv = document.querySelector('.viewer-img-list');
  2760. if( !imgListDiv )
  2761. return;
  2762. var imageDivs = imgListDiv.querySelectorAll('.viewer-img-div'),
  2763. imageList = imageBoard.images.list,
  2764. imgLen = imgListDiv.getAttribute('data-image-list-length') || 0,
  2765. viewOriginal = userOptions.val('viewOriginal'),
  2766. viewJPEG = userOptions.val('viewJPEG'),
  2767. i, imgD, imgDiv, viewType;
  2768. imgLen = parseInt(imgLen, 10);
  2769. for( i = 0; i < imgLen; ++i )
  2770. {
  2771. imgD = imageList[i];
  2772. imgDiv = imageDivs[i];
  2773. viewType = imgDiv.getAttribute('data-image-view-type');
  2774. if( reset || !viewType || viewType === 'none_src' )
  2775. imgDiv.setAttribute('data-image-view-type', getImageViewType(imgD, viewOriginal, viewJPEG) );
  2776. }
  2777. }
  2778. function getImageViewType( imgD, viewOriginal, viewJPEG )
  2779. {
  2780. if( imgD['vid_file-source'] )
  2781. imgD.viewType = 'vid_file';
  2782. else if( imgD['jpeg_img-source'] && viewJPEG && viewOriginal )
  2783. imgD.viewType = 'jpeg_img';
  2784. else if( imgD['orig_img-source'] && (viewOriginal || !imgD['samp_img-source']) )
  2785. imgD.viewType = 'orig_img';
  2786. else if( imgD['samp_img-source'] )
  2787. imgD.viewType = 'samp_img';
  2788. else
  2789. imgD.viewType = 'none_src';
  2790. return imgD.viewType;
  2791. }
  2792. //--------------------------------- VIEWER-IMAGE-LIST --------------------------------//
  2793. //------------------------------------------------------------------------------------//
  2794. //------------------------------------ LOAD-IMAGE ------------------------------------//
  2795. function loadImage(idx, div, isNext)
  2796. {
  2797. idx = parseInt(idx, 10);
  2798. div = div || document.querySelector('div[data-image-index="' + idx + '"]');
  2799. var viewType = div.getAttribute('data-image-view-type');
  2800. if( !viewType || viewType === 'none_src' )
  2801. return;
  2802. var img = div.querySelector('.' + viewType),
  2803. //complete = (img.tagName === 'IMG' && img.complete),// || (img.tagName === 'VIDEO' && img.readyState > 2),
  2804. imgD = imageBoard.images.list[idx],
  2805. total = imageBoard.imgBrdVw.total,
  2806. curr = imageBoard.imgBrdVw.curr,
  2807. diff = (total + idx - curr)%total,
  2808. next = (total + idx + diff)%total;
  2809. if( !!isNext && img.tagName === 'VIDEO' )
  2810. return;
  2811. else if( !img.src )
  2812. img.src = imgD[viewType + '-source'];
  2813. if( (diff == 1 || diff == (total-1)) && total > 1 )
  2814. {
  2815. if( (img.tagName === 'IMG' && img.complete) || img.tagName === 'VIDEO' )
  2816. loadImage( next, diff == 1 ? div.nextSibling : div.previousSibling, true );
  2817. else{
  2818. img.setAttribute('data-index-diff', (diff == 1 ? 1 : -1) );
  2819. img.setAttribute('data-index-next', next );
  2820. img.addEventListener('load', preloadImageEvent, false);
  2821. }
  2822. }
  2823. if( img.tagName === 'VIDEO' && img.src )
  2824. resumeVideo( idx, div, img );
  2825. }
  2826. function preloadImageEvent(event)
  2827. {
  2828. var t = this,
  2829. p = t.parentNode,
  2830. diff = t.getAttribute('data-index-diff'),
  2831. next = t.getAttribute('data-index-next');
  2832. if( diff == 1 )
  2833. loadImage(next, p.nextSibling, true);
  2834. else if( diff == -1 )
  2835. loadImage(next, p.previousSibling, true);
  2836. else
  2837. loadImage(next);
  2838. t.removeAttribute('data-index-diff');
  2839. t.removeAttribute('data-index-next');
  2840. setTimeout(function(){
  2841. t.removeEventListener('load', preloadImageEvent, false);
  2842. }, 100);
  2843. }
  2844. function getVideoElm( idx, div, img )
  2845. {
  2846. if( !img || img.tagName === 'VIDEO' )
  2847. {
  2848. div = div || document.querySelector('[data-image-index="' + idx + '"]');
  2849. if( div.getAttribute('data-image-view-type') != 'vid_file' )
  2850. return;
  2851. img = div.querySelector('.vid_file');
  2852. }
  2853. return img;
  2854. }
  2855. function pauseVideo( idx, div, img )
  2856. {
  2857. img = getVideoElm( idx, div, img );
  2858. if( !img )
  2859. return;
  2860. else if( img.paused )
  2861. addClass( img, 'video-paused');
  2862. else
  2863. removeClass( img, 'video-paused');
  2864. img.pause();
  2865. }
  2866. function resumeVideo( idx, div, img, forcePause )
  2867. {
  2868. img = getVideoElm( idx, div, img );
  2869. if( !img )
  2870. return;
  2871. else if( forcePause || hasClass(img, 'video-paused') )
  2872. img.pause();
  2873. else
  2874. img.play();
  2875. }
  2876. //------------------------------------------------------------------------------------//
  2877. //---------------------------------- VIEWER-TAG-LIST ---------------------------------//
  2878. function makeTagListHTML( idx )
  2879. {
  2880. var tagListDiv = document.querySelector('.viewer-tag-list');
  2881. if( !tagListDiv )
  2882. {
  2883. console.error("[makeTagListHTML] can't find div");
  2884. return;
  2885. }
  2886. var imgD = imageBoard.images.list[idx],
  2887. tagList = imgD.tagList,
  2888. tagClass = (tagList && tagList[0] || {'class': ''}).class,
  2889. templ, html = '';
  2890. if( tagClass === 'tag-type' )
  2891. templ = '<li class="tag-type-';
  2892. else if( tagClass === 'category' )
  2893. templ = '<li class="category-';
  2894. else
  2895. templ = '<li class="empty-category';
  2896. html = '<h4 style="color:#a0a0a0;">Tags</h4>';
  2897. for( var i = 0, tagObj; i < tagList.length; ++i )
  2898. {
  2899. tagObj = tagList[i];
  2900. html += templ + (tagObj.category !== null ? tagObj.category : '') + '">';
  2901. html += makeTagHTML( tagObj );
  2902. html += '</li>';
  2903. }
  2904. tagListDiv.innerHTML = html;
  2905. }
  2906. function makeTagHTML( tagObj )
  2907. {
  2908. var html = (tagObj.wiki ? '<a href="' + tagObj.wiki + '"> ? </a>' : '') + '' +
  2909. '<a href="' + tagObj.href + '">' + tagObj.name + '</a>' +
  2910. '<span class="post-count" style="color:#a0a0a0;"> ' + (!!tagObj.count ? tagObj.count : '') + ' </span>';
  2911. return html;
  2912. }
  2913. //---------------------------------- VIEWER-TAG-LIST ---------------------------------//
  2914. //------------------------------------------------------------------------------------//
  2915. function historyChange( postURL )
  2916. {
  2917. this.href = this.href || window.location.href;
  2918. if( postURL )
  2919. window.history.replaceState(null, null, postURL);
  2920. else
  2921. window.history.replaceState(null, null, this.href);
  2922. }
  2923. function setViewerBottom( viewer, source, name )
  2924. {
  2925. var doc = document,
  2926. prevElm = doc.querySelector('#' + viewer.prevId ),
  2927. nextElm = doc.querySelector('#' + viewer.nextId ),
  2928. sourceElm = doc.querySelector('#' + viewer.sourceId ),
  2929. downloadElm = doc.querySelector('#' + viewer.downloadId ),
  2930. useCtrl = userOptions.val('holdCtrl') || window.location.hostname.indexOf('donmai.us') != -1;
  2931. prevElm.setAttribute('title', (useCtrl ? 'Ctrl+' : '') + 'Left');
  2932. nextElm.setAttribute('title', (useCtrl ? 'Ctrl+' : '') + 'Right');
  2933. sourceElm.setAttribute('title', source );
  2934. downloadElm.setAttribute('title', name );
  2935. }
  2936. function handleViewerSwitchEvent(event)
  2937. {
  2938. if( imageBoard.viewer.isActive() )
  2939. {
  2940. imageBoard.viewer.deactivate();
  2941. }else{
  2942. imageBoard.viewer.activate();
  2943. }
  2944. }
  2945. function handleViewerNavigationEvent(event)
  2946. {
  2947. var t = event.target,
  2948. viewer, idx, total, imgD;
  2949. if( !hasClass(t, this.getAttribute('data-class-button')) )
  2950. return;
  2951. viewer = imageBoard.imgBrdVw;
  2952. if( !viewer )
  2953. return;
  2954. idx = viewer.curr;
  2955. total = viewer.total;
  2956. clog("[navigation] index: " + idx);
  2957. clog("[navigation] total: " + total);
  2958. idx = parseInt(idx, 10);
  2959. total = parseInt(total, 10);
  2960. if( t.id == this.getAttribute('data-prev-id') )
  2961. {
  2962. viewImage( (idx + total - 1)%total );
  2963. }
  2964. else if( t.id == this.getAttribute('data-next-id') )
  2965. {
  2966. viewImage( (idx + total + 1)%total );
  2967. }
  2968. else if( t.id == this.getAttribute('data-download-id') )
  2969. {
  2970. imgD = imageBoard.images.list[idx];
  2971. downloadFile( imgD );
  2972. }
  2973. else if( t.id == this.getAttribute('data-source-id') )
  2974. {
  2975. imgD = imageBoard.images.list[idx];
  2976. window.open( imgD.source );
  2977. }
  2978. else if( t.id == this.getAttribute('data-close-id') )
  2979. {
  2980. handleViewerSwitchEvent();
  2981. }
  2982. }
  2983. //-------------------------------------- VIEWER-2 ------------------------------------//
  2984. //------------------------------------------------------------------------------------//
  2985. //------------------------------------- USER MENU ------------------------------------//
  2986. function initUserMenu()
  2987. {
  2988. var retVal = {
  2989. data: {
  2990. 'container-id': 'image-board-user-menu-container-' + RANDOM,
  2991. 'container-class': 'image-board-user-menu-container',
  2992. 'title-class': 'image-board-user-menu-title',
  2993. 'content-class': 'image-board-user-menu-content',
  2994. 'bottom-class': 'image-board-user-menu-bottom',
  2995. 'open-class': 'image-board-user-menu-open',
  2996. 'close-class': 'image-board-user-menu-close',
  2997. },
  2998. get containerId(){return this.data['container-id'];},
  2999. get containerClass(){return this.data['container-class'];},
  3000. get titleClass(){return this.data['title-class'];},
  3001. get contentClass(){return this.data['content-class'];},
  3002. get bottomClass(){return this.data['bottom-class'];},
  3003. get openClass(){return this.data['open-class'];},
  3004. get closeClass(){return this.data['close-class'];},
  3005. init: function(id, doc){
  3006. doc = doc || document;
  3007. var div = doc.querySelector('div#' + id), btn;
  3008. clog("div: ", div, id);
  3009. if( !div )
  3010. {
  3011. console.error("[initUserMenu] can't find div#" + id);
  3012. return;
  3013. }
  3014. var userMenuId = 'image-board-user-menu-id-' + RANDOM,
  3015. userMenuActive = 'image-board-user-menu-button-active',
  3016. userMenuBtn = div.querySelector('#' + userMenuId );
  3017. if( !userMenuBtn )
  3018. {
  3019. btn = document.createElement('button');
  3020. btn.setAttribute('id', userMenuId );
  3021. for( var key in this.data )
  3022. btn.setAttribute('data-' + key, this.data[key] );
  3023. btn.setAttribute('title', 'Press \'Shift+M\' to open/close User Menu');
  3024. btn.appendChild(document.createTextNode('User Menu'));
  3025. userMenuBtn = div.insertBefore( btn, div.firstChild );
  3026. }
  3027. return div;
  3028. },
  3029. };
  3030. return retVal;
  3031. }
  3032. function handleUserMenuEvent(event)
  3033. {
  3034. var dataSet = (this.dataset && this.dataset.containerId ? this.dataset : imageBoard.userMenu ),
  3035. div = document.querySelector('#' + dataSet.containerId ),
  3036. body = document.body, contentHtml, bottomHtml,
  3037. html = '';
  3038. if( !div )
  3039. {
  3040. contentHtml = makeUserMenuContentHtml();
  3041. bottomHtml = makeUserMenuBottomHtml();
  3042. div = document.createElement('div');
  3043. div.setAttribute('id', dataSet.containerId);
  3044. div.setAttribute('class', dataSet.containerClass);
  3045. html = '' +
  3046. '<div class="' + dataSet.titleClass + '">' +
  3047. '<span>' + GM.info.script.name + ' v' + GM.info.script.version + '</span>' +
  3048. '<span class="image-board-user-menu-x"></span>' +
  3049. '</div>' +
  3050. '<div class="' + dataSet.contentClass + '">' + contentHtml + '</div>' +
  3051. '<div class="' + dataSet.bottomClass + '">' + bottomHtml + '</div>';
  3052. div.insertAdjacentHTML('beforeend', html);
  3053. div = body.appendChild(div);
  3054. addClass( div, dataSet.closeClass );
  3055. activateUserMenu();
  3056. }
  3057. if( hasClass(div, dataSet.openClass) )
  3058. {
  3059. closeUserMenu.call(this);
  3060. imageBoard.imgBrdSt.userMenu = false;
  3061. }
  3062. else if( hasClass(div, dataSet.closeClass) )
  3063. {
  3064. openUserMenu.call(this);
  3065. imageBoard.imgBrdSt.userMenu = true;
  3066. }
  3067. }
  3068. function makeUserMenuContentHtml()
  3069. {
  3070. var typeList = ['checkbox', 'number', 'text'],
  3071. longOptions = ['tagsOrder', 'ignoredTags'],
  3072. html = '', key, dt, inp, labl, inputWidth, i, k, catName, keyList;
  3073. html += '<div class="image-board-user-menu-tabs-navigation">';
  3074. html += '<ul class="image-board-user-menu-tabs-list">';
  3075. for( i = 0; i < userOptions.categoryList.length; ++i )
  3076. {
  3077. catName = userOptions.categoryList[i];
  3078. html += '<li id="image-board-user-menu-tab-nav-' + catName.replace(/\s+/g, '-') + '" ' +
  3079. 'class="' + (catName === 'General' ? 'tab-nav-active' : '') + '"><span>' + catName + '</span></li>';
  3080. }
  3081. html += '</ul></div>';
  3082. html += '<div class="image-board-user-menu-tabs-content">';
  3083. for( i = 0; i < userOptions.categoryList.length; ++i )
  3084. {
  3085. catName = userOptions.categoryList[i];
  3086. keyList = userOptions.category(catName);
  3087. html += '<div id="image-board-user-menu-tab-' + catName.replace(/\s+/g, '-') + '" ' +
  3088. 'tab-selected="' + (catName === 'General' ? 'true' : 'false') + '">';
  3089. for( k = 0; k < keyList.length; ++k )
  3090. {
  3091. key = keyList[k];
  3092. dt = userOptions.data[key];
  3093. if( typeList.indexOf(dt.type) == -1 )
  3094. continue;
  3095. inputWidth = (longOptions.indexOf(key) != -1 ? '200px' : '70px');
  3096. inp = '<input id="image-board-user-menu-' + key + '-val" type="' + dt.type + '" ' +
  3097. 'style="' + (dt.type!=='checkbox' ? 'text-align: center; width: ' + inputWidth: '') + '"/>';
  3098. labl = '<label id="image-board-user-menu-' + key + '-caption" ' + (key == 'holdCtrl' ? 'title="Hodor" ': '') + '' +
  3099. 'for="image-board-user-menu-' + key + '-val" style="cursor: pointer;">' + dt.getDesc() + '</label>';
  3100. html += '<section class="image-board-user-menu-section">' +
  3101. (dt.type === 'checkbox' ? inp + labl : labl + inp ) + '</section>';
  3102. }
  3103. html += '</div>';
  3104. }
  3105. html += '</div>';
  3106. return html;
  3107. }
  3108. function getUserOptionsListOf( prop )
  3109. {
  3110. var propList = [], key, dt;
  3111. for( key in userOptions.data )
  3112. {
  3113. dt = userOptions.data[key];
  3114. if( propList.indexOf(dt[prop]) == -1 )
  3115. propList.push(dt[prop]);
  3116. }
  3117. return propList;
  3118. }
  3119. function makeUserMenuBottomHtml()
  3120. {
  3121. this.btnList = this.btnList || {
  3122. 'reset': {
  3123. html: 'Reset',
  3124. title: 'Reset all options to default ones',
  3125. },
  3126. 'remove': {
  3127. html: 'Remove',
  3128. title: 'Remove all saved options',
  3129. },
  3130. 'save': {
  3131. html: 'Save Settings',
  3132. title: '',
  3133. },
  3134. };
  3135. var key, val, html = '';
  3136. for( key in this.btnList )
  3137. {
  3138. val = this.btnList[key];
  3139. html += '<button id="image-board-user-menu-' + key + '-button" class="user-menu-button" ' +
  3140. 'title="' + val.title + '">' + val.html + '</button>';
  3141. }
  3142. return html;
  3143. }
  3144. function activateUserMenu()
  3145. {
  3146. var doc = document,
  3147. active = 'image-board-user-menu-button-active',
  3148. btn, key;
  3149. var userMenuMethodsObj = {
  3150. 'save': saveUserMenu,
  3151. 'remove': removeUserMenu,
  3152. 'reset': resetUserMenu,
  3153. 'x': closeUserMenu,
  3154. 'tabs-navigation': tabsUserMenu,
  3155. };
  3156. for( key in userMenuMethodsObj )
  3157. {
  3158. btn = doc.querySelector('#image-board-user-menu-' + key + '-button');
  3159. if( !btn )
  3160. btn = doc.querySelector('.image-board-user-menu-' + key );
  3161. if( btn && !btn.classList.contains(active) )
  3162. {
  3163. btn.addEventListener('click', userMenuMethodsObj[key], false );
  3164. btn.classList.add(active);
  3165. }
  3166. }
  3167. }
  3168. function setUserMenu()
  3169. {
  3170. var doc = document,
  3171. key, dt, valueElm, captionElm;
  3172. for( key in userOptions.data )
  3173. {
  3174. dt = userOptions.data[key];
  3175. valueElm = doc.querySelector('#image-board-user-menu-' + key + '-val');
  3176. if( !valueElm )
  3177. continue;
  3178. else if( dt.type === 'checkbox' )
  3179. valueElm.checked = dt.val;
  3180. else if( dt.type === 'number' || dt.type === 'text' )
  3181. valueElm.value = _toString_( dt.val, ', ' );
  3182. captionElm = doc.querySelector('#image-board-user-menu-' + key + '-caption');
  3183. if( captionElm )
  3184. captionElm.textContent = dt.getDesc();
  3185. }
  3186. }
  3187. function saveUserMenu()
  3188. {
  3189. var doc = document,
  3190. key, dt, valueElm;
  3191. for( key in userOptions.data )
  3192. {
  3193. dt = userOptions.data[key];
  3194. valueElm = doc.querySelector('#image-board-user-menu-' + key + '-val');
  3195. if( !valueElm )
  3196. continue;
  3197. else if( dt.type === 'checkbox' )
  3198. userOptions.val(key, valueElm.checked );
  3199. else if( dt.type === 'number' || dt.type === 'text' )
  3200. userOptions.val( key, valueElm.value );
  3201. }
  3202. userOptions.saveData();
  3203. closeUserMenu();
  3204. renameImages();
  3205. resetViewerSettings();
  3206. showViewerProgressBar( userOptions.val('showProgress') );
  3207. }
  3208. function renameImages()
  3209. {
  3210. if( !imageBoard )
  3211. return;
  3212. try{
  3213. var list = imageBoard.images.list,
  3214. site = imageBoard.siteList.val();
  3215. for( var i = 0, len = list.length; i < len; ++i )
  3216. site.setImageDataName( list[i] );
  3217. }catch(error){
  3218. console.error(error);
  3219. }
  3220. }
  3221. function resetViewerSettings()
  3222. {
  3223. if( !imageBoard )
  3224. return;
  3225. var viewer = imageBoard.imgBrdVw,
  3226. container = document.querySelector('#' + viewer.containerId);
  3227. if( userOptions.val('fixedThumbs') )
  3228. addClass( container, 'viewer-thumb-list-fixed' );
  3229. else
  3230. removeClass( container, 'viewer-thumb-list-fixed' );
  3231. if( userOptions.val('fixedTags') )
  3232. addClass( container, 'viewer-tag-list-fixed' );
  3233. else
  3234. removeClass( container, 'viewer-tag-list-fixed' );
  3235. }
  3236. function removeUserMenu()
  3237. {
  3238. userOptions.removeData();
  3239. }
  3240. function resetUserMenu()
  3241. {
  3242. userOptions.setDefs();
  3243. userOptions.saveData();
  3244. setUserMenu();
  3245. renameImages();
  3246. }
  3247. function closeUserMenu()
  3248. {
  3249. var dataSet = imageBoard.userMenu.data,
  3250. userMenu = document.querySelector('#' + dataSet['container-id']);
  3251. toggleClass( userMenu, dataSet['close-class'], dataSet['open-class'] );
  3252. imageBoard.imgBrdSt.userMenu = false;
  3253. }
  3254. function openUserMenu()
  3255. {
  3256. var dataSet = imageBoard.userMenu.data,
  3257. userMenu = document.querySelector('#' + dataSet['container-id']);
  3258. toggleClass( userMenu, dataSet['open-class'], dataSet['close-class'] );
  3259. setUserMenu();
  3260. imageBoard.imgBrdSt.userMenu = true;
  3261. }
  3262. function tabsUserMenu(event)
  3263. {
  3264. var t = event.target, categoryName, tabsNav, tabs, activeTab, i;
  3265. if( t.tagName === 'SPAN' )
  3266. t = t.parentNode;
  3267. if( t.tagName !== 'LI' )
  3268. return;
  3269. tabsNav = document.querySelectorAll('.image-board-user-menu-tabs-navigation li');
  3270. tabs = document.querySelectorAll('div[tab-selected]');
  3271. for( i = 0; i < tabs.length; ++i )
  3272. {
  3273. tabs[i].setAttribute('tab-selected', 'false');
  3274. removeClass( tabsNav[i], 'tab-nav-active' );
  3275. }
  3276. categoryName = t.id.replace('image-board-user-menu-tab-nav-', '');
  3277. activeTab = document.querySelector('#image-board-user-menu-tab-' + categoryName);
  3278. if( !activeTab )
  3279. return;
  3280. activeTab.setAttribute('tab-selected', 'true');
  3281. addClass(t, 'tab-nav-active');
  3282. }
  3283. //------------------------------------- USER MENU ------------------------------------//
  3284. //------------------------------------------------------------------------------------//
  3285. //------------------------------------ USER OPTIONS ----------------------------------//
  3286. async function initOptions()
  3287. {
  3288. function _setDef(){this.val = this.def;}
  3289. var tagsType = ['character', 'copyright', 'artist', 'species', 'model', 'idol', 'photo_set', 'circle', 'medium', 'metadata', 'general', 'faults'];
  3290. var retVal = {
  3291. data: {
  3292. 'autoRun': {
  3293. val: null,
  3294. def: true,
  3295. type: 'checkbox',
  3296. category: 'General',
  3297. setDef: _setDef,
  3298. getDesc: function(){return 'Initialize the Script on start';},
  3299. },
  3300. 'createViewer': {
  3301. val: null,
  3302. def: true,
  3303. type: 'checkbox',
  3304. category: 'General',
  3305. setDef: _setDef,
  3306. getDesc: function(){return 'Add Image Viewer to ImageBoard';},
  3307. },
  3308. 'downloadJPEG': {
  3309. val: null,
  3310. def: false,
  3311. type: 'checkbox',
  3312. category: 'General',
  3313. setDef: _setDef,
  3314. getDesc: function(){return 'Donwload jpeg instead of png (yande.re option)';},
  3315. },
  3316. 'animateProgress': {
  3317. val: null,
  3318. def: true,
  3319. type: 'checkbox',
  3320. category: 'General',
  3321. setDef: _setDef,
  3322. getDesc: function(){return 'Animate initialization/downloading progress'; },
  3323. },
  3324. 'maxTagsInName': {
  3325. val: null,
  3326. def: 10,
  3327. type: 'number',
  3328. category: 'Filename',
  3329. setDef: _setDef,
  3330. getDesc: function(){return 'Maximum tags in file name:';},
  3331. validator: function( v ){
  3332. return v > 3 && v < 100;
  3333. },
  3334. },
  3335. 'tagsOrder': {
  3336. _val: null,
  3337. set val(s){
  3338. if( !this._val )
  3339. this._val = [];
  3340. if( typeof s === 'string' )
  3341. s = s.split(/\s?\,\s?/i);
  3342. else if( !s )
  3343. s = tagsType;
  3344. //copy( this._val, s );
  3345. this._val.length = 0;
  3346. for( var i = 0; i < s.length; ++i )
  3347. this._val.push(s[i].trim());
  3348. },
  3349. get val(){return this._val;},
  3350. def: tagsType.join(','),
  3351. type: 'text',
  3352. category: 'Filename',
  3353. setDef: _setDef,
  3354. getDesc: function(){return 'Tags order in file name:';},
  3355. validator: function(s){
  3356. if( !s )
  3357. return false;
  3358. else if( typeof s === 'string' )
  3359. s = s.trim().split(/\s?\,\s?/i);
  3360. for( var i = 0, len = s.length; i < len; ++i )
  3361. {
  3362. if( tagsType.indexOf(s[i]) == -1 )
  3363. return false;
  3364. }
  3365. return true;
  3366. },
  3367. },
  3368. 'ignoredTags': {
  3369. _val: null,
  3370. set val(s){
  3371. if( !this._val )
  3372. this._val = [];
  3373. if( !s )
  3374. s = [];
  3375. else if( typeof s === 'string' )
  3376. s = s.trim().split(',');
  3377. //s.forEach(function(item){item = item.trim();});
  3378. this._val.length = 0;
  3379. for( var i = 0; i < s.length; ++i )
  3380. this._val.push(s[i].trim());
  3381. },
  3382. get val(){return this._val;},
  3383. def: '',
  3384. type: 'text',
  3385. category: 'Filename',
  3386. setDef: _setDef,
  3387. getDesc: function(){return 'Ignored tag names:';},
  3388. },
  3389. 'tagsDelim': {
  3390. val: null,
  3391. def: '-',
  3392. type: 'text',
  3393. category: 'Filename',
  3394. setDef: _setDef,
  3395. getDesc: function(){return 'Tags delimeter:';},
  3396. validator: function(v){
  3397. v = v.toString();
  3398. return v.length > 0 && v.length < 5;
  3399. },
  3400. },
  3401. 'addImgBrdName': {
  3402. val: null,
  3403. def: true,
  3404. type: 'checkbox',
  3405. category: 'Filename',
  3406. setDef: _setDef,
  3407. getDesc: function(){return 'Add ImageBoard name to file name';},
  3408. },
  3409. 'prefixedName': {
  3410. val: null,
  3411. def: false,
  3412. type: 'checkbox',
  3413. category: 'Filename',
  3414. setDef: _setDef,
  3415. getDesc: function(){return 'Prefixed ImageBoard name';},
  3416. },
  3417. 'imgIdAtNameEnd': {
  3418. val: null,
  3419. def: true,
  3420. type: 'checkbox',
  3421. category: 'Filename',
  3422. setDef: _setDef,
  3423. getDesc: function(){return 'Image ID, and ImageBoard name at file name end';},
  3424. },
  3425. 'viewOriginal': {
  3426. val: null,
  3427. def: false,
  3428. type: 'checkbox',
  3429. category: 'Viewer',
  3430. setDef: _setDef,
  3431. getDesc: function(){return 'View original images';},
  3432. },
  3433. 'viewJPEG': {
  3434. val: null,
  3435. def: false,
  3436. type: 'checkbox',
  3437. category: 'Viewer',
  3438. setDef: _setDef,
  3439. getDesc: function(){return 'View jpeg image (yande.re option)';},
  3440. },
  3441. 'viewFirst': {
  3442. val: null,
  3443. def: true,
  3444. type: 'checkbox',
  3445. category: 'Viewer',
  3446. setDef: _setDef,
  3447. getDesc: function(){return 'Load 1st image on viewer activation';},
  3448. },
  3449. 'holdCtrl': {
  3450. val: null,
  3451. def: false,
  3452. type: 'checkbox',
  3453. category: 'Viewer',
  3454. setDef: _setDef,
  3455. getDesc: function(){return 'Hold Ctrl key to left/right navigate when viewing';},
  3456. },
  3457. 'fixedTags': {
  3458. val: null,
  3459. def: true,
  3460. type: 'checkbox',
  3461. category: 'Viewer',
  3462. setDef: _setDef,
  3463. getDesc: function(){return 'Fix tag list';},
  3464. },
  3465. 'fixedThumbs': {
  3466. val: null,
  3467. def: false,
  3468. type: 'checkbox',
  3469. category: 'Viewer',
  3470. setDef: _setDef,
  3471. getDesc: function(){return 'Fix thumb list';},
  3472. },
  3473. 'showProgress': {
  3474. val: null,
  3475. def: true,
  3476. type: 'checkbox',
  3477. category: 'Viewer',
  3478. setDef: _setDef,
  3479. getDesc: function(){return 'Show progress/status bar';},
  3480. },
  3481. },
  3482. get storageKey(){ return 'user-options-storage-key';},
  3483. get categoryList()
  3484. {
  3485. var catList = [], key, opt;
  3486. for( key in this.data )
  3487. {
  3488. opt = this.data[key];
  3489. if( catList.indexOf(opt.category) == -1 )
  3490. catList.push(opt.category);
  3491. }
  3492. Object.defineProperty(this, 'categoryList', {
  3493. get: function(){return catList;},
  3494. enumerable: true,
  3495. configurable: true,
  3496. });
  3497. return catList;
  3498. },
  3499. category: function( categoryName )
  3500. {
  3501. if( this.keyList === undefined )
  3502. {
  3503. this.keyList = {};
  3504. var opt, key, list, i, catName;
  3505. for( i = 0; i < this.categoryList.length; ++i )
  3506. {
  3507. catName = this.categoryList[i];
  3508. list = this.keyList[catName] = [];
  3509. for( key in this.data )
  3510. {
  3511. opt = this.data[key];
  3512. if( opt.category === catName )
  3513. list.push(key);
  3514. }
  3515. }
  3516. Object.defineProperty(this, 'category', {
  3517. value: function(name)
  3518. {
  3519. if( this.categoryList.indexOf(name) == -1 )
  3520. return null;
  3521. return this.keyList[name];
  3522. },
  3523. enumerable: true,
  3524. configurable: true,
  3525. });
  3526. }
  3527. return this.keyList[categoryName];
  3528. },
  3529. val: function( opt, v )
  3530. {
  3531. if( this.data[opt] )
  3532. {
  3533. if( v !== undefined )
  3534. {
  3535. if( typeof this.data[opt].validator !== 'function' && v !== null )
  3536. this.data[opt].val = v;
  3537. else if( this.data[opt].validator(v) )
  3538. this.data[opt].val = v;
  3539. }
  3540. return this.data[opt].val;
  3541. }else
  3542. return null;
  3543. },
  3544. fixStorage: async function(){
  3545. // backward compatibility with v0.2.0 and older
  3546. var oldKey = 'user-options-storage-key-1681238',
  3547. objStr = await GM.getValue( oldKey, '' );
  3548. if( objStr )
  3549. {
  3550. GM.deleteValue(oldKey);
  3551. GM.setValue( this.storageKey, objStr );
  3552. }
  3553. // backward compatibility with v0.7.0 and older
  3554. var obj = null;
  3555. objStr = await GM.getValue(this.storageKey, '');
  3556. if( objStr )
  3557. obj = JSON.parse(objStr);
  3558. if( obj && obj.viewSample !== undefined )
  3559. {
  3560. obj.viewOriginal = !obj.viewSample;
  3561. delete obj.viewSample;
  3562. GM.deleteValue(this.storageKey);
  3563. GM.setValue(this.storageKey, JSON.stringify(obj));
  3564. }
  3565. },
  3566. saveData: function(){
  3567. var storageObj = {};
  3568. for( var key in this.data )
  3569. storageObj[key] = this.data[key].val;
  3570. GM.setValue( this.storageKey, JSON.stringify(storageObj) );
  3571. },
  3572. removeData: function(){
  3573. GM.deleteValue(this.storageKey);
  3574. },
  3575. loadData: async function(){
  3576. var storageObj = await GM.getValue(this.storageKey, null), v;
  3577. if( storageObj )
  3578. storageObj = JSON.parse(storageObj);
  3579. else
  3580. storageObj = {};
  3581. clog('storage [loadData]:', storageObj);
  3582. for( var key in this.data )
  3583. {
  3584. v = storageObj[key];
  3585. if( v !== undefined )
  3586. {
  3587. clog('storage[' + key + ']: ', v);
  3588. this.val( key, v );
  3589. }else{
  3590. clog('storage[' + key + ']: ', '[default]');
  3591. this.data[key].setDef();
  3592. }
  3593. }
  3594. this.saveData();
  3595. },
  3596. setDefs: function(){
  3597. for( var key in this.data )
  3598. this.data[key].setDef();
  3599. this.saveData();
  3600. },
  3601. init: async function(){
  3602. await this.fixStorage();
  3603. await this.loadData();
  3604. },
  3605. };
  3606. await retVal.init();
  3607. clog('end [initOptions]');
  3608. return retVal;
  3609. }
  3610. //------------------------------------ USER OPTIONS ----------------------------------//
  3611. //------------------------------------------------------------------------------------//
  3612. function newCssClasses()
  3613. {
  3614. generalCssClass();
  3615. userMenuCssClass();
  3616. imageViewerCssClass();
  3617. progressBarCssClass();
  3618. }
  3619. function generalCssClass()
  3620. {
  3621. var id = 'general-css-' + RANDOM;
  3622. addCssClass(`
  3623. #image-board-div-${RANDOM} {
  3624. text-align: right;
  3625. position: relative;
  3626. }
  3627. #image-board-user-menu-container-${RANDOM} button,
  3628. #image-board-div-${RANDOM} button {
  3629. margin: 3px 10px;
  3630. color: ${imageBoard.siteList.style().color};
  3631. font-weight: bold;
  3632. width: 180px;
  3633. border: 0px;
  3634. padding: 5px;
  3635. background: ${imageBoard.siteList.style().background};
  3636. cursor: pointer;
  3637. }
  3638. .image-board-viewer-bottom-class button:hover ,
  3639. #image-board-user-menu-container-${RANDOM} button:hover ,
  3640. #image-board-div-${RANDOM} button:hover {
  3641. background: ${imageBoard.siteList.style().backgroundHover};
  3642. color: ${imageBoard.siteList.style().colorHover};
  3643. }
  3644. div.image-board-viewer-on ,
  3645. .image-board-user-menu-open {
  3646. display: initial;
  3647. }
  3648. div.image-board-viewer-off ,
  3649. .image-board-user-menu-close {
  3650. display: none;
  3651. }
  3652. .image-board-downloader-off::after {
  3653. content: " [off]";
  3654. }
  3655. .image-board-downloader-on::after {
  3656. content: " [on]";
  3657. }
  3658. button.image-board-viewer-off::after {
  3659. content: " [+]";
  3660. }
  3661. button.image-board-viewer-on::after {
  3662. content: " [\u2013]";
  3663. }
  3664. .image-board-active-for-view,
  3665. .image-board-active-for-download {
  3666. cursor: default;
  3667. }
  3668. `, id);
  3669. }
  3670. function userMenuCssClass()
  3671. {
  3672. var id = 'user-menu-css-' + RANDOM;
  3673. addCssClass(`
  3674. .image-board-user-menu-title,
  3675. #image-board-user-menu-container-${RANDOM} {
  3676. border-top-left-radius: 5px;
  3677. border-top-right-radius: 5px;
  3678. }
  3679. .image-board-user-menu-bottom,
  3680. #image-board-user-menu-container-${RANDOM} {
  3681. border-bottom-left-radius: 5px;
  3682. border-bottom-right-radius: 5px;
  3683. }
  3684. #image-board-user-menu-container-${RANDOM} {
  3685. position: fixed;
  3686. bottom: 10px;
  3687. right: 10px;
  3688. z-index: 100200;
  3689. background-color: #e7e7e7;
  3690. width: 40%;
  3691. height: 40%;
  3692. }
  3693. div.image-board-user-menu-title {
  3694. font-weight: bold;
  3695. line-height: 30px;
  3696. color: ${imageBoard.siteList.style().color};
  3697. background-color: ${imageBoard.siteList.style().background};
  3698. position: absolute;
  3699. width: 100%;
  3700. height: 30px;
  3701. }
  3702. div.image-board-user-menu-title > span {
  3703. padding-left: 8px;
  3704. }
  3705. .image-board-user-menu-x::after,
  3706. .image-board-user-menu-x::before {
  3707. content: "";
  3708. position: absolute;
  3709. width: 2px;
  3710. height: 1.5em;
  3711. background: ${imageBoard.siteList.style().color} !important;
  3712. display: block;
  3713. transform: rotate(45deg);
  3714. left: 50%;
  3715. margin: -1px 0 0 -1px;
  3716. top: 0;
  3717. }
  3718. .image-board-user-menu-x::before {
  3719. transform: rotate(-45deg);
  3720. }
  3721. .image-board-user-menu-x:hover::after,
  3722. .image-board-user-menu-x:hover::before {
  3723. background: ${imageBoard.siteList.style().colorHover} !important;
  3724. }
  3725. .image-board-user-menu-x:hover {
  3726. background: ${imageBoard.siteList.style().backgroundHover};
  3727. }
  3728. .image-board-user-menu-x {
  3729. position: absolute;
  3730. width: 1.3em;
  3731. height: 1.3em;
  3732. cursor: pointer;
  3733. top: 8px;
  3734. right: 1px;
  3735. }
  3736. div.image-board-user-menu-content {
  3737. background-color: #eeeeee;
  3738. overflow-x: auto;
  3739. overflow-y: hidden;
  3740. position: absolute;
  3741. top: 30px;
  3742. right: 0px;
  3743. bottom: 30px;
  3744. left: 0px;
  3745. }
  3746. div.image-board-user-menu-content label {
  3747. font-family: verdana, sans-serif;
  3748. font-weight: initial;
  3749. font-size: 12px;
  3750. color: #7d7d7d !important;
  3751. line-height: 30px;
  3752. display: initial !important;
  3753. white-space: initial !important;
  3754. }
  3755. .image-board-user-menu-content label {
  3756. padding: 0 3px;
  3757. }
  3758. .image-board-user-menu-tabs-navigation {
  3759. position: absolute;
  3760. left: 0;
  3761. width: 100px;
  3762. height: 100%;
  3763. overflow-y: auto;
  3764. background-color: #e0e0e0;
  3765. }
  3766. .image-board-user-menu-tabs-navigation ul {
  3767. padding: 0;
  3768. }
  3769. .image-board-user-menu-tabs-navigation li {
  3770. list-style-type: none;
  3771. color: #7d7d7d !important;
  3772. height: 30px;
  3773. line-height: 30px;
  3774. padding-left: 10px;
  3775. margin: 0;
  3776. cursor: pointer;
  3777. }
  3778. .image-board-user-menu-tabs-navigation li:hover ,
  3779. .image-board-user-menu-tabs-navigation li.tab-nav-active {
  3780. background-color: #d0d0d0;
  3781. }
  3782. .image-board-user-menu-tabs-navigation li.tab-nav-active {
  3783. font-weight: bold;
  3784. }
  3785. div.image-board-user-menu-tabs-content {
  3786. position: absolute;
  3787. left: 100px;
  3788. right: 0;
  3789. padding-left: 10px;
  3790. overflow-y: auto;
  3791. min-width: 240px;
  3792. height: 100%;
  3793. }
  3794. div.image-board-user-menu-tabs-content div {
  3795. display: none;
  3796. }
  3797. div.image-board-user-menu-tabs-content div[tab-selected="true"] {
  3798. display: initial;
  3799. }
  3800. .image-board-user-menu-bottom {
  3801. /*text-align: right;*/
  3802. background-color: ${imageBoard.siteList.style().background};
  3803. position: absolute;
  3804. bottom: 0px;
  3805. width: 100%;
  3806. height: 30px;
  3807. }
  3808. #image-board-user-menu-reset-button {
  3809. left: 10px;
  3810. }
  3811. #image-board-user-menu-save-button {
  3812. position: absolute;
  3813. right: 10px;
  3814. }
  3815. #image-board-user-menu-container-${RANDOM} button {
  3816. width: initial;
  3817. margin: 1px 2px;
  3818. padding: 4px 6px;
  3819. `, id);
  3820. }
  3821. function imageViewerCssClass()
  3822. {
  3823. var id = 'image-viewer-css-' + RANDOM,
  3824. col = '#000';
  3825. addCssClass(`
  3826. .image-board-viewer-container {
  3827. position: fixed;
  3828. top: 0;
  3829. right: 0;
  3830. bottom: 0;
  3831. left: 0;
  3832. z-index: 100100;
  3833. background-color: ${col};
  3834. }
  3835. button.image-board-viewer-btn {
  3836. cursor: pointer;
  3837. }
  3838. .viewer-tag-list li {
  3839. list-style-type: none;
  3840. line-height: 1.8em;
  3841. display: block;
  3842. padding-left: 4px;
  3843. }
  3844. .viewer-thumb-div,
  3845. .viewer-tag-list * {
  3846. background-color: #303030;
  3847. }
  3848. .viewer-tag-list li.category-0 a,
  3849. .viewer-tag-list li.tag-type-general a,
  3850. .viewer-tag-list li.empty-category a {
  3851. color: #337ab7;
  3852. }
  3853. div.viewer-tag-list-fixed > div.viewer-tag-list {
  3854. opacity: 1;
  3855. left: 0;
  3856. }
  3857. div.viewer-tag-list-fixed > div.viewer-img-list {
  3858. left: 200px;
  3859. }
  3860. .viewer-tag-list:hover {
  3861. opacity: 1;
  3862. left: 0;
  3863. }
  3864. .viewer-tag-list:hover + * + .viewer-img-list {
  3865. left: 200px;
  3866. }
  3867. .viewer-tag-list {
  3868. position: absolute;
  3869. width: 200px;
  3870. min-width: 50px;
  3871. top: 0;
  3872. left: -170px;
  3873. overflow-y: auto;
  3874. height: 100%;
  3875. /*padding: 3px 10px;*/
  3876. background-color: #303030;
  3877. opacity: 0.2;
  3878. transition: all 0.75s;
  3879. }
  3880. div.viewer-thumb-list-fixed > div.viewer-thumb-list {
  3881. opacity: 1;
  3882. right: 0;
  3883. }
  3884. div.viewer-thumb-list-fixed > div.viewer-img-list {
  3885. right: 200px;
  3886. }
  3887. .viewer-thumb-list:hover {
  3888. opacity: 1;
  3889. right: 0;
  3890. }
  3891. .viewer-thumb-list {
  3892. position: absolute;
  3893. width: 200px;
  3894. min-width: 50px;
  3895. top: 0;
  3896. right: -170px;
  3897. opacity: 0.2;
  3898. overflow-y: auto;
  3899. height: 100%;
  3900. background-color: #303030;
  3901. text-align: right;
  3902. transition: all 0.75s;
  3903. }
  3904. .viewer-thumb-div {
  3905. max-width: 200px;
  3906. padding: 2px 1px 2px 0;
  3907. }
  3908. .viewer-thumb {
  3909. max-width: 180px;
  3910. /*max-height: 240px;*/
  3911. }
  3912. .viewer-thumb-list:hover + .viewer-img-list {
  3913. right: 200px;
  3914. }
  3915. .viewer-img-list {
  3916. position: absolute;
  3917. top: 0;
  3918. left: 30px;
  3919. right: 30px;
  3920. bottom: 5px;
  3921. transition: all 0.75s;
  3922. background-color: ${col};
  3923. }
  3924. .viewer-img-list > .viewer-img-div:not(.img-show) ,
  3925. div[data-image-view-type="none_src"],
  3926. div[data-image-view-type="vid_file"] > *:not(.vid_file) ,
  3927. div[data-image-view-type="orig_img"] > *:not(.orig_img) ,
  3928. div[data-image-view-type="jpeg_img"] > *:not(.jpeg_img) ,
  3929. div[data-image-view-type="samp_img"] > *:not(.samp_img) {
  3930. display: none;
  3931. }
  3932. .viewer-img-div.img-show {
  3933. width: 100%;
  3934. height: 100%;
  3935. text-align: center;
  3936. background-color: ${col};
  3937. }
  3938. .viewer-img-div > * {
  3939. max-width: 100%;
  3940. max-height: 100%;
  3941. }
  3942. .viewer-img-div:before {
  3943. content: "";
  3944. display: inline-block;
  3945. height: 100%;
  3946. vertical-align: middle;
  3947. }
  3948. .viewer-bottom:hover {
  3949. opacity: 1;
  3950. }
  3951. .viewer-bottom {
  3952. transition: all 0.5s;
  3953. opacity: 0.2;
  3954. background-color: ${col};
  3955. }
  3956. .image-board-viewer-bottom-class {
  3957. position: absolute;
  3958. left: 200px;
  3959. right: 200px;
  3960. bottom: 0px;
  3961. text-align: center;
  3962. }
  3963. .image-board-viewer-bottom-class button {
  3964. color: ${imageBoard.siteList.style().color};
  3965. background-color: #303030;/*${imageBoard.siteList.style().background};*/
  3966. cursor: initial;
  3967. margin: 1px 1px 3px 1px;
  3968. padding: 1px 5px;
  3969. border: 0;
  3970. font-weight: bold;
  3971. }
  3972. `, id);
  3973. }
  3974. function progressBarCssClass()
  3975. {
  3976. var id = 'progress-bar-css-' + RANDOM;
  3977. addCssClass(`
  3978. @-webkit-keyframes progression{
  3979. from{background-position:0px 0px;}
  3980. to{background-position:50px 0px;}
  3981. }
  3982. @-o-keyframes progression{
  3983. from{background-position:0px 0px;}
  3984. to{background-position:50px 0px;}
  3985. }
  3986. @keyframes progression{
  3987. from{background-position:0px 0px;}
  3988. to{background-position:50px 0px;}
  3989. }
  3990. .progress-bar div.progress-counted {
  3991. background-color: #da504e;
  3992. width: 100%;
  3993. }
  3994. .progress-bar div.image-ready {
  3995. background-color: #fda02e;
  3996. }
  3997. div.progress-bar > div.progress-complete {
  3998. background-color: #5db75d;
  3999. }
  4000. div.progress-stripe {
  4001. background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);
  4002. background-image:-o-linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);
  4003. background-image:linear-gradient(-45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);
  4004. background-size: 50px 50px;
  4005. height: 12px;
  4006. }
  4007. div.progress-bar{
  4008. margin: 2px 2px 0px 0px;
  4009. }
  4010. .progress-bar > div.download-in-progress {
  4011. background-color: #0773fb;
  4012. }
  4013. div.progress-animated {
  4014. animation: progression 2s linear infinite;
  4015. }
  4016. `, id);
  4017. }
  4018. function addCssClass(cssClass, id)
  4019. {
  4020. var style = document.createElement('style'),
  4021. head = document.querySelector('head');
  4022. style.type = 'text/css';
  4023. if( id )
  4024. style.setAttribute('id', id);
  4025. if( style.styleSheets )
  4026. style.styleSheets.cssText = cssClass;
  4027. else
  4028. style.appendChild(document.createTextNode(cssClass));
  4029. return head.appendChild(style);
  4030. }
  4031. function resetCssClass(cssClass, id)
  4032. {
  4033. var style = document.getElementById(id);
  4034. if( style && style.type === 'text/css' )
  4035. remove( style );
  4036. addCssClass(cssClass, id);
  4037. }
  4038. function attr( elm, name, val )
  4039. {
  4040. var cond = (val === null || val === undefined);
  4041. if( !elm || !name )
  4042. return;
  4043. if( cond )
  4044. {
  4045. if( elm.length === undefined )
  4046. return elm.getAttribute(name);
  4047. return null;
  4048. }
  4049. else if( elm.length > 0 )
  4050. [].forEach.call(elm, function(it){it.setAttribute(name, val);});
  4051. else
  4052. elm.setAttribute(name, val);
  4053. }
  4054. function addClass( elm, name )
  4055. {
  4056. if( elm && name )
  4057. {
  4058. if( elm.length > 0 )
  4059. [].forEach.call(elm, function(it){it.classList.add(name);});
  4060. else
  4061. elm.classList.add(name);
  4062. }
  4063. }
  4064. function removeClass( elm, name )
  4065. {
  4066. if( elm && name )
  4067. {
  4068. if( elm.length > 0 )
  4069. [].forEach.call(elm, function(it){it.classList.remove(name);});
  4070. else
  4071. elm.classList.remove(name);
  4072. }
  4073. }
  4074. function hasClass( elm, name )
  4075. {
  4076. if( elm && name )
  4077. return elm.classList.contains(name);
  4078. return false;
  4079. }
  4080. function toggleClass( elm, newClass, oldClass )
  4081. {
  4082. if( !elm || !newClass )
  4083. return;
  4084. if( oldClass )
  4085. {
  4086. elm.classList.remove(oldClass);
  4087. elm.classList.add(newClass);
  4088. }
  4089. else if( elm.classList.contains(newClass) )
  4090. elm.classList.remove(newClass);
  4091. else
  4092. elm.classList.add(newClass);
  4093. }
  4094. function getLocation( url, attr )
  4095. {
  4096. if( !url || !attr )
  4097. return null;
  4098. this.link = this.link || document.createElement('a');
  4099. this.link.href = url;
  4100. return this.link[attr];
  4101. }
  4102. function getFileExt( source )
  4103. {
  4104. var ext = source ? getLocation( source, 'pathname' ) : null;
  4105. ext = ext ? ext.match(/\.([^\.]+)$/) : null;
  4106. ext = ext ? ext[1] : null;
  4107. return ext;
  4108. }
  4109. function getSearchObject( search )
  4110. {
  4111. var keys = {};
  4112. if( search )
  4113. {
  4114. search = search.replace(/^\?/, '');
  4115. search.split('&').forEach(function(item){
  4116. item = item.split('=');
  4117. keys[item[0]] = item[1];
  4118. });
  4119. }
  4120. return keys;
  4121. }
  4122. function isSameLink( lhs, rhs )
  4123. {
  4124. lhs = getLocation(lhs, 'href');
  4125. rhs = getLocation(rhs, 'href');
  4126. return lhs === rhs;
  4127. }
  4128. function last( arr )
  4129. {
  4130. if( arr && arr.length > 0 )
  4131. return arr[arr.length-1];
  4132. return null;
  4133. }
  4134. function copy( arr, v )
  4135. {
  4136. arr = arr || [];
  4137. if( v && v.length !== undefined )
  4138. {
  4139. arr.length = 0;
  4140. for( var i = 0, len = v.length; i < len; ++i )
  4141. arr.push(v[i]);
  4142. }
  4143. return arr;
  4144. }
  4145. function _toString_( obj, delim )
  4146. {
  4147. if( typeof obj === 'string' )
  4148. return obj;
  4149. else if( obj && obj.length !== undefined )
  4150. try{
  4151. return obj.join( delim || ', ' );
  4152. }catch(e){
  4153. return obj.toString();
  4154. }
  4155. else if( obj )
  4156. return obj.toString();
  4157. return '';
  4158. }
  4159. function nodeWalk()
  4160. {
  4161. var len = arguments.length, obj = this, i, arg;
  4162. for( i = 0; i < len; ++i )
  4163. {
  4164. arg = arguments[i];
  4165. if( arg === undefined )
  4166. return obj;
  4167. else if( obj[arg] === undefined )
  4168. return null;
  4169. obj = obj[arg];
  4170. }
  4171. return obj;
  4172. }
  4173. function hide( elm )
  4174. {
  4175. if( !elm )
  4176. return;
  4177. else if( elm.length === undefined )
  4178. elm.style.display = 'none';
  4179. else{
  4180. try{
  4181. for( var i = 0, len = elm.length; i < len; ++i )
  4182. elm[i].style.display = 'none';
  4183. }catch(e){console.error(e);}
  4184. }
  4185. }
  4186. function show( elm )
  4187. {
  4188. if( elm )
  4189. elm.style.display = '';
  4190. }
  4191. function remove( elm )
  4192. {
  4193. if( elm && elm.parentNode )
  4194. return elm.parentNode.removeChild(elm);
  4195. return null;
  4196. }
  4197. function parent( elm, n )
  4198. {
  4199. if( !elm || n === null || n === undefined )
  4200. return elm;
  4201. else if( /^\d+$/.test(n.toString()) )
  4202. {
  4203. n = parseInt(n, 10);
  4204. for( var i = 0; i < n && elm; ++i )
  4205. elm = elm.parentNode;
  4206. }
  4207. else if( typeof n === 'string' )
  4208. {
  4209. n = n.toUpperCase();
  4210. while( elm && elm.tagName !== n )
  4211. elm = elm.parentNode;
  4212. }
  4213. return elm;
  4214. }
  4215. })();