lemmy-keyboard-navigation

Easily navigate Lemmy with your keyboard

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

  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.  
  19.  
  20. //isLemmySite
  21. if (document.querySelectorAll('.lemmy-site').length >= 1){
  22.  
  23. // Vim key toggle
  24. // Default: true
  25. // Set to false for arrow key navigation
  26. var vimKeyNavigation = true;
  27.  
  28. // Set selected entry colors
  29. const backgroundColor = '#373737';
  30. const textColor = 'white';
  31.  
  32. // Set navigation keys with keycodes here: https://www.toptal.com/developers/keycode
  33. let nextKey = 'ArrowDown';
  34. let prevKey = 'ArrowUp';
  35. let nextPageKey = 'ArrowRight';
  36. let prevPageKey = 'ArrowLeft';
  37.  
  38. if (vimKeyNavigation) {
  39. nextKey = 'KeyJ';
  40. prevKey = 'KeyK';
  41. nextPageKey = 'KeyL';
  42. prevPageKey = 'KeyH';
  43. }
  44.  
  45. const expandKey = 'KeyX';
  46. const openCommentsKey = 'KeyC';
  47. const openLinkandcollapseKey = 'Enter';
  48. const parentComment = 'KeyP';
  49. const upvoteKey = 'KeyA';
  50. const downvoteKey = 'KeyZ';
  51. const replycommKey = 'KeyR';
  52. const saveKey = 'KeyS';
  53. const popupKey = 'KeyG';
  54. const contextKey = 'KeyQ';
  55. const smallerimgKey = 'Minus';
  56. const biggerimgKey = 'Equal';
  57. const userKey = 'KeyU';
  58. const editKey = 'KeyE';
  59. const inputSwitchKey = 'KeyV';
  60.  
  61. const modalCommentsKey = 'KeyC';
  62. const modalPostsKey = 'KeyP';
  63. const modalSubscribedKey = 'Digit1';
  64. const modalLocalKey = 'Digit2';
  65. const modalAllKey = 'Digit3';
  66. const modalSavedKey = 'KeyS';
  67. const modalFrontpageKey = 'KeyF';
  68. const modalProfileKey = 'KeyU';
  69. const modalInboxKey = 'KeyI';
  70.  
  71. const escapeKey = 'Escape';
  72. let modalMode = 0;
  73. console.log('modalMode: ' + modalMode);
  74.  
  75. // Stop arrows from moving the page if not using Vim navigation
  76. window.addEventListener("keydown", function(e) {
  77. if (["ArrowUp", "ArrowDown"].indexOf(e.code) > -1 && !vimKeyNavigation) {
  78. e.preventDefault();
  79. }
  80. }, false);
  81.  
  82. // Remove scroll animations
  83. document.documentElement.style = "scroll-behavior: auto";
  84.  
  85. // Set CSS for selected entry
  86. const css = [
  87. ".selected {",
  88. " background-color: " + backgroundColor + " !important;",
  89. " color: " + textColor + ";",
  90. "}"
  91. ].join("\n");
  92.  
  93. // dialog box
  94. let myDialog = document.createElement("dialog");
  95. document.body.appendChild(myDialog);
  96. let para = document.createElement("p");
  97. para.innerText = '--- Frontpage Sort ---\nP = posts\nC = comments\n1 = subscribed\n2 = local\n3 = all\n\n--- Everywhere Else ---\nS = saved\nF = frontpage\nU = profile\nI = inbox\nV = Toggle input style';
  98. myDialog.appendChild(para);
  99. let button = document.createElement("button");
  100. button.classList.add('CLOSEBUTTON1');
  101. button.innerHTML = 'Press ESC or G to Close';
  102. myDialog.appendChild(button);
  103.  
  104. // Global variables
  105. let currentEntry;
  106. let commentBlock;
  107. let addStyle;
  108. let PRO_addStyle;
  109. let entries = [];
  110. let previousUrl = "";
  111. let expand = false;
  112.  
  113. if (typeof GM_addStyle !== "undefined") {
  114. GM_addStyle(css);
  115. } else if (typeof PRO_addStyle !== "undefined") {
  116. PRO_addStyle(css);
  117. } else if (typeof addStyle !== "undefined") {
  118. addStyle(css);
  119. } else {
  120. let node = document.createElement("style");
  121. node.type = "text/css";
  122. node.appendChild(document.createTextNode(css));
  123. let heads = document.getElementsByTagName("head");
  124. if (heads.length > 0) {
  125. heads[0].appendChild(node);
  126. } else {
  127. // no head yet, stick it whereever
  128. document.documentElement.appendChild(node);
  129. }
  130. }
  131. const selectedClass = "selected";
  132.  
  133. const targetNode = document.documentElement;
  134. const config = {
  135. childList: true,
  136. subtree: true
  137. };
  138.  
  139. const observer = new MutationObserver(() => {
  140. entries = document.querySelectorAll(".post-listing, .comment-node");
  141.  
  142. if (entries.length > 0) {
  143. if (location.href !== previousUrl) {
  144. previousUrl = location.href;
  145. currentEntry = null;
  146. }
  147. init();
  148. }
  149. });
  150.  
  151. observer.observe(targetNode, config);
  152.  
  153. function init() {
  154.  
  155. // If jumping to comments
  156. if (window.location.search.includes("scrollToComments=true") &&
  157. entries.length > 1 &&
  158. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  159. ) {
  160. selectEntry(entries[1], true);
  161. }
  162. // If jumping to comment from anchor link
  163. else if (window.location.pathname.includes("/comment/") &&
  164. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  165. ) {
  166. const commentId = window.location.pathname.replace("/comment/", "");
  167. const anchoredEntry = document.getElementById("comment-" + commentId);
  168.  
  169. if (anchoredEntry) {
  170. selectEntry(anchoredEntry, true);
  171. }
  172. }
  173. // If no entries yet selected, default to first
  174. else if (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0) {
  175. selectEntry(entries[0]);
  176. }
  177.  
  178. Array.from(entries).forEach(entry => {
  179. entry.removeEventListener("click", clickEntry, true);
  180. entry.addEventListener('click', clickEntry, true);
  181. });
  182.  
  183. document.removeEventListener("keydown", handleKeyPress, true);
  184. document.addEventListener("keydown", handleKeyPress, true);
  185. }
  186.  
  187. function handleKeyPress(event) {
  188. if (["TEXTAREA", "INPUT"].indexOf(event.target.tagName) > -1 || event.metaKey) {
  189. return;
  190. }
  191.  
  192. switch (modalMode) {
  193. case modalMode = 0:
  194. switch (event.code) {
  195. case nextKey:
  196. case prevKey:
  197. previousKey(event);
  198. break;
  199. case upvoteKey:
  200. upVote();
  201. break;
  202. case downvoteKey:
  203. downVote();
  204. break;
  205. case expandKey:
  206. toggleExpand();
  207. expand = isExpanded() ? true : false;
  208. break;
  209. case smallerimgKey:
  210. imgresize(0);
  211. break;
  212. case biggerimgKey:
  213. imgresize(1);
  214. break;
  215. case saveKey:
  216. save();
  217. break;
  218. case editKey:
  219. edit();
  220. break;
  221. case openCommentsKey:
  222. comments(event);
  223. break;
  224. case popupKey:
  225. gotodialog(1);
  226. instanceanduser();
  227. break;
  228. case contextKey:
  229. getcontext(event);
  230. break;
  231. case replycommKey:
  232. if (window.location.pathname.includes("/post/")) {
  233. reply(event);
  234. } else {
  235. community(event);
  236. }
  237. break;
  238. case userKey:
  239. visituser(event);
  240. break;
  241. case openLinkandcollapseKey:
  242. if (window.location.pathname.includes("/post/")) {
  243. toggleExpand();
  244. } else {
  245. const linkElement = currentEntry.querySelector(".col.flex-grow-1>p>a");
  246. if (linkElement) {
  247. if (event.shiftKey) {
  248. window.open(linkElement.href);
  249. } else {
  250. linkElement.click();
  251. }
  252. } else {
  253. comments(event);
  254. }
  255. }
  256. break;
  257. case parentComment: {
  258. let targetBlock;
  259. if (currentEntry.classList.contains("ms-1")) {
  260. targetBlock = getPrevEntry(currentEntry);
  261. } else if (currentEntry.parentElement.parentElement.parentElement.nodeName === "LI") {
  262. targetBlock = currentEntry.parentElement.parentElement.parentElement.getElementsByTagName("article")[0];
  263. }
  264. if (targetBlock) {
  265. if (expand) {
  266. collapseEntry();
  267. }
  268. selectEntry(targetBlock, true);
  269. if (expand) {
  270. expandEntry();
  271. }
  272. }
  273. }
  274. break;
  275. case nextPageKey:
  276. case prevPageKey: {
  277. const pageButtons = Array.from(document.querySelectorAll(".paginator>button"));
  278.  
  279. if (pageButtons && (document.getElementsByClassName('paginator').length > 0)) {
  280. const buttonText = event.code === nextPageKey ? "Next" : "Prev";
  281. pageButtons.find(btn => btn.innerHTML === buttonText).click();
  282. }
  283. // Jump next block of comments
  284. if (event.code === nextPageKey) {
  285. commentBlock = getNextEntrySameLevel(currentEntry);
  286. }
  287. // Jump previous block of comments
  288. if (event.code === prevPageKey) {
  289. commentBlock = getPrevEntrySameLevel(currentEntry);
  290. }
  291. if (commentBlock) {
  292. if (expand) {
  293. collapseEntry();
  294. }
  295. selectEntry(commentBlock, true);
  296. if (expand) {
  297. expandEntry();
  298. }
  299. }
  300. }
  301. }
  302. break;
  303. case modalMode = 1:
  304. switch (event.code) {
  305. case escapeKey:
  306. modalMode = 0;
  307. console.log('modalMode: ' + modalMode);
  308. break;
  309. case popupKey:
  310. gotodialog(0);
  311. break;
  312. case inputSwitchKey:
  313. vimKeyNavigation = !vimKeyNavigation;
  314. //Repeat definitions
  315. if (vimKeyNavigation) {
  316. nextKey = 'KeyJ';
  317. prevKey = 'KeyK';
  318. nextPageKey = 'KeyL';
  319. prevPageKey = 'KeyH';
  320. }else{
  321. nextKey = 'ArrowDown';
  322. prevKey = 'ArrowUp';
  323. nextPageKey = 'ArrowRight';
  324. prevPageKey = 'ArrowLeft';
  325. }
  326. gotodialog(0);
  327. break;
  328. case modalSubscribedKey:
  329. let subelement = document.querySelectorAll('[title="Shows the communities you\'ve subscribed to"]')[0];
  330. subelement.click();
  331. gotodialog(0);
  332. break;
  333. case modalLocalKey:
  334. let localelement = document.querySelectorAll('[title="Shows only local communities"]')[0];
  335. localelement.click();
  336. gotodialog(0);
  337. break;
  338. case modalAllKey:
  339. let allelement = document.querySelectorAll('[title="Shows all communities, including federated ones"]')[0];
  340. allelement.click();
  341. gotodialog(0);
  342. break;
  343. case modalSavedKey:
  344. if (window.location.pathname.includes("/u/")) {
  345. let savedelement = document.getElementsByClassName("btn btn-outline-secondary pointer")[3];
  346. if (savedelement) {
  347. savedelement.click();
  348. gotodialog(0);
  349. }
  350. } else {
  351. instanceanduser(2);
  352. }
  353. break;
  354. case modalFrontpageKey:
  355. frontpage();
  356. break;
  357. case modalProfileKey:
  358. let profileelement = document.querySelectorAll('[title="Profile"]')[0];
  359. if (profileelement) {
  360. profileelement.click();
  361. gotodialog(0);
  362. } else {
  363. instanceanduser(1);
  364. }
  365. break;
  366. case modalInboxKey:
  367. let notifelement = document.getElementsByClassName("nav-link d-inline-flex align-items-center d-md-inline-block")[2];
  368. if (notifelement) {
  369. notifelement.click();
  370. gotodialog(0);
  371. } else {
  372. console.log('Not logged in!');
  373. }
  374. break;
  375. case modalCommentsKey:
  376. let commentsbutton = document.getElementsByClassName("pointer btn btn-outline-secondary")[1];
  377. commentsbutton.click();
  378. gotodialog(0);
  379. break;
  380. case modalPostsKey:
  381. let postsbutton = document.getElementsByClassName("pointer btn btn-outline-secondary")[0];
  382. postsbutton.click();
  383. gotodialog(0);
  384. break;
  385. }
  386. }
  387. }
  388.  
  389. function getNextEntry(e) {
  390. const currentEntryIndex = Array.from(entries).indexOf(e);
  391.  
  392. if (currentEntryIndex + 1 >= entries.length) {
  393. return e;
  394. }
  395.  
  396. return entries[currentEntryIndex + 1];
  397. }
  398.  
  399. function getPrevEntry(e) {
  400. const currentEntryIndex = Array.from(entries).indexOf(e);
  401.  
  402. if (currentEntryIndex - 1 < 0) {
  403. return e;
  404. }
  405.  
  406. return entries[currentEntryIndex - 1];
  407. }
  408.  
  409. function getNextEntrySameLevel(e) {
  410. const nextSibling = e.parentElement.nextElementSibling;
  411.  
  412. if (!nextSibling || nextSibling.getElementsByTagName("article").length < 1) {
  413. return getNextEntry(e);
  414. }
  415.  
  416. return nextSibling.getElementsByTagName("article")[0];
  417. }
  418.  
  419. function getPrevEntrySameLevel(e) {
  420. const prevSibling = e.parentElement.previousElementSibling;
  421.  
  422. if (!prevSibling || prevSibling.getElementsByTagName("article").length < 1) {
  423. return getPrevEntry(e);
  424. }
  425.  
  426. return prevSibling.getElementsByTagName("article")[0];
  427. }
  428.  
  429. function clickEntry(event) {
  430. const e = event.currentTarget;
  431. const target = event.target;
  432.  
  433. // Deselect if already selected, also ignore if clicking on any link/button
  434. if (e === currentEntry && e.classList.contains(selectedClass) &&
  435. !(
  436. target.tagName.toLowerCase() === "button" || target.tagName.toLowerCase() === "a" ||
  437. target.parentElement.tagName.toLowerCase() === "button" ||
  438. target.parentElement.tagName.toLowerCase() === "a" ||
  439. target.parentElement.parentElement.tagName.toLowerCase() === "button" ||
  440. target.parentElement.parentElement.tagName.toLowerCase() === "a"
  441. )
  442. ) {
  443. e.classList.remove(selectedClass);
  444. } else {
  445. selectEntry(e);
  446. }
  447. }
  448.  
  449. function selectEntry(e, scrollIntoView = false) {
  450. if (currentEntry) {
  451. currentEntry.classList.remove(selectedClass);
  452. }
  453. currentEntry = e;
  454. currentEntry.classList.add(selectedClass);
  455.  
  456. if (scrollIntoView) {
  457. scrollIntoViewWithOffset(e, 15);
  458. }
  459. }
  460.  
  461. function isExpanded() {
  462. if (
  463. currentEntry.querySelector("a.d-inline-block:not(.thumbnail)") ||
  464. currentEntry.querySelector("#postContent") ||
  465. currentEntry.querySelector(".card-body")
  466. ) {
  467. return true;
  468. }
  469.  
  470. return false;
  471. }
  472.  
  473. function previousKey(event) {
  474. let selectedEntry;
  475. // Next button
  476. if (event.code === nextKey) {
  477. if (event.shiftKey && vimKeyNavigation) {
  478. selectedEntry = getNextEntrySameLevel(currentEntry);
  479.  
  480. } else {
  481. selectedEntry = getNextEntry(currentEntry);
  482. }
  483. }
  484. // Previous button
  485. if (event.code === prevKey) {
  486. if (event.shiftKey && vimKeyNavigation) {
  487. selectedEntry = getPrevEntrySameLevel(currentEntry);
  488.  
  489. } else {
  490. selectedEntry = getPrevEntry(currentEntry);
  491. }
  492. }
  493. if (selectedEntry) {
  494. if (expand) {
  495. collapseEntry();
  496. }
  497. selectEntry(selectedEntry, true);
  498. if (expand) {
  499. expandEntry();
  500. }
  501. }
  502. }
  503.  
  504. function upVote() {
  505. const upvoteButton = currentEntry.querySelector("button[aria-label='Upvote']");
  506.  
  507. if (upvoteButton) {
  508. upvoteButton.click();
  509. }
  510. }
  511.  
  512. function downVote() {
  513. const downvoteButton = currentEntry.querySelector("button[aria-label='Downvote']");
  514.  
  515. if (downvoteButton) {
  516. downvoteButton.click();
  517. }
  518. }
  519.  
  520. function gotodialog(n) {
  521.  
  522. const closeButton = document.getElementsByClassName("CLOSEBUTTON1")[0];
  523. closeButton.addEventListener("click", () => {
  524. myDialog.close();
  525. modalMode = 0;
  526. console.log('modalMode: ' + modalMode);
  527. });
  528. if (n === 1) {
  529. myDialog.showModal();
  530. modalMode = 1;
  531. console.log('modalMode: ' + modalMode);
  532. }
  533.  
  534. if (n === 0) {
  535. myDialog.close();
  536. modalMode = 0;
  537. console.log('modalMode: ' + modalMode);
  538. }
  539. }
  540.  
  541. function instanceanduser(n) {
  542. let currentinstance = window.location.origin;
  543. let dropdownuser = document.getElementsByClassName("btn dropdown-toggle")[0];
  544. let username = dropdownuser.textContent;
  545.  
  546. if (n === 0) {
  547. window.location.replace(currentinstance);
  548. }
  549. if (n === 1) {
  550. if (username) {
  551. let userlink = currentinstance + "/u/" + username;
  552. window.location.replace(userlink);
  553. } else {
  554. console.log('Not logged in!');
  555. frontpage();
  556. }
  557. }
  558. if (n === 2) {
  559. if (username) {
  560. let savedlink = currentinstance + "/u/" + username + "?page=1&sort=New&view=Saved";
  561. window.location.replace(savedlink);
  562. } else {
  563. console.log('Not logged in!');
  564. frontpage();
  565. }
  566. }
  567. }
  568.  
  569. function frontpage() {
  570. let homeelement = document.getElementsByClassName("d-flex align-items-center navbar-brand me-md-3 active")[0];
  571. if (homeelement) {
  572. homeelement.click();
  573. gotodialog(0);
  574. } else {
  575. instanceanduser(0);
  576. }
  577. }
  578.  
  579. function reply(event) {
  580. const replyButton = currentEntry.querySelector("button[data-tippy-content='reply']");
  581.  
  582. if (replyButton) {
  583. event.preventDefault();
  584. replyButton.click();
  585. }
  586. }
  587.  
  588. function community(event) {
  589. if (event.shiftKey) {
  590. window.open(
  591. currentEntry.querySelector("a.community-link").href,
  592. );
  593. } else {
  594. currentEntry.querySelector("a.community-link").click();
  595. }
  596. }
  597.  
  598. function visituser(event) {
  599. if (event.shiftKey) {
  600. window.open(
  601. currentEntry.getElementsByClassName("person-listing d-inline-flex align-items-baseline text-info")[0].href,
  602. );
  603. } else {
  604. currentEntry.getElementsByClassName("person-listing d-inline-flex align-items-baseline text-info")[0].click();
  605. }
  606. }
  607.  
  608. function comments(event) {
  609. if (event.shiftKey) {
  610. window.open(
  611. currentEntry.querySelector("a.btn[title*='Comment']").href,
  612. );
  613. } else {
  614. currentEntry.querySelector("a.btn[title*='Comment']").click();
  615. }
  616. }
  617.  
  618. function getcontext(event) {
  619. if (event.shiftKey) {
  620. window.open(
  621. currentEntry.getElementsByClassName("btn btn-link btn-animate text-muted btn-sm")[0].href,
  622. );
  623. } else {
  624. currentEntry.getElementsByClassName("btn btn-link btn-animate text-muted btn-sm")[0].click();
  625. }
  626. }
  627.  
  628. let maxsize = 0;
  629. console.log('maxsize ' + maxsize);
  630.  
  631. function imgresize(n) {
  632. let expandedimg = currentEntry.getElementsByClassName("overflow-hidden pictrs-image img-fluid img-expanded slight-radius")[0];
  633. let expandedheight = expandedimg.height;
  634. let expandedwidth = expandedimg.width;
  635. let expandedheightbefore = expandedheight;
  636. let expandedwidthbefore = expandedwidth;
  637.  
  638. if (n === 0) {
  639. expandedheight = expandedheight / 1.15;
  640. expandedwidth = expandedwidth / 1.15;
  641. expandedimg.style.height = expandedheight + 'px';
  642. expandedimg.style.width = expandedwidth + 'px';
  643. maxsize = 0;
  644. console.log('maxsize ' + maxsize);
  645. }
  646.  
  647. if (n === 1) {
  648. expandedheight = expandedheight * 1.15;
  649. expandedwidth = expandedwidth * 1.15;
  650. expandedimg.style.width = expandedwidth + 'px';
  651. expandedimg.style.height = expandedheight + 'px';
  652.  
  653. if (maxsize === 1) {
  654. expandedimg.style.width = expandedwidthbefore + 'px';
  655. expandedimg.style.height = expandedheightbefore + 'px';
  656. }
  657. if (expandedimg.width !== Math.round(expandedwidth) || expandedimg.height !== Math.round(expandedheight)) {
  658. maxsize = 1;
  659. console.log('maxsize ' + maxsize);
  660. }
  661. }
  662. }
  663.  
  664. function save() {
  665. const saveButton = currentEntry.querySelector("button[aria-label='save']");
  666. const unsaveButton = currentEntry.querySelector("button[aria-label='unsave']");
  667. const moreButton = currentEntry.querySelector("button[aria-label='more']");
  668. if (saveButton) {
  669. saveButton.click();
  670. } else if (unsaveButton) {
  671. unsaveButton.click();
  672. } else {
  673. moreButton.click();
  674. if (saveButton) {
  675. saveButton.click();
  676. } else if (unsaveButton) {
  677. unsaveButton.click();
  678. }
  679. }
  680. }
  681.  
  682. function edit() {
  683. let editButton = currentEntry.querySelector("button[aria-label='Edit']");
  684. let moreButton = currentEntry.querySelector("button[aria-label='more']");
  685.  
  686. if (editButton) {
  687. editButton.click();
  688. } else {
  689. moreButton.click();
  690. }
  691. }
  692.  
  693. function toggleExpand() {
  694. const expandButton = currentEntry.querySelector("button[aria-label='Expand here']");
  695. const textExpandButton = currentEntry.querySelector(".post-title>button");
  696. const commentExpandButton = currentEntry.querySelector(".ms-2>div>button");
  697. const moreExpandButton = currentEntry.querySelector(".ms-1>button");
  698.  
  699. if (expandButton) {
  700. expandButton.click();
  701.  
  702. // Scroll into view if picture/text preview cut off
  703. const imgContainer = currentEntry.querySelector("a.d-inline-block");
  704.  
  705. if (imgContainer) {
  706. // Check container positions once image is loaded
  707. imgContainer.querySelector("img").addEventListener("load", function() {
  708. scrollIntoViewWithOffset(
  709. imgContainer,
  710. currentEntry.offsetHeight - imgContainer.offsetHeight + 10
  711. );
  712. }, true);
  713. currentEntry.getElementsByClassName("offset-sm-3 my-2 d-none d-sm-block")[0].className = "my-2 d-none d-sm-block";
  714. }
  715. }
  716.  
  717. if (textExpandButton) {
  718. textExpandButton.click();
  719.  
  720. const textContainers = [currentEntry.querySelector("#postContent"), currentEntry.querySelector(".card-body")];
  721. textContainers.forEach(container => {
  722. if (container) {
  723. scrollIntoViewWithOffset(
  724. container,
  725. currentEntry.offsetHeight - container.offsetHeight + 10
  726. );
  727. }
  728. });
  729. }
  730.  
  731. if (commentExpandButton) {
  732. commentExpandButton.click();
  733. }
  734.  
  735. if (moreExpandButton) {
  736. moreExpandButton.click();
  737. selectEntry(getPrevEntry(currentEntry), true);
  738. }
  739. }
  740.  
  741. function expandEntry() {
  742. if (!isExpanded()) {
  743. toggleExpand();
  744. }
  745. }
  746.  
  747. function collapseEntry() {
  748. if (isExpanded()) {
  749. toggleExpand();
  750. }
  751. }
  752.  
  753. function scrollIntoViewWithOffset(e, offset) {
  754. if (e.getBoundingClientRect().top < 0 ||
  755. e.getBoundingClientRect().bottom > window.innerHeight
  756. ) {
  757. const y = e.getBoundingClientRect().top + window.pageYOffset - offset;
  758. window.scrollTo({
  759. top: y
  760. });
  761. }
  762.  
  763. }
  764.  
  765. }