lemmy-keyboard-navigation

Easily navigate Lemmy with your keyboard

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

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