lemmy-keyboard-navigation

Easily navigate Lemmy with your keyboard

当前为 2023-07-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name lemmy-keyboard-navigation
  3. // @match https://*/*
  4. // @grant none
  5. // @version 2.0
  6. // @author vmavromatis
  7. // @author howdy@thesimplecorner.org
  8. // @author InfinibyteF4
  9. // @author aglidden
  10. // @license GPL3
  11. // @icon https://raw.githubusercontent.com/vmavromatis/Lemmy-keyboard-navigation/main/icon.png?inline=true
  12. // @homepageURL https://github.com/vmavromatis/Lemmy-keyboard-navigation
  13. // @namespace https://github.com/vmavromatis/Lemmy-keyboard-navigation
  14. // @description Easily navigate Lemmy with your keyboard
  15. // @run-at document-end
  16. // ==/UserScript==
  17.  
  18. //isLemmySite
  19. if (document.querySelectorAll('.lemmy-site').length >= 1){
  20.  
  21. //////////////////////////////////////////
  22. //DEBUGGING (ignore me!)
  23. //localStorage.clear();
  24. //sessionStorage.clear();
  25. //////////////////////////////////////////
  26.  
  27.  
  28. //TODO add way of changing pageOffset, smoothScroll, scrollPosition in page
  29. //until then
  30. //pageOffset defaults to 5% of window
  31. //smoothScroll defaults to false
  32. //scrollPosition defaults to middle
  33.  
  34. //////////////////////////////////////////
  35. //QUICK SETTINGS CHANGE (larger page offset, opposite of defaults)
  36. //localStorage.setItem('pageOffset', window.innerHeight * 0.20); //20%
  37. //localStorage.setItem('smoothScroll', true);
  38. //localStorage.setItem('scrollPosition', "top");
  39. //localStorage.setItem('vimKeyNavigation', false);
  40. //////////////////////////////////////////
  41.  
  42. //set page offset size (default 5% of window)
  43. let pageOffset;
  44. if (localStorage.getItem('pageOffset') === null) {
  45. localStorage.setItem('pageOffset', window.innerHeight * 0.05); //5% window height
  46. }
  47. if (localStorage.getItem('pageOffset')) {
  48. pageOffset = localStorage.getItem('pageOffset');
  49. }
  50. console.log(`pageOffset: ${pageOffset}`);
  51.  
  52. //enable or disable smooth scrolling `true` or `false` (default false)
  53. let smoothScroll;
  54. if (localStorage.getItem('smoothScroll') === null) {
  55. localStorage.setItem('smoothScroll', false);
  56. }
  57. if (localStorage.getItem('smoothScroll') === 'false') {
  58. smoothScroll = false;
  59. } else if (localStorage.getItem('smoothScroll') === 'true') {
  60. smoothScroll = true;
  61. }
  62. console.log(`smoothScroll: ${smoothScroll}`);
  63.  
  64. //set scrolling position "middle" or "top" (default middle)
  65. // "middle" means only scroll the page if selected post is near the bottom
  66. // "top" always scrolls the page to keep selected post near the top
  67. let scrollPosition;
  68. if (localStorage.getItem('scrollPosition') === null) {
  69. localStorage.setItem('scrollPosition', "middle");
  70. }
  71. if (localStorage.getItem('scrollPosition') === "middle") {
  72. scrollPosition = "middle";
  73. } else if (localStorage.getItem('scrollPosition') === "top") {
  74. scrollPosition = "top";
  75. }
  76. console.log(`scrollPosition: ${scrollPosition}`);
  77.  
  78.  
  79. //set vimKeyNavigation based on localStorage (default true)
  80. //set vimKeyNavigation based on localStorage
  81. let vimKeyNavigation = '';
  82. if (localStorage.getItem('vimKeyNavigation') === null) {
  83. localStorage.setItem('vimKeyNavigation', true);
  84. }
  85. if (localStorage.getItem('vimKeyNavigation') === 'false') {
  86. vimKeyNavigation = false;
  87. } else if (localStorage.getItem('vimKeyNavigation') === 'true') {
  88. vimKeyNavigation = true;
  89. }
  90. console.log(`vimKeyNavigation: ${vimKeyNavigation}`);
  91.  
  92. // Set selected entry colors
  93. const backgroundColor = '#373737';
  94. const textColor = 'white';
  95.  
  96. // Set navigation keys with keycodes here: https://www.toptal.com/developers/keycode
  97. let nextKey = 'ArrowDown';
  98. let prevKey = 'ArrowUp';
  99. let nextPageKey = 'ArrowRight';
  100. let prevPageKey = 'ArrowLeft';
  101.  
  102. if (vimKeyNavigation) {
  103. nextKey = 'KeyJ';
  104. prevKey = 'KeyK';
  105. nextPageKey = 'KeyL';
  106. prevPageKey = 'KeyH';
  107. }
  108.  
  109. const expandKey = 'KeyX';
  110. const openCommentsKey = 'KeyC';
  111. const openLinkAndCollapseKey = 'Enter';
  112. const parentCommentKey = 'KeyP';
  113. const upvoteKey = 'KeyA';
  114. const downvoteKey = 'KeyZ';
  115. const replyCommKey = 'KeyR';
  116. const saveKey = 'KeyS';
  117. const popupKey = 'KeyG';
  118. const contextKey = 'KeyQ';
  119. const smallerImgKey = 'Minus';
  120. const biggerImgKey = 'Equal';
  121. const userKey = 'KeyU';
  122. const editKey = 'KeyE';
  123. const linkOneKey = 'Digit1';
  124. const linkTwoKey = 'Digit2';
  125. const linkThreeKey = 'Digit3';
  126. const linkFourKey = 'Digit4';
  127. const linkFiveKey = 'Digit5';
  128. const linkSixKey = 'Digit6';
  129. const linkSevenKey = 'Digit7';
  130. const linkEightKey = 'Digit8';
  131. const linkNineKey = 'Digit9';
  132. const linkZeroKey = 'Digit0';
  133.  
  134. const modalCommentsKey = 'KeyC';
  135. const modalPostsKey = 'KeyP';
  136. const modalSubscribedKey = 'Digit1';
  137. const modalLocalKey = 'Digit2';
  138. const modalAllKey = 'Digit3';
  139. const modalSavedKey = 'KeyS';
  140. const modalFrontpageKey = 'KeyF';
  141. const modalProfileKey = 'KeyU';
  142. const modalInboxKey = 'KeyI';
  143. const modalToggleNavigationKey = 'KeyV';
  144.  
  145. const escapeKey = 'Escape';
  146. let modalMode = 0;
  147. console.log(`modalMode: ${modalMode}`);
  148.  
  149. // Stop arrows from moving the page if not using Vim navigation
  150. window.addEventListener("keydown", function(e) {
  151. if (["ArrowUp", "ArrowDown"].indexOf(e.code) > -1 && !vimKeyNavigation) {
  152. e.preventDefault();
  153. }
  154. }, false);
  155.  
  156. // Remove scroll animations
  157. document.documentElement.style = "scroll-behavior: auto";
  158.  
  159. // Set CSS for selected entry
  160. const css = `
  161. .selected {
  162. background-color: ${backgroundColor} !important;
  163. color: ${textColor};
  164. }`;
  165.  
  166. // dialog box
  167. let myDialog = document.createElement("dialog");
  168. document.body.appendChild(myDialog);
  169. let para = document.createElement("p");
  170. para.innerHTML = `
  171. <h3><b>Frontpage Sort</b></h3>
  172. <p>P = Posts</br>
  173. C = Comments</br>
  174. 1 = Subscribed</br>
  175. 2 = Local</br>
  176. 3 = all</p>
  177. <h3><b>Go To Page</b></h3>
  178. <p>F = Frontpage</br>
  179. S = Saved</br>
  180. U = User Profile Page</br>
  181. I = Inbox</br></p>
  182. <h6>V = Toggle HJKL (currently ${vimKeyNavigation})</br></br></h6>
  183. `;
  184. myDialog.appendChild(para);
  185. let button = document.createElement("button");
  186. button.classList.add('CLOSEBUTTON1');
  187. button.innerHTML = 'Press ESC or G to Close';
  188. myDialog.appendChild(button);
  189.  
  190. // Global variables
  191. let currentEntry;
  192. let commentBlock;
  193. let entries = [];
  194. let previousUrl = "";
  195. let expand = false;
  196.  
  197. if (typeof GM_addStyle !== "undefined") {
  198. GM_addStyle(css);
  199. } else if (typeof PRO_addStyle !== "undefined") {
  200. PRO_addStyle(css);
  201. } else if (typeof addStyle !== "undefined") {
  202. addStyle(css);
  203. } else {
  204. let node = document.createElement("style");
  205. node.type = "text/css";
  206. node.appendChild(document.createTextNode(css));
  207. let heads = document.getElementsByTagName("head");
  208. if (heads.length > 0) {
  209. heads[0].appendChild(node);
  210. } else {
  211. // no head yet, stick it whereever
  212. document.documentElement.appendChild(node);
  213. }
  214. }
  215.  
  216. const selectedClass = "selected";
  217.  
  218. const targetNode = document.documentElement;
  219. const config = {
  220. childList: true,
  221. subtree: true
  222. };
  223.  
  224. const observer = new MutationObserver(() => {
  225. entries = document.querySelectorAll(".post-listing, .comment-node");
  226.  
  227. if (entries.length > 0) {
  228. if (location.href !== previousUrl) {
  229. previousUrl = location.href;
  230. currentEntry = null;
  231. }
  232. init();
  233. }
  234. });
  235.  
  236. observer.observe(targetNode, config);
  237.  
  238. function init() {
  239. // If jumping to comments
  240. if (window.location.search.includes("scrollToComments=true") &&
  241. entries.length > 1 &&
  242. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  243. ) {
  244. selectEntry(entries[1], true);
  245. }
  246. // If jumping to comment from anchor link
  247. else if (window.location.pathname.includes("/comment/") &&
  248. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  249. ) {
  250. const commentId = window.location.pathname.replace("/comment/", "");
  251. const anchoredEntry = document.getElementById("comment-" + commentId);
  252.  
  253. if (anchoredEntry) {
  254. selectEntry(anchoredEntry, true);
  255. }
  256. }
  257. // If no entries yet selected, default to last selected
  258. else if (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0) {
  259. if (sessionStorage.getItem('currentselection') === null) {
  260. selectEntry(entries[0]);
  261. } else {
  262. sessionCurrentEntry("restore");
  263. }
  264. }
  265.  
  266. Array.from(entries).forEach(entry => {
  267. entry.removeEventListener("click", clickEntry, true);
  268. entry.addEventListener('click', clickEntry, true);
  269. });
  270.  
  271. document.removeEventListener("keydown", handleKeyPress, true);
  272. document.addEventListener("keydown", handleKeyPress, true);
  273. }
  274.  
  275. function handleKeyPress(event) {
  276. if (["TEXTAREA", "INPUT"].indexOf(event.target.tagName) > -1 || event.metaKey) {
  277. return;
  278. }
  279.  
  280. switch (modalMode) {
  281. case modalMode = 0:
  282. switch (event.code) {
  283. case nextKey:
  284. case prevKey:
  285. previousKey(event);
  286. break;
  287. case upvoteKey:
  288. upVote();
  289. break;
  290. case downvoteKey:
  291. downVote();
  292. break;
  293. case expandKey:
  294. toggleExpand();
  295. expand = isExpanded() ? true : false;
  296. break;
  297. case smallerImgKey:
  298. imgResize("smaller");
  299. break;
  300. case biggerImgKey:
  301. imgResize("larger");
  302. break;
  303. case saveKey:
  304. save();
  305. break;
  306. case editKey:
  307. edit();
  308. break;
  309. case openCommentsKey:
  310. comments(event);
  311. break;
  312. case popupKey:
  313. goToDialog("open");
  314. break;
  315. case contextKey:
  316. getContext(event);
  317. break;
  318. case replyCommKey:
  319. // allow refresh with Ctrl + R
  320. if (!event.ctrlKey) {
  321. if (window.location.pathname.includes("/post/")) {
  322. reply(event);
  323. } else {
  324. community(event);
  325. }
  326. }
  327. break;
  328. case userKey:
  329. visitUser(event);
  330. break;
  331. case openLinkAndCollapseKey:
  332. if (window.location.pathname.includes("/post/")) {
  333. toggleExpand();
  334. } else {
  335. const linkElement = currentEntry.querySelector(".col.flex-grow-1>p>a");
  336. if (linkElement) {
  337. if (event.shiftKey) {
  338. window.open(linkElement.href);
  339. } else {
  340. linkElement.click();
  341. }
  342. } else {
  343. comments(event);
  344. }
  345. }
  346. break;
  347. case parentCommentKey: {
  348. let targetBlock;
  349. if (currentEntry.classList.contains("ms-1")) {
  350. targetBlock = getPrevEntry(currentEntry);
  351. } else if (currentEntry.parentElement.parentElement.parentElement.nodeName === "LI") {
  352. targetBlock = currentEntry.parentElement.parentElement.parentElement.getElementsByTagName("article")[0];
  353. }
  354. if (targetBlock) {
  355. if (expand) {
  356. collapseEntry();
  357. }
  358. selectEntry(targetBlock, true);
  359. if (expand) {
  360. expandEntry();
  361. }
  362. }
  363. }
  364. break;
  365. case linkOneKey:
  366. clickLink(1);
  367. break;
  368. case linkTwoKey:
  369. clickLink(2);
  370. break;
  371. case linkThreeKey:
  372. clickLink(3);
  373. break;
  374. case linkFourKey:
  375. clickLink(4);
  376. break;
  377. case linkFiveKey:
  378. clickLink(5);
  379. break;
  380. case linkSixKey:
  381. clickLink(6);
  382. break;
  383. case linkSevenKey:
  384. clickLink(7);
  385. break;
  386. case linkEightKey:
  387. clickLink(8);
  388. break;
  389. case linkNineKey:
  390. clickLink(9);
  391. break;
  392. case linkZeroKey:
  393. clickLink(0);
  394. break;
  395. case nextPageKey:
  396. case prevPageKey: {
  397. const pageButtons = Array.from(document.querySelectorAll(".paginator>button"));
  398.  
  399. if (pageButtons && (document.getElementsByClassName('paginator').length > 0)) {
  400. const buttonText = event.code === nextPageKey ? "Next" : "Prev";
  401. pageButtons.find(btn => btn.innerHTML === buttonText).click();
  402. }
  403. // Jump next block of comments
  404. if (event.code === nextPageKey) {
  405. commentBlock = getNextEntrySameLevel(currentEntry);
  406. }
  407. // Jump previous block of comments
  408. if (event.code === prevPageKey) {
  409. commentBlock = getPrevEntrySameLevel(currentEntry);
  410. }
  411. if (commentBlock) {
  412. if (expand) {
  413. collapseEntry();
  414. }
  415. selectEntry(commentBlock, true);
  416. if (expand) {
  417. expandEntry();
  418. }
  419. }
  420. }
  421. }
  422. break;
  423. case modalMode = 1:
  424. switch (event.code) {
  425. case escapeKey:
  426. modalMode = 0;
  427. console.log(`modalMode: ${modalMode}`);
  428. break;
  429. case popupKey:
  430. goToDialog("close");
  431. break;
  432. case modalSubscribedKey:
  433. let subelement = document.querySelectorAll('[title="Shows the communities you\'ve subscribed to"]')[0];
  434. subelement.click();
  435. goToDialog("close");
  436. break;
  437. case modalLocalKey:
  438. let localelement = document.querySelectorAll('[title="Shows only local communities"]')[0];
  439. localelement.click();
  440. goToDialog("close");
  441. break;
  442. case modalAllKey:
  443. let allelement = document.querySelectorAll('[title="Shows all communities, including federated ones"]')[0];
  444. allelement.click();
  445. goToDialog("close");
  446. break;
  447. case modalSavedKey:
  448. if (window.location.pathname.includes("/u/")) {
  449. let savedelement = document.getElementsByClassName("btn btn-outline-secondary pointer")[3];
  450. if (savedelement) {
  451. savedelement.click();
  452. goToDialog("close");
  453. }
  454. } else {
  455. instanceAndUser("saved");
  456. }
  457. break;
  458. case modalFrontpageKey:
  459. frontpage();
  460. break;
  461. case modalProfileKey:
  462. let profileelement = document.querySelectorAll('[title="Profile"]')[0];
  463. if (profileelement) {
  464. profileelement.click();
  465. goToDialog("close");
  466. } else {
  467. instanceAndUser("profile");
  468. }
  469. break;
  470. case modalInboxKey:
  471. let notifelement = document.getElementsByClassName("nav-link d-inline-flex align-items-center d-md-inline-block")[2];
  472. if (notifelement) {
  473. notifelement.click();
  474. goToDialog("close");
  475. } else {
  476. window.location.replace(window.location.origin + "/login");
  477. }
  478. break;
  479. case modalCommentsKey:
  480. let commentsbutton = document.getElementsByClassName("pointer btn btn-outline-secondary")[1];
  481. commentsbutton.click();
  482. goToDialog("close");
  483. break;
  484. case modalPostsKey:
  485. let postsbutton = document.getElementsByClassName("pointer btn btn-outline-secondary")[0];
  486. postsbutton.click();
  487. goToDialog("close");
  488. break;
  489. case modalToggleNavigationKey:
  490. //set to opposite current
  491. localStorage.setItem('vimKeyNavigation', !vimKeyNavigation);
  492. window.location.reload();
  493. break;
  494. }
  495. }
  496. }
  497.  
  498. function getNextEntry(e) {
  499. const currentEntryIndex = Array.from(entries).indexOf(e);
  500.  
  501. if (currentEntryIndex + 1 >= entries.length) {
  502. return e;
  503. }
  504. return entries[currentEntryIndex + 1];
  505. }
  506.  
  507. function getPrevEntry(e) {
  508. const currentEntryIndex = Array.from(entries).indexOf(e);
  509.  
  510. if (currentEntryIndex - 1 < 0) {
  511. return e;
  512. }
  513. return entries[currentEntryIndex - 1];
  514. }
  515.  
  516. function getNextEntrySameLevel(e) {
  517. const nextSibling = e.parentElement.nextElementSibling;
  518.  
  519. if (!nextSibling || nextSibling.getElementsByTagName("article").length < 1) {
  520. return getNextEntry(e);
  521. }
  522.  
  523. return nextSibling.getElementsByTagName("article")[0];
  524. }
  525.  
  526. function getPrevEntrySameLevel(e) {
  527. const prevSibling = e.parentElement.previousElementSibling;
  528.  
  529. if (!prevSibling || prevSibling.getElementsByTagName("article").length < 1) {
  530. return getPrevEntry(e);
  531. }
  532.  
  533. return prevSibling.getElementsByTagName("article")[0];
  534. }
  535.  
  536. function clickEntry(event) {
  537. const e = event.currentTarget;
  538. const target = event.target;
  539.  
  540. // Deselect if already selected, also ignore if clicking on any link/button
  541. if (e === currentEntry && e.classList.contains(selectedClass) &&
  542. !(
  543. target.tagName.toLowerCase() === "button" || target.tagName.toLowerCase() === "a" ||
  544. target.parentElement.tagName.toLowerCase() === "button" ||
  545. target.parentElement.tagName.toLowerCase() === "a" ||
  546. target.parentElement.parentElement.tagName.toLowerCase() === "button" ||
  547. target.parentElement.parentElement.tagName.toLowerCase() === "a"
  548. )
  549. ) {
  550. e.classList.remove(selectedClass);
  551. } else {
  552. selectEntry(e);
  553. }
  554. }
  555.  
  556. function selectEntry(e, scrollIntoView = false) {
  557. if (currentEntry) {
  558. currentEntry.classList.remove(selectedClass);
  559. let linkNumber = currentEntry.querySelectorAll(".linkNumber");
  560. if (linkNumber) {
  561. for (const link of linkNumber) {
  562. link.remove();
  563. }
  564. }
  565. }
  566. currentEntry = e;
  567. currentEntry.classList.add(selectedClass);
  568. sessionCurrentEntry("save");
  569. let links = currentEntry.getElementsByClassName("md-div")[0];
  570. if (links) {
  571. let alink = links.querySelectorAll('a');
  572. if (alink.length > 0) {
  573. alink.forEach(function (value, i) {
  574. let linkNumber = document.createElement("span");
  575. linkNumber.classList.add("linkNumber");
  576. linkNumber.style.fontSize = "9px";
  577. linkNumber.style.lineHeight = 0;
  578. linkNumber.style.verticalAlign = "super";
  579. linkNumber.setAttribute("data-text", `[${i+1}]`);
  580. linkNumber.innerText = `[${i+1}]`;
  581. linkNumber.title = `Press ${i+1} to open link`;
  582. if (i <= 9) {
  583. value.appendChild(linkNumber);
  584. }
  585. });
  586. }
  587. }
  588.  
  589. if (scrollIntoView) {
  590. scrollIntoViewWithOffset(e, pageOffset);
  591. }
  592. }
  593.  
  594. function sessionCurrentEntry(n) {
  595. const sessionEntry = sessionStorage.getItem('currentselection');
  596. const currentEntryIndex = Array.from(entries).indexOf(currentEntry);
  597.  
  598. if (n === "save") {
  599. if (document.querySelector(".home")) {
  600. sessionStorage.setItem('currentselection', currentEntryIndex);
  601. }
  602. } else if (n === "restore") {
  603. selectEntry(entries[sessionEntry]);
  604. console.log(`Set to entry ${sessionEntry}`);
  605. }
  606. }
  607.  
  608. function clickLink(n) {
  609. let links = currentEntry.getElementsByClassName("md-div")[0];
  610. let alink = links.querySelectorAll('a');
  611. if (n === 1) {
  612. window.open(
  613. alink[0].href
  614. );
  615. } else if (n === 2) {
  616. window.open(
  617. alink[1].href
  618. );
  619. } else if (n === 3) {
  620. window.open(
  621. alink[2].href
  622. );
  623. } else if (n === 4) {
  624. window.open(
  625. alink[3].href
  626. );
  627. } else if (n === 5) {
  628. window.open(
  629. alink[4].href
  630. );
  631. } else if (n === 6) {
  632. window.open(
  633. alink[5].href
  634. );
  635. } else if (n === 7) {
  636. window.open(
  637. alink[6].href
  638. );
  639. } else if (n === 8) {
  640. window.open(
  641. alink[7].href
  642. );
  643. } else if (n === 9) {
  644. window.open(
  645. alink[8].href
  646. );
  647. } else if (n === 0) {
  648. window.open(
  649. alink[9].href
  650. );
  651. }
  652. }
  653.  
  654. function isExpanded() {
  655. if (
  656. currentEntry.querySelector("a.d-inline-block:not(.thumbnail)") ||
  657. currentEntry.querySelector("#postContent") ||
  658. currentEntry.querySelector(".card-body")
  659. ) {
  660. return true;
  661. }
  662.  
  663. return false;
  664. }
  665.  
  666. function previousKey(event) {
  667. let selectedEntry;
  668. // Next button
  669. if (event.code === nextKey) {
  670. if (event.shiftKey && vimKeyNavigation) {
  671. selectedEntry = getNextEntrySameLevel(currentEntry);
  672.  
  673. } else {
  674. selectedEntry = getNextEntry(currentEntry);
  675. }
  676. }
  677. // Previous button
  678. if (event.code === prevKey) {
  679. if (event.shiftKey && vimKeyNavigation) {
  680. selectedEntry = getPrevEntrySameLevel(currentEntry);
  681.  
  682. } else {
  683. selectedEntry = getPrevEntry(currentEntry);
  684. }
  685. }
  686. if (selectedEntry) {
  687. if (expand) {
  688. collapseEntry();
  689. }
  690. selectEntry(selectedEntry, true);
  691. if (expand) {
  692. expandEntry();
  693. }
  694. }
  695. }
  696.  
  697. function upVote() {
  698. const upvoteButton = currentEntry.querySelector("button[aria-label='Upvote']");
  699.  
  700. if (upvoteButton) {
  701. upvoteButton.click();
  702. }
  703. }
  704.  
  705. function downVote() {
  706. const downvoteButton = currentEntry.querySelector("button[aria-label='Downvote']");
  707.  
  708. if (downvoteButton) {
  709. downvoteButton.click();
  710. }
  711. }
  712.  
  713. function goToDialog(n) {
  714.  
  715. const closeButton = document.getElementsByClassName("CLOSEBUTTON1")[0];
  716. closeButton.addEventListener("click", () => {
  717. myDialog.close();
  718. modalMode = 0;
  719. console.log(`modalMode: ${modalMode}`);
  720. });
  721. if (n === "open") {
  722. myDialog.showModal();
  723. modalMode = 1;
  724. console.log(`modalMode: ${modalMode}`);
  725. }
  726.  
  727. if (n === "close") {
  728. myDialog.close();
  729. modalMode = 0;
  730. console.log(`modalMode: ${modalMode}`);
  731. }
  732. }
  733.  
  734. function instanceAndUser(n) {
  735. let currentInstance = window.location.origin;
  736. let dropdownUser = document.getElementsByClassName("btn dropdown-toggle")[0];
  737. let username;
  738. if (dropdownUser) {
  739. username = dropdownUser.textContent;
  740. }
  741. if (n === "profile") {
  742. if (username) {
  743. let userlink = currentInstance + "/u/" + username;
  744. window.location.replace(userlink);
  745. } else {
  746. window.location.replace(currentInstance + "/login");
  747. }
  748. }
  749. if (n === "saved") {
  750. if (username) {
  751. let savedlink = currentInstance + "/u/" + username + "?page=1&sort=New&view=Saved";
  752. window.location.replace(savedlink);
  753. } else {
  754. window.location.replace(currentInstance + "/login");
  755. }
  756. }
  757. }
  758.  
  759. function frontpage() {
  760. let homeElement = document.getElementsByClassName("d-flex align-items-center navbar-brand me-md-3 active")[0];
  761. if (homeElement) {
  762. homeElement.click();
  763. goToDialog("close");
  764. } else {
  765. window.location.replace(window.location.origin);
  766. }
  767. }
  768.  
  769. function reply(event) {
  770. const replyButton = currentEntry.querySelector("button[data-tippy-content='reply']");
  771.  
  772. if (replyButton) {
  773. event.preventDefault();
  774. replyButton.click();
  775. }
  776. }
  777.  
  778. function community(event) {
  779. if (event.shiftKey) {
  780. window.open(
  781. currentEntry.querySelector("a.community-link").href
  782. );
  783. } else {
  784. currentEntry.querySelector("a.community-link").click();
  785. }
  786. }
  787.  
  788. function visitUser(event) {
  789. if (event.shiftKey) {
  790. window.open(
  791. currentEntry.getElementsByClassName("person-listing d-inline-flex align-items-baseline text-info")[0].href
  792. );
  793. } else {
  794. currentEntry.getElementsByClassName("person-listing d-inline-flex align-items-baseline text-info")[0].click();
  795. }
  796. }
  797.  
  798. function comments(event) {
  799. if (event.shiftKey) {
  800. window.open(
  801. currentEntry.querySelector("a.btn[title*='Comment']").href
  802. );
  803. } else {
  804. currentEntry.querySelector("a.btn[title*='Comment']").click();
  805. }
  806. }
  807.  
  808. function getContext(event) {
  809. if (event.shiftKey) {
  810. window.open(
  811. currentEntry.getElementsByClassName("btn btn-link btn-animate text-muted btn-sm")[0].href
  812. );
  813. } else {
  814. currentEntry.getElementsByClassName("btn btn-link btn-animate text-muted btn-sm")[0].click();
  815. }
  816. }
  817.  
  818. let maxSize = 0;
  819.  
  820. function imgResize(n) {
  821. let expandedImg = currentEntry.getElementsByClassName("overflow-hidden pictrs-image img-fluid img-expanded slight-radius")[0];
  822. let expandedHeight = expandedImg.height;
  823. let expandedWidth = expandedImg.width;
  824. let expandedHeightbefore = expandedHeight;
  825. let expandedWidthbefore = expandedWidth;
  826.  
  827. if (n === "smaller") {
  828. expandedHeight = expandedHeight / 1.15;
  829. expandedWidth = expandedWidth / 1.15;
  830. expandedImg.style.height = expandedHeight + 'px';
  831. expandedImg.style.width = expandedWidth + 'px';
  832. maxSize = 0;
  833. console.log(`maxSize: ${maxSize}`);
  834. }
  835.  
  836. if (n === "larger") {
  837. expandedHeight = expandedHeight * 1.15;
  838. expandedWidth = expandedWidth * 1.15;
  839. expandedImg.style.width = expandedWidth + 'px';
  840. expandedImg.style.height = expandedHeight + 'px';
  841.  
  842. if (maxSize === 1) {
  843. expandedImg.style.width = expandedWidthbefore + 'px';
  844. expandedImg.style.height = expandedHeightbefore + 'px';
  845. }
  846. if (expandedImg.width !== Math.round(expandedWidth) || expandedImg.height !== Math.round(expandedHeight)) {
  847. maxSize = 1;
  848. console.log(`maxSize: ${maxSize}`);
  849. }
  850. }
  851. }
  852.  
  853. function save() {
  854. const saveButton = currentEntry.querySelector("button[aria-label='save']");
  855. const unsaveButton = currentEntry.querySelector("button[aria-label='unsave']");
  856. const moreButton = currentEntry.querySelector("button[aria-label='more']");
  857. if (saveButton) {
  858. saveButton.click();
  859. } else if (unsaveButton) {
  860. unsaveButton.click();
  861. } else {
  862. moreButton.click();
  863. if (saveButton) {
  864. saveButton.click();
  865. } else if (unsaveButton) {
  866. unsaveButton.click();
  867. }
  868. }
  869. }
  870.  
  871. function edit() {
  872. let editButton = currentEntry.querySelector("button[aria-label='Edit']");
  873. let moreButton = currentEntry.querySelector("button[aria-label='more']");
  874.  
  875. if (editButton) {
  876. editButton.click();
  877. } else {
  878. moreButton.click();
  879. }
  880. }
  881.  
  882. function toggleExpand() {
  883. const expandButton = currentEntry.querySelector("button[aria-label='Expand here']");
  884. const textExpandButton = currentEntry.querySelector(".post-title>button");
  885. const commentExpandButton = currentEntry.querySelector(".ms-2>div>button");
  886. const moreExpandButton = currentEntry.querySelector(".ms-1>button");
  887.  
  888. if (expandButton) {
  889. expandButton.click();
  890.  
  891. // Scroll into view if picture/text preview cut off
  892. const imgContainer = currentEntry.querySelector("a.d-inline-block");
  893.  
  894. if (imgContainer) {
  895. // Check container positions once image is loaded
  896. imgContainer.querySelector("img").addEventListener("load", function() {
  897. scrollIntoViewWithOffset(
  898. imgContainer,
  899. currentEntry.offsetHeight - imgContainer.offsetHeight + pageOffset
  900. );
  901. }, true);
  902. currentEntry.getElementsByClassName("offset-sm-3 my-2 d-none d-sm-block")[0].className = "my-2 d-none d-sm-block";
  903. }
  904. }
  905.  
  906. if (textExpandButton) {
  907. textExpandButton.click();
  908.  
  909. const textContainers = [currentEntry.querySelector("#postContent"), currentEntry.querySelector(".card-body")];
  910. textContainers.forEach(container => {
  911. if (container) {
  912. scrollIntoViewWithOffset(
  913. container,
  914. currentEntry.offsetHeight - container.offsetHeight + pageOffset
  915. );
  916. }
  917. });
  918. }
  919.  
  920. if (commentExpandButton) {
  921. commentExpandButton.click();
  922. }
  923.  
  924. if (moreExpandButton) {
  925. moreExpandButton.click();
  926. selectEntry(getPrevEntry(currentEntry), true);
  927. }
  928. }
  929.  
  930. function expandEntry() {
  931. if (!isExpanded()) {
  932. toggleExpand();
  933. }
  934. }
  935.  
  936. function collapseEntry() {
  937. if (isExpanded()) {
  938. toggleExpand();
  939. }
  940. }
  941.  
  942. function scrollIntoViewWithOffset(e, offset) {
  943. const y = e.getBoundingClientRect().top + window.scrollY - offset;
  944. if (scrollPosition === "middle") {
  945. if (e.getBoundingClientRect().top < 0 ||
  946. e.getBoundingClientRect().bottom > window.innerHeight
  947. ) {
  948. scrollPage(y);
  949. }
  950. } else if (scrollPosition === "top") {
  951. scrollPage(y);
  952. }
  953. }
  954.  
  955. function scrollPage(y) {
  956. if (smoothScroll) {
  957. window.scrollTo({
  958. top: y,
  959. behavior: "smooth"
  960. });
  961. } else {
  962. window.scrollTo({
  963. top: y
  964. });
  965. }
  966. }
  967.  
  968. }