client.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. /*
  2. Global functions
  3. */
  4. function htmlspecialchars(str){
  5. var map = {
  6. '&': '&',
  7. '<': '&lt;',
  8. '>': '&gt;',
  9. '"': '&quot;',
  10. "'": '&#039;'
  11. }
  12. return str.replace(/[&<>"']/g, function(m){return map[m];});
  13. }
  14. function htmlspecialchars_decode(str){
  15. var map = {
  16. '&amp;': '&',
  17. '&lt;': '<',
  18. '&gt;': '>',
  19. '&quot;': '"',
  20. '&#039;': "'"
  21. }
  22. return str.replace(/&amp;|&lt;|&gt;|&quot;|&#039;/g, function(m){return map[m];});
  23. }
  24. function is_click_within(elem, classname, is_id = false){
  25. while(true){
  26. if(elem === null){
  27. return false;
  28. }
  29. if(
  30. (
  31. is_id === false &&
  32. elem.className == classname
  33. ) ||
  34. (
  35. is_id === true &&
  36. elem.id == classname
  37. )
  38. ){
  39. return elem;
  40. }
  41. elem = elem.parentElement;
  42. }
  43. }
  44. /*
  45. Prevent GET parameter pollution
  46. */
  47. var form = document.getElementsByTagName("form");
  48. if(
  49. form.length !== 0 &&
  50. window.location.pathname != "/" &&
  51. window.location.pathname != "/settings.php" &&
  52. window.location.pathname != "/settings"
  53. ){
  54. form = form[0];
  55. var scraper_dropdown = document.getElementsByName("scraper")[0];
  56. scraper_dropdown.addEventListener("change", function(choice){
  57. submit(form);
  58. });
  59. form.addEventListener("submit", function(e){
  60. e.preventDefault();
  61. submit(e.srcElement);
  62. });
  63. }
  64. function submit(e){
  65. var GET = "";
  66. var first = true;
  67. if((s = document.getElementsByName("s")).length !== 0){
  68. GET += "?s=" + encodeURIComponent(s[0].value).replaceAll("%20", "+");
  69. first = false;
  70. }
  71. Array.from(
  72. e.getElementsByTagName("select")
  73. ).concat(
  74. Array.from(
  75. e.getElementsByTagName("input")
  76. )
  77. ).forEach(function(el){
  78. var firstelem = el.getElementsByTagName("option");
  79. if(
  80. (
  81. (
  82. firstelem.length === 0 ||
  83. firstelem[0].value != el.value
  84. ) &&
  85. el.name != "" &&
  86. el.value != "" &&
  87. el.name != "s"
  88. ) ||
  89. el.name == "scraper" ||
  90. el.name == "nsfw"
  91. ){
  92. if(first){
  93. GET += "?";
  94. first = false;
  95. }else{
  96. GET += "&";
  97. }
  98. GET += encodeURIComponent(el.name).replaceAll("%20", "+") + "=" + encodeURIComponent(el.value).replaceAll("%20", "+");
  99. }
  100. });
  101. window.location.href = GET;
  102. }
  103. /*
  104. Hide show more button when it's not needed on answers
  105. */
  106. var answer_div = document.getElementsByClassName("answer");
  107. if(answer_div.length !== 0){
  108. answer_div = Array.from(answer_div);
  109. var spoiler_button_div = Array.from(document.getElementsByClassName("spoiler-button"));
  110. // execute on pageload
  111. hide_show_more();
  112. window.addEventListener("resize", hide_show_more);
  113. function hide_show_more(){
  114. var height = window.innerWidth >= 1000 ? 600 : 200;
  115. for(i=0; i<answer_div.length; i++){
  116. if(answer_div[i].scrollHeight < height){
  117. spoiler_button_div[i].style.display = "none";
  118. document.getElementById(spoiler_button_div[i].htmlFor).checked = true;
  119. }else{
  120. spoiler_button_div[i].style.display = "block";
  121. }
  122. }
  123. }
  124. }
  125. switch(document.location.pathname){
  126. case "/web":
  127. case "/web.php":
  128. var image_class = "image";
  129. break;
  130. case "/images":
  131. case "/images.php":
  132. var image_class = "thumb";
  133. break;
  134. default:
  135. var image_class = null;
  136. }
  137. if(image_class !== null){
  138. /*
  139. Add popup to document
  140. */
  141. var popup_bg = document.createElement("div");
  142. popup_bg.id = "popup-bg";
  143. document.body.appendChild(popup_bg);
  144. // enable/disable pointer events
  145. if(!document.cookie.includes("bg_noclick=yes")){
  146. popup_bg.style.pointerEvents = "none";
  147. }
  148. var popup_status = document.createElement("div");
  149. popup_status.id = "popup-status";
  150. document.body.appendChild(popup_status);
  151. var popup_body = document.createElement("div");
  152. popup_body.id = "popup";
  153. document.body.appendChild(popup_body);
  154. // import popup
  155. var popup_body = document.getElementById("popup");
  156. var popup_status = document.getElementById("popup-status");
  157. var popup_image = null; // is set later on popup click
  158. // image metadata
  159. var collection = []; // will contain width, height, image URL
  160. var collection_index = 0;
  161. // event handling helper variables
  162. var is_popup_shown = false;
  163. var mouse_down = false;
  164. var mouse_move = false;
  165. var move_x = 0;
  166. var move_y = 0;
  167. var target_is_popup = false;
  168. var mirror_x = false;
  169. var mirror_y = false;
  170. var rotation = 0;
  171. /*
  172. Image dragging (mousedown)
  173. */
  174. document.addEventListener("mousedown", function(div){
  175. if(div.buttons !== 1){
  176. return;
  177. }
  178. mouse_down = true;
  179. mouse_move = false;
  180. if(is_click_within(div.target, "popup", true) === false){
  181. target_is_popup = false;
  182. }else{
  183. target_is_popup = true;
  184. var pos = popup_body.getBoundingClientRect();
  185. move_x = div.x - pos.x;
  186. move_y = div.y - pos.y;
  187. }
  188. });
  189. /*
  190. Image dragging (mousemove)
  191. */
  192. document.addEventListener("mousemove", function(pos){
  193. if(
  194. target_is_popup &&
  195. mouse_down
  196. ){
  197. mouse_move = true;
  198. movepopup(popup_body, pos.clientX - move_x, pos.clientY - move_y);
  199. }
  200. });
  201. /*
  202. Image dragging (mouseup)
  203. */
  204. document.addEventListener("mouseup", function(){
  205. mouse_down = false;
  206. });
  207. /*
  208. Image popup open
  209. */
  210. document.addEventListener("click", function(click){
  211. // should our click trigger image open?
  212. if(
  213. elem = is_click_within(click.target, image_class) ||
  214. click.target.classList.contains("openimg")
  215. ){
  216. event.preventDefault();
  217. is_popup_shown = true;
  218. // reset position params
  219. mirror_x = false;
  220. mirror_y = false;
  221. rotation = 0;
  222. scale = 60;
  223. collection_index = 0;
  224. // get popup data
  225. if(elem === true){
  226. // we clicked a simple image preview
  227. elem = click.target;
  228. var image_url = elem.getAttribute("src");
  229. if(image_url.startsWith("/proxy")){
  230. var match = image_url.match(/i=([^&]+)/);
  231. if(match !== null){
  232. image_url = decodeURIComponent(match[1]);
  233. }
  234. }else{
  235. image_url = htmlspecialchars_decode(image_url);
  236. }
  237. collection = [
  238. {
  239. "url": image_url,
  240. "width": Math.round(click.target.naturalWidth),
  241. "height": Math.round(click.target.naturalHeight)
  242. }
  243. ];
  244. var title = "No description provided";
  245. if(click.target.title != ""){
  246. title = click.target.title;
  247. }else{
  248. if(click.target.alt != ""){
  249. title = click.target.alt;
  250. }
  251. }
  252. }else{
  253. if(image_class == "thumb"){
  254. // we're inside image.php
  255. elem =
  256. elem
  257. .parentElement
  258. .parentElement;
  259. var image_url = elem.getElementsByTagName("a")[1].href;
  260. }else{
  261. // we're inside web.php
  262. var image_url = elem.href;
  263. }
  264. collection =
  265. JSON.parse(
  266. elem.getAttribute("data-json")
  267. );
  268. var imagesize = elem.getElementsByTagName("img")[0];
  269. if(imagesize.complete){
  270. var imagesize_w = imagesize.naturalWidth;
  271. var imagesize_h = imagesize.naturalHeight;
  272. }
  273. for(var i=0; i<collection.length; i++){
  274. if(collection[i].width === null){
  275. collection[i].width = imagesize_w;
  276. collection[i].height = imagesize_h;
  277. }
  278. }
  279. var title = elem.title;
  280. }
  281. // prepare HTML
  282. var html =
  283. '<div id="popup-num">(' + collection.length + ')</div>' +
  284. '<div id="popup-dropdown">' +
  285. '<select name="viewer-res" onchange="changeimage(event)">';
  286. for(i=0; i<collection.length; i++){
  287. if(collection[i].url.startsWith("data:")){
  288. var domain = "&lt;Base64 Data&gt;";
  289. }else{
  290. var domain = new URL(collection[i].url).hostname;
  291. }
  292. html += '<option value="' + i + '">' + '(' + collection[i].width + 'x' + collection[i].height + ') ' + domain + '</option>';
  293. }
  294. popup_status.innerHTML =
  295. html + '</select></div>' +
  296. '<a href="' + htmlspecialchars(image_url) + '" rel="noreferrer nofollow "id="popup-title">' + htmlspecialchars(title) + '</a>';
  297. popup_body.innerHTML =
  298. '<img src="' + getproxylink(collection[0].url) + '" draggable="false" id="popup-image">';
  299. // make changes to DOM
  300. popup_body.style.display = "block";
  301. popup_bg.style.display = "block";
  302. popup_status.style.display = "table";
  303. // store for rotation functions & changeimage()
  304. popup_image = document.getElementById("popup-image");
  305. scalepopup(collection[collection_index], scale);
  306. centerpopup();
  307. }else{
  308. // click inside the image viewer
  309. // resize image
  310. if(is_click_within(click.target, "popup", true)){
  311. if(mouse_move === false){
  312. scale = 80;
  313. scalepopup(collection[collection_index], scale);
  314. centerpopup();
  315. }
  316. }else{
  317. if(is_click_within(click.target, "popup-status", true) === false){
  318. // click outside the popup while its open
  319. // close it
  320. if(is_popup_shown){
  321. hidepopup();
  322. }
  323. }
  324. }
  325. }
  326. });
  327. /*
  328. Scale image viewer
  329. */
  330. popup_body.addEventListener("wheel", function(scroll){
  331. event.preventDefault();
  332. if(
  333. scroll.altKey ||
  334. scroll.ctrlKey ||
  335. scroll.shiftKey
  336. ){
  337. var increment = 7;
  338. }else{
  339. var increment = 14;
  340. }
  341. if(scroll.wheelDelta > 0){
  342. // scrolling up
  343. scale = scale + increment;
  344. }else{
  345. // scrolling down
  346. if(scale - increment > 7){
  347. scale = scale - increment;
  348. }
  349. }
  350. // calculate relative size before scroll
  351. var pos = popup_body.getBoundingClientRect();
  352. var x = (scroll.x - pos.x) / pos.width;
  353. var y = (scroll.y - pos.y) / pos.height;
  354. scalepopup(collection[collection_index], scale);
  355. // move popup to % we found
  356. pos = popup_body.getBoundingClientRect();
  357. movepopup(
  358. popup_body,
  359. scroll.clientX - (x * pos.width),
  360. scroll.clientY - (y * pos.height)
  361. );
  362. });
  363. /*
  364. Keyboard controls
  365. */
  366. document.addEventListener("keydown", function(key){
  367. // close popup
  368. if(
  369. is_popup_shown &&
  370. key.keyCode === 27
  371. ){
  372. hidepopup();
  373. return;
  374. }
  375. if(is_popup_shown === false){
  376. return;
  377. }
  378. if(
  379. key.altKey ||
  380. key.ctrlKey ||
  381. key.shiftKey
  382. ){
  383. // mirror image
  384. switch(key.keyCode){
  385. case 37:
  386. // left
  387. key.preventDefault();
  388. mirror_x = true;
  389. break;
  390. case 38:
  391. // up
  392. key.preventDefault();
  393. mirror_y = false;
  394. break;
  395. case 39:
  396. // right
  397. key.preventDefault();
  398. mirror_x = false;
  399. break;
  400. case 40:
  401. // down
  402. key.preventDefault();
  403. mirror_y = true;
  404. break;
  405. }
  406. }else{
  407. // rotate image
  408. switch(key.keyCode){
  409. case 37:
  410. // left
  411. key.preventDefault();
  412. rotation = -90;
  413. break;
  414. case 38:
  415. // up
  416. key.preventDefault();
  417. rotation = 0;
  418. break;
  419. case 39:
  420. // right
  421. key.preventDefault();
  422. rotation = 90;
  423. break;
  424. case 40:
  425. // down
  426. key.preventDefault();
  427. rotation = -180;
  428. break;
  429. }
  430. }
  431. popup_image.style.transform =
  432. "scale(" +
  433. (mirror_x ? "-1" : "1") +
  434. ", " +
  435. (mirror_y ? "-1" : "1") +
  436. ") " +
  437. "rotate(" +
  438. rotation + "deg" +
  439. ")";
  440. });
  441. }
  442. function getproxylink(url){
  443. if(url.startsWith("data:")){
  444. return htmlspecialchars(url);
  445. }else{
  446. return '/proxy?i=' + encodeURIComponent(url);
  447. }
  448. }
  449. function hidepopup(){
  450. is_popup_shown = false;
  451. popup_status.style.display = "none";
  452. popup_body.style.display = "none";
  453. popup_bg.style.display = "none";
  454. }
  455. function scalepopup(size, scale){
  456. var ratio =
  457. Math.min(
  458. (window.innerWidth * (scale / 100)) / collection[collection_index].width, (window.innerHeight * (scale / 100)) / collection[collection_index].height
  459. );
  460. popup_body.style.width = size.width * ratio + "px";
  461. popup_body.style.height = size.height * ratio + "px";
  462. }
  463. function centerpopup(){
  464. var size = popup_body.getBoundingClientRect();
  465. var size = {
  466. "width": parseInt(size.width),
  467. "height": parseInt(size.height)
  468. };
  469. movepopup(
  470. popup_body,
  471. (window.innerWidth / 2) - (size.width / 2),
  472. (window.innerHeight / 2) - (size.height / 2)
  473. );
  474. }
  475. function movepopup(popup_body, x, y){
  476. popup_body.style.left = x + "px";
  477. popup_body.style.top = y + "px";
  478. }
  479. function changeimage(event){
  480. // reset rotation params
  481. mirror_x = false;
  482. mirror_y = false;
  483. rotation = 0;
  484. scale = 60;
  485. collection_index = parseInt(event.target.value);
  486. // we set innerHTML otherwise old image lingers a little
  487. popup_body.innerHTML =
  488. '<img src="' + getproxylink(collection[collection_index].url) + '" draggable="false" id="popup-image">';
  489. // store for rotation functions & changeimage()
  490. popup_image = document.getElementById("popup-image");
  491. scalepopup(collection[collection_index], scale);
  492. centerpopup();
  493. }
  494. /*
  495. Shortcuts
  496. */
  497. var searchbox_wrapper = document.getElementsByClassName("searchbox");
  498. if(searchbox_wrapper.length !== 0){
  499. searchbox_wrapper = searchbox_wrapper[0];
  500. var searchbox = searchbox_wrapper.getElementsByTagName("input")[1];
  501. document.addEventListener("keydown", function(key){
  502. switch(key.keyCode){
  503. case 191:
  504. // 191 = /
  505. if(document.activeElement.tagName == "INPUT"){
  506. // already focused, ignore
  507. break;
  508. }
  509. if(
  510. typeof is_popup_shown != "undefined" &&
  511. is_popup_shown
  512. ){
  513. hidepopup();
  514. }
  515. window.scrollTo(0, 0);
  516. searchbox.focus();
  517. key.preventDefault();
  518. break;
  519. }
  520. });
  521. }