Advertisement
Guest User

Untitled

a guest
Jul 7th, 2023
170
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 11.79 KB | None | 0 0
  1. // ==UserScript==
  2. // @name lemmy navigation
  3. // @description Lemmy hotkeys for navigating.
  4. // @match https://sh.itjust.works/*
  5. // @match https://burggit.moe/*
  6. // @match https://vlemmy.net/*
  7. // @match https://lemmy.world/*
  8. // @match https://lemm.ee/*
  9. // @version 1.2
  10. // @run-at document-start
  11. // ==/UserScript==
  12.  
  13. // Set selected entry colors
  14. const backgroundColor = 'darkslategray';
  15. const textColor = 'white';
  16.  
  17. // Set navigation keys with keycodes here: https://www.toptal.com/developers/keycode
  18. const nextKey = 'KeyJ';
  19. const prevKey = 'KeyK';
  20. const expandKey = 'KeyX';
  21. const openCommentsKey = 'KeyC';
  22. const openLinkKey = 'Enter';
  23. const nextPageKey = 'KeyN';
  24. const prevPageKey = 'KeyP';
  25. const upvoteKey = 'KeyA';
  26. const downvoteKey = 'KeyZ';
  27.  
  28.  
  29. const css = [
  30. ".selected {",
  31. " background-color: " + backgroundColor + " !important;",
  32. " color: " + textColor + ";",
  33. "}"
  34. ].join("\n");
  35.  
  36. if (typeof GM_addStyle !== "undefined") {
  37. GM_addStyle(css);
  38. } else if (typeof PRO_addStyle !== "undefined") {
  39. PRO_addStyle(css);
  40. } else if (typeof addStyle !== "undefined") {
  41. addStyle(css);
  42. } else {
  43. let node = document.createElement("style");
  44. node.type = "text/css";
  45. node.appendChild(document.createTextNode(css));
  46. let heads = document.getElementsByTagName("head");
  47. if (heads.length > 0) {
  48. heads[0].appendChild(node);
  49. } else {
  50. // no head yet, stick it whereever
  51. document.documentElement.appendChild(node);
  52. }
  53. }
  54. const selectedClass = "selected";
  55.  
  56. let currentEntry;
  57. let selectedEntry;
  58. let entries = [];
  59. let previousUrl = "";
  60. let expand = false;
  61.  
  62. const targetNode = document.documentElement;
  63. const config = { childList: true, subtree: true };
  64.  
  65. const observer = new MutationObserver(() => {
  66. entries = document.querySelectorAll(".post-listing, .comment-node");
  67.  
  68. if (entries.length > 0) {
  69. if (location.href !== previousUrl) {
  70. previousUrl = location.href;
  71. currentEntry = null;
  72. }
  73. init();
  74. }
  75. });
  76.  
  77. observer.observe(targetNode, config);
  78.  
  79. function init() {
  80. // If jumping to comments
  81. if (window.location.search.includes("scrollToComments=true") &&
  82. entries.length > 1 &&
  83. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  84. ) {
  85. selectEntry(entries[1], true);
  86. }
  87. // If jumping to comment from anchor link
  88. else if (window.location.pathname.includes("/comment/") &&
  89. (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0)
  90. ) {
  91. const commentId = window.location.pathname.replace("/comment/", "");
  92. const anchoredEntry = document.getElementById("comment-" + commentId);
  93.  
  94. if (anchoredEntry) {
  95. selectEntry(anchoredEntry, true);
  96. }
  97. }
  98. // If no entries yet selected, default to first
  99. else if (!currentEntry || Array.from(entries).indexOf(currentEntry) < 0) {
  100. selectEntry(entries[0]);
  101. if (expand) expandEntry();
  102. }
  103.  
  104. Array.from(entries).forEach(entry => {
  105. entry.removeEventListener("click", clickEntry, true);
  106. entry.addEventListener('click', clickEntry, true);
  107. });
  108.  
  109. document.removeEventListener("keydown", handleKeyPress, true);
  110. document.addEventListener("keydown", handleKeyPress, true);
  111. }
  112.  
  113. function handleKeyPress(event) {
  114. if (["TEXTAREA", "INPUT"].indexOf(event.target.tagName) > -1) {
  115. return;
  116. }
  117.  
  118. // Ignore when modifier keys held
  119. if (event.altKey || event.ctrlKey || event.metaKey) {
  120. return;
  121. }
  122.  
  123. switch (event.code) {
  124. case nextKey:
  125. case prevKey:
  126. // let selectedEntry; //moved to global scope
  127.  
  128. // Next button
  129. if (event.code === nextKey) {
  130. // if shift key also pressed
  131. if (event.shiftKey) {
  132. selectedEntry = getNextEntrySameLevel(currentEntry);
  133. } else {
  134. selectedEntry = getNextEntry(currentEntry);
  135. }
  136. }
  137.  
  138. // Previous button
  139. if (event.code === prevKey) {
  140. // if shift key also pressed
  141. if (event.shiftKey) {
  142. selectedEntry = getPrevEntrySameLevel(currentEntry);
  143. } else {
  144. selectedEntry = getPrevEntry(currentEntry);
  145. }
  146. }
  147.  
  148. if (selectedEntry) {
  149. if (expand) collapseEntry();
  150. selectEntry(selectedEntry, true);
  151. if (expand) expandEntry();
  152. }
  153. break;
  154. case expandKey:
  155. toggleExpand();
  156. expand = isExpanded() ? true : false;
  157. break;
  158. case openCommentsKey:
  159. if (event.shiftKey) {
  160. window.open(
  161. currentEntry.querySelector("a.btn[title$='Comments']").href,
  162. );
  163. } else {
  164. currentEntry.querySelector("a.btn[title$='Comments']").click();
  165. }
  166. break;
  167. case openLinkKey:
  168. const linkElement = currentEntry.querySelector(".col.flex-grow-0.px-0>div>a") || currentEntry.querySelector(".col.flex-grow-1>p>a");
  169. if (linkElement) {
  170. if (event.shiftKey) {
  171. window.open(linkElement.href);
  172. } else {
  173. linkElement.click();
  174. }
  175. }
  176. break;
  177. case nextPageKey:
  178. case prevPageKey:
  179. const pageButtons = Array.from(document.querySelectorAll(".paginator>button"));
  180.  
  181. if (pageButtons) {
  182. const buttonText = event.code === nextPageKey ? "Next" : "Prev";
  183. pageButtons.find(btn => btn.innerHTML === buttonText).click();
  184. }
  185. break;
  186. case upvoteKey:
  187. case downvoteKey:
  188. // upvote button
  189. if (event.code === upvoteKey) {
  190. toggleUpvote();
  191. }
  192. // downvote button
  193. if (event.code === downvoteKey) {
  194. toggleDownvote();
  195. }
  196. // go to next post (remove this if you don't want this behavior)
  197. selectedEntry = getNextEntry(currentEntry);
  198. if (selectedEntry) {
  199. if (expand) collapseEntry();
  200. selectEntry(selectedEntry, true);
  201. if (expand) expandEntry();
  202. }
  203. break;
  204.  
  205. }
  206. }
  207.  
  208. function getNextEntry(e) {
  209. const currentEntryIndex = Array.from(entries).indexOf(e);
  210.  
  211. if (currentEntryIndex + 1 >= entries.length) {
  212. return e;
  213. }
  214.  
  215. return entries[currentEntryIndex + 1];
  216. }
  217.  
  218. function getPrevEntry(e) {
  219. const currentEntryIndex = Array.from(entries).indexOf(e);
  220.  
  221. if (currentEntryIndex - 1 < 0) {
  222. return e;
  223. }
  224.  
  225. return entries[currentEntryIndex - 1];
  226. }
  227.  
  228. function getNextEntrySameLevel(e) {
  229. const nextSibling = e.parentElement.nextElementSibling;
  230.  
  231. if (!nextSibling || nextSibling.getElementsByTagName("article").length < 1) {
  232. return getNextEntry(e);
  233. }
  234.  
  235. return nextSibling.getElementsByTagName("article")[0];
  236. }
  237.  
  238. function getPrevEntrySameLevel(e) {
  239. const prevSibling = e.parentElement.previousElementSibling;
  240.  
  241. if (!prevSibling || prevSibling.getElementsByTagName("article").length < 1) {
  242. return getPrevEntry(e);
  243. }
  244.  
  245. return prevSibling.getElementsByTagName("article")[0];
  246. }
  247.  
  248. function clickEntry(event) {
  249. const e = event.currentTarget;
  250. const target = event.target;
  251.  
  252. // Deselect if already selected, also ignore if clicking on any link/button
  253. if (e === currentEntry && e.classList.contains(selectedClass) &&
  254. !(
  255. target.tagName.toLowerCase() === "button" || target.tagName.toLowerCase() === "a" ||
  256. target.parentElement.tagName.toLowerCase() === "button" ||
  257. target.parentElement.tagName.toLowerCase() === "a" ||
  258. target.parentElement.parentElement.tagName.toLowerCase() === "button" ||
  259. target.parentElement.parentElement.tagName.toLowerCase() === "a"
  260. )
  261. ) {
  262. e.classList.remove(selectedClass);
  263. } else {
  264. selectEntry(e);
  265. }
  266. }
  267.  
  268. function selectEntry(e, scrollIntoView=false) {
  269. if (currentEntry) {
  270. currentEntry.classList.remove(selectedClass);
  271. }
  272. currentEntry = e;
  273. currentEntry.classList.add(selectedClass);
  274.  
  275. if (scrollIntoView) {
  276. scrollIntoViewWithOffset(e, 15)
  277. }
  278. }
  279.  
  280. function isExpanded() {
  281. if (
  282. currentEntry.querySelector("a.d-inline-block:not(.thumbnail)") ||
  283. currentEntry.querySelector("#postContent") ||
  284. currentEntry.querySelector(".card-body")
  285. ) {
  286. return true;
  287. }
  288.  
  289. return false;
  290. }
  291.  
  292. function toggleExpand() {
  293. const expandButton = currentEntry.querySelector("button[aria-label='Expand here']");
  294. const textExpandButton = currentEntry.querySelector(".post-title>button");
  295.  
  296. if (expandButton) {
  297. expandButton.click();
  298.  
  299. // Scroll into view if picture/text preview cut off
  300. const imgContainer = currentEntry.querySelector("a.d-inline-block");
  301. if (imgContainer) {
  302. // Check container positions once image is loaded
  303. imgContainer.querySelector("img").addEventListener("load", function() {
  304. scrollIntoViewWithOffset(currentEntry, 0);
  305. }, true);
  306. }
  307. }
  308.  
  309. if (textExpandButton) {
  310. textExpandButton.click();
  311. }
  312.  
  313. scrollIntoViewWithOffset(currentEntry, 0);
  314. }
  315.  
  316. function expandEntry() {
  317. if (!isExpanded()) toggleExpand();
  318. }
  319.  
  320. function collapseEntry() {
  321. if (isExpanded()) toggleExpand();
  322. }
  323.  
  324. function scrollIntoViewWithOffset(e, offset) {
  325. if (e.getBoundingClientRect().top < 0 ||
  326. e.getBoundingClientRect().bottom > window.innerHeight
  327. ) {
  328. const y = e.getBoundingClientRect().top + window.pageYOffset - offset;
  329. window.scrollTo({
  330. top: y
  331. });
  332. }
  333. }
  334.  
  335. function toggleUpvote() {
  336. const upvoteButton = currentEntry.querySelector("button[aria-label='Upvote']");
  337. const textUpvoteButton = currentEntry.querySelector(".post-title>button");
  338.  
  339. if (upvoteButton) {
  340. upvoteButton.click();
  341.  
  342. // Scroll into view if picture/text preview cut off
  343. const imgContainer = currentEntry.querySelector("a.d-inline-block");
  344. if (imgContainer) {
  345. // Check container positions once image is loaded
  346. imgContainer.querySelector("img").addEventListener("load", function() {
  347. scrollIntoViewWithOffset(currentEntry, 0);
  348. }, true);
  349. }
  350. }
  351.  
  352. if (textUpvoteButton) {
  353. textUpvoteButton.click();
  354. }
  355.  
  356. scrollIntoViewWithOffset(currentEntry, 0);
  357. }
  358.  
  359. function upvoteEntry() {
  360. if (!isUpvoted()) toggleUpvote();
  361. }
  362.  
  363. function toggleDownvote() {
  364. const downvoteButton = currentEntry.querySelector("button[aria-label='Downvote']");
  365. const textDownvoteButton = currentEntry.querySelector(".post-title>button");
  366.  
  367. if (downvoteButton) {
  368. downvoteButton.click();
  369.  
  370. // Scroll into view if picture/text preview cut off
  371. const imgContainer = currentEntry.querySelector("a.d-inline-block");
  372. if (imgContainer) {
  373. // Check container positions once image is loaded
  374. imgContainer.querySelector("img").addEventListener("load", function() {
  375. scrollIntoViewWithOffset(currentEntry, 0);
  376. }, true);
  377. }
  378. }
  379.  
  380. if (textDownvoteButton) {
  381. textDownvoteButton.click();
  382. }
  383.  
  384. scrollIntoViewWithOffset(currentEntry, 0);
  385. }
  386.  
  387. function downvoteEntry() {
  388. if (!isDownvoted()) toggleDownvote();
  389. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement