myhackernews

Apply a dark theme to Hacker News, modify navigation links, and add a custom menu with highlighted active links

  1. // ==UserScript==
  2. // @name myhackernews
  3. // @namespace https://github.com/jeanlucaslima/myhackernews/
  4. // @version 3.1.1
  5. // @description Apply a dark theme to Hacker News, modify navigation links, and add a custom menu with highlighted active links
  6. // @license MIT
  7. // @copyright jeanlucaslima
  8. // @author jeanlucaslima
  9. // @homepageURL https://github.com/jeanlucaslima/myhackernews/
  10. // @supportURL https://github.com/jeanlucaslima/myhackernews/issues
  11. // @match https://news.ycombinator.com/*
  12. // @grant none
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // Theme definitions
  19. const themes = {
  20. darkNavy: {
  21. dark: true,
  22. '--background-color': '#1a202c',
  23. '--table-background-color': '#2d3848',
  24. '--text-color': '#dddddd',
  25. '--link-color': '#9facbe',
  26. '--pagetop-background-color': '#2d3848',
  27. '--pagetop-text-color': '#9facbe',
  28. '--hnname-color': '#bb86fc',
  29. '--title-link-color': '#ededed',
  30. '--title-link-visited-color': '#7fe0d4',
  31. '--subtext-link-color': '#c8d2dc',
  32. '--itemlist-even-bg-color': '#1c1c1c',
  33. '--itemlist-odd-bg-color': '#121212',
  34. '--c00-color': '#c8d2dc',
  35. '--active-link-color': '#ff4500'
  36. },
  37. blackTheme: {
  38. dark: true,
  39. '--background-color': '#1f1f1f',
  40. '--table-background-color': '#1f1f1f',
  41. '--text-color': '#e0e0e0',
  42. '--link-color': '#828282',
  43. '--pagetop-background-color': '#1f1f1f',
  44. '--pagetop-text-color': '#828282',
  45. '--hnname-color': '#bb86fc',
  46. '--title-link-color': '#ededed',
  47. '--title-link-visited-color': '#868686',
  48. '--subtext-link-color': '#03dac6',
  49. '--itemlist-even-bg-color': '#1c1c1c',
  50. '--itemlist-odd-bg-color': '#121212',
  51. '--c00-color': '#ededed',
  52. '--active-link-color': '#ff6600'
  53. }
  54. };
  55.  
  56. // Function to generate CSS rules from theme object
  57. function generateCSS(theme) {
  58. return `
  59. :root {
  60. color-scheme: ${theme.dark ? 'dark' : 'light'};
  61. }
  62. body, tbody {
  63. background-color: ${theme['--background-color']};
  64. color: ${theme['--text-color']};
  65. }
  66. a {
  67. color: ${theme['--link-color']};
  68. }
  69. a:link {
  70. color: ${theme['--link-color']};
  71. }
  72. .pagetop {
  73. background-color: ${theme['--pagetop-background-color']};
  74. padding: 0;
  75. color: ${theme['--pagetop-text-color']};
  76. }
  77. .pagetop a {
  78. color: ${theme['--pagetop-text-color']};
  79. }
  80. .pagetop a:visited {
  81. color: ${theme['--pagetop-text-color']};
  82. }
  83. .hnname a {
  84. color: ${theme['--hnname-color']};
  85. }
  86. td {
  87. background-color: ${theme['--table-background-color']};
  88. }
  89. td.title a {
  90. color: ${theme['--title-link-color']};
  91. }
  92. td.title a:visited {
  93. color: ${theme['--title-link-visited-color']};
  94. }
  95. .subtext a {
  96. color: ${theme['--subtext-link-color']};
  97. }
  98. .itemlist tr:nth-child(even) td {
  99. background-color: ${theme['--itemlist-even-bg-color']};
  100. }
  101. .itemlist tr:nth-child(odd) td {
  102. background-color: ${theme['--itemlist-odd-bg-color']};
  103. }
  104. table {
  105. background-color: ${theme['--table-background-color']} !important;
  106. }
  107. .c00, .c00 a:link { color: ${theme['--c00-color']}; }
  108. .menu a.active {
  109. color: ${theme['--active-link-color']};
  110. font-weight: bold;
  111. }
  112. `;
  113. }
  114.  
  115. // Function to apply the theme
  116. function applyTheme(themeName) {
  117. const theme = themes[themeName];
  118. const style = document.createElement('style');
  119. style.textContent = generateCSS(theme);
  120. document.head.appendChild(style);
  121. }
  122.  
  123. // Function to create and append new link
  124. function createLink(container, text, href) {
  125. const link = document.createElement('a');
  126. link.href = href;
  127. link.textContent = text;
  128.  
  129. // we use link.pathname to not mix with the queries when adding active status
  130. if (link.pathname === window.location.pathname) {
  131. link.classList.add('active');
  132. }
  133. if (container.childNodes.length === 1) {
  134. container.appendChild(document.createTextNode(' '));
  135. }
  136. if (container.childNodes.length > 2) {
  137. container.appendChild(document.createTextNode(' | '));
  138. }
  139. container.appendChild(link);
  140. }
  141.  
  142. // Function to build the menu
  143. function buildMenu(container, userId) {
  144. createLink(container, 'Hacker News', '/');
  145. createLink(container, 'active', '/active');
  146. createLink(container, 'best', '/best');
  147. createLink(container, 'threads', `/threads?id=${userId}`);
  148. createLink(container, 'ask', '/ask');
  149. createLink(container, 'show', '/show');
  150. createLink(container, 'past', '/front');
  151. createLink(container, 'submit', '/submit');
  152. }
  153.  
  154. // Function to modify the navigation links
  155. function modifyNav() {
  156. const pagetop = document.querySelector('.pagetop');
  157. if (pagetop) {
  158. // Find the user id for the threads link before clearing pagetop
  159. const userLink = pagetop.querySelector('a[href^="threads?id="]');
  160. const userId = userLink ? userLink.getAttribute('href').split('=')[1] : '';
  161.  
  162. pagetop.innerHTML = ''; // Clear existing links
  163. pagetop.classList.add('menu');
  164. buildMenu(pagetop, userId);
  165. }
  166. }
  167.  
  168. // Function to add the theme switcher
  169. function addThemeSwitcher() {
  170. const switcherSpan = document.createElement('span');
  171. const bottomContainer = document.querySelector('.yclinks');
  172.  
  173. switcherSpan.className = 'theme_switcher';
  174. switcherSpan.style.display = 'block';
  175. switcherSpan.style.marginTop = '10px';
  176.  
  177. const select = document.createElement('select');
  178. select.innerHTML = `
  179. <option value="darkNavy">Deep Navy</option>
  180. <option value="blackTheme">Black</option>
  181. `;
  182. select.value = localStorage.getItem('hn-theme') || 'darkNavy';
  183. select.addEventListener('change', () => {
  184. const selectedTheme = select.value;
  185. localStorage.setItem('hn-theme', selectedTheme);
  186. applyTheme(selectedTheme);
  187. });
  188.  
  189. switcherSpan.appendChild(document.createTextNode('Theme: '));
  190. switcherSpan.appendChild(select);
  191.  
  192. bottomContainer.appendChild(switcherSpan);
  193. }
  194.  
  195. // Apply the saved theme on load
  196. const savedTheme = localStorage.getItem('hn-theme') || 'darkNavy';
  197. console.log(savedTheme);
  198. applyTheme(savedTheme);
  199.  
  200. // Modify navigation and add theme switcher
  201. modifyNav();
  202. addThemeSwitcher();
  203. })();