client.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  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 title = elem.title;
  269. }
  270. // prepare HTML
  271. var html =
  272. '<div id="popup-num">(' + collection.length + ')</div>' +
  273. '<div id="popup-dropdown">' +
  274. '<select name="viewer-res" onchange="changeimage(event)">';
  275. for(i=0; i<collection.length; i++){
  276. if(collection[i].url.startsWith("data:")){
  277. var domain = "&lt;Base64 Data&gt;";
  278. }else{
  279. var domain = new URL(collection[i].url).hostname;
  280. }
  281. html += '<option value="' + i + '">' + '(' + collection[i].width + 'x' + collection[i].height + ') ' + domain + '</option>';
  282. }
  283. popup_status.innerHTML =
  284. html + '</select></div>' +
  285. '<a href="' + htmlspecialchars(image_url) + '" rel="noreferrer nofollow "id="popup-title">' + htmlspecialchars(title) + '</a>';
  286. popup_body.innerHTML =
  287. '<img src="' + getproxylink(collection[0].url) + '" draggable="false" id="popup-image">';
  288. // make changes to DOM
  289. popup_body.style.display = "block";
  290. popup_bg.style.display = "block";
  291. popup_status.style.display = "table";
  292. // store for rotation functions & changeimage()
  293. popup_image = document.getElementById("popup-image");
  294. scalepopup(collection[collection_index], scale);
  295. centerpopup();
  296. }else{
  297. // click inside the image viewer
  298. // resize image
  299. if(is_click_within(click.target, "popup", true)){
  300. if(mouse_move === false){
  301. scale = 80;
  302. scalepopup(collection[collection_index], scale);
  303. centerpopup();
  304. }
  305. }else{
  306. if(is_click_within(click.target, "popup-status", true) === false){
  307. // click outside the popup while its open
  308. // close it
  309. if(is_popup_shown){
  310. hidepopup();
  311. }
  312. }
  313. }
  314. }
  315. });
  316. /*
  317. Scale image viewer
  318. */
  319. popup_body.addEventListener("wheel", function(scroll){
  320. event.preventDefault();
  321. if(
  322. scroll.altKey ||
  323. scroll.ctrlKey ||
  324. scroll.shiftKey
  325. ){
  326. var increment = 7;
  327. }else{
  328. var increment = 14;
  329. }
  330. if(scroll.wheelDelta > 0){
  331. // scrolling up
  332. scale = scale + increment;
  333. }else{
  334. // scrolling down
  335. if(scale - increment > 7){
  336. scale = scale - increment;
  337. }
  338. }
  339. // calculate relative size before scroll
  340. var pos = popup_body.getBoundingClientRect();
  341. var x = (scroll.x - pos.x) / pos.width;
  342. var y = (scroll.y - pos.y) / pos.height;
  343. scalepopup(collection[collection_index], scale);
  344. // move popup to % we found
  345. pos = popup_body.getBoundingClientRect();
  346. movepopup(
  347. popup_body,
  348. scroll.clientX - (x * pos.width),
  349. scroll.clientY - (y * pos.height)
  350. );
  351. });
  352. /*
  353. Keyboard controls
  354. */
  355. document.addEventListener("keydown", function(key){
  356. // close popup
  357. if(
  358. is_popup_shown &&
  359. key.keyCode === 27
  360. ){
  361. hidepopup();
  362. return;
  363. }
  364. if(is_popup_shown === false){
  365. return;
  366. }
  367. if(
  368. key.altKey ||
  369. key.ctrlKey ||
  370. key.shiftKey
  371. ){
  372. // mirror image
  373. switch(key.keyCode){
  374. case 37:
  375. // left
  376. key.preventDefault();
  377. mirror_x = true;
  378. break;
  379. case 38:
  380. // up
  381. key.preventDefault();
  382. mirror_y = false;
  383. break;
  384. case 39:
  385. // right
  386. key.preventDefault();
  387. mirror_x = false;
  388. break;
  389. case 40:
  390. // down
  391. key.preventDefault();
  392. mirror_y = true;
  393. break;
  394. }
  395. }else{
  396. // rotate image
  397. switch(key.keyCode){
  398. case 37:
  399. // left
  400. key.preventDefault();
  401. rotation = -90;
  402. break;
  403. case 38:
  404. // up
  405. key.preventDefault();
  406. rotation = 0;
  407. break;
  408. case 39:
  409. // right
  410. key.preventDefault();
  411. rotation = 90;
  412. break;
  413. case 40:
  414. // down
  415. key.preventDefault();
  416. rotation = -180;
  417. break;
  418. }
  419. }
  420. popup_image.style.transform =
  421. "scale(" +
  422. (mirror_x ? "-1" : "1") +
  423. ", " +
  424. (mirror_y ? "-1" : "1") +
  425. ") " +
  426. "rotate(" +
  427. rotation + "deg" +
  428. ")";
  429. });
  430. }
  431. function getproxylink(url){
  432. if(url.startsWith("data:")){
  433. return htmlspecialchars(url);
  434. }else{
  435. console.log(url);
  436. return '/proxy?i=' + encodeURIComponent(url);
  437. }
  438. }
  439. function hidepopup(){
  440. is_popup_shown = false;
  441. popup_status.style.display = "none";
  442. popup_body.style.display = "none";
  443. popup_bg.style.display = "none";
  444. }
  445. function scalepopup(size, scale){
  446. var ratio =
  447. Math.min(
  448. (window.innerWidth * (scale / 100)) / collection[collection_index].width, (window.innerHeight * (scale / 100)) / collection[collection_index].height
  449. );
  450. popup_body.style.width = size.width * ratio + "px";
  451. popup_body.style.height = size.height * ratio + "px";
  452. }
  453. function centerpopup(){
  454. var size = popup_body.getBoundingClientRect();
  455. var size = {
  456. "width": parseInt(size.width),
  457. "height": parseInt(size.height)
  458. };
  459. movepopup(
  460. popup_body,
  461. (window.innerWidth / 2) - (size.width / 2),
  462. (window.innerHeight / 2) - (size.height / 2)
  463. );
  464. }
  465. function movepopup(popup_body, x, y){
  466. popup_body.style.left = x + "px";
  467. popup_body.style.top = y + "px";
  468. }
  469. function changeimage(event){
  470. // reset rotation params
  471. mirror_x = false;
  472. mirror_y = false;
  473. rotation = 0;
  474. scale = 60;
  475. collection_index = parseInt(event.target.value);
  476. // we set innerHTML otherwise old image lingers a little
  477. popup_body.innerHTML =
  478. '<img src="' + getproxylink(collection[collection_index].url) + '" draggable="false" id="popup-image">';
  479. // store for rotation functions & changeimage()
  480. popup_image = document.getElementById("popup-image");
  481. scalepopup(collection[collection_index], scale);
  482. centerpopup();
  483. }
  484. /*
  485. Shortcuts
  486. */
  487. var searchbox_wrapper = document.getElementsByClassName("searchbox");
  488. if(searchbox_wrapper.length !== 0){
  489. searchbox_wrapper = searchbox_wrapper[0];
  490. var searchbox = searchbox_wrapper.getElementsByTagName("input")[1];
  491. document.addEventListener("keydown", function(key){
  492. switch(key.keyCode){
  493. case 191:
  494. // 191 = /
  495. if(document.activeElement.tagName == "INPUT"){
  496. // already focused, ignore
  497. break;
  498. }
  499. if(
  500. typeof is_popup_shown != "undefined" &&
  501. is_popup_shown
  502. ){
  503. hidepopup();
  504. }
  505. window.scrollTo(0, 0);
  506. searchbox.focus();
  507. key.preventDefault();
  508. break;
  509. }
  510. });
  511. }