Hacker News Tweaks (Modern)

Improve Hacker News readability with larger fonts, modern styling.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Hacker News Tweaks (Modern)
// @namespace    HackerNewsReadabilityTweaks
// @homepage     https://github.com/Meekelis/Hacker-News-ReadabilityTweaks
// @version      1.0
// @description  Improve Hacker News readability with larger fonts, modern styling.
// @match        *://news.ycombinator.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=ycombinator.com
// @license      MIT
// @grant        none
// ==/UserScript==
(function() {
  'use strict';
  const css = `
    .comment-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 0.5em 0;
      border-bottom: 1px solid #e1e1e1;
      background: #f9f9f9;
    }
    .comment-header__details {
      display: flex;
      gap: 0.5em;
      align-items: center;
    }
    .comment-header__username {
      font-weight: bold;
      color: #333;
      text-decoration: none;
    }
    .comment-header__timestamp a {
      color: #888;
      text-decoration: none;
    }
    .comment-header__actions {
      display: flex;
      gap: 0.5em;
      align-items: center;
    }
    .comment-header__link {
      font-size: 0.9em;
      color: #0077cc;
      text-decoration: none;
    }
    .comment-header__toggle {
      display: inline-block;
      width: 2em;
      text-align: center;
      background: none;
      border: none;
      color: #0077cc;
      cursor: pointer;
      font-size: 0.9em;
      line-height: 1;
    }
  `;
  const styleEl = document.createElement('style');
  styleEl.textContent = css;
  document.head.appendChild(styleEl);
  document.querySelectorAll('.comhead').forEach(comhead => {
    const origToggle = comhead.querySelector('a.togg');
    if (origToggle) { origToggle.style.display = 'none'; }
    const userEl = comhead.querySelector('a.hnuser');
    if (!userEl) return;
    const username = userEl.textContent.trim();
    const userHref = userEl.href;
    const ageEl = comhead.querySelector('.age');
    const ageLink = ageEl ? ageEl.querySelector('a') : null;
    const timestamp = ageLink ? ageLink.textContent.replace(/^on\s+/i, '').trim() : '';
    const timeHref = ageLink ? ageLink.href : '#';
    const datetime = ageEl ? ageEl.getAttribute('title') : '';
    let nextHref = '#';
    const navs = comhead.querySelector('.navs');
    if (navs) {
      const nextLink = Array.from(navs.querySelectorAll('a')).find(a => /next/i.test(a.textContent));
      if (nextLink) { nextHref = nextLink.href; }
    }
    const toggleId = origToggle ? origToggle.id : 'toggle';
    const newHeader = document.createElement('div');
    newHeader.className = 'comment-header';
    newHeader.innerHTML = `
      <div class="comment-header__details">
        <a href="${userHref}" class="comment-header__username" target="_blank" rel="noopener noreferrer">
          ${username}
        </a>
        <time datetime="${datetime}" class="comment-header__timestamp">
          <a href="${timeHref}" rel="noopener noreferrer">
            ${timestamp}
          </a>
        </time>
      </div>
      <div class="comment-header__actions">
        <a href="${nextHref}" class="comment-header__link" rel="noopener noreferrer">
          Next
        </a>
        <button class="comment-header__toggle" id="new-toggle-${toggleId}" aria-expanded="true">–</button>
      </div>
    `;
    comhead.innerHTML = '';
    comhead.appendChild(newHeader);
    const defaultCell = comhead.closest('td.default');
    if (!defaultCell) return;
    const commentText = defaultCell.querySelector('.comment');
    if (!commentText) return;
    const collapseBtn = newHeader.querySelector('.comment-header__toggle');
    collapseBtn.addEventListener('click', function(event) {
      event.preventDefault();
      const button = event.target;
      const commentRow = button.closest('.athing.comtr');
      if (!commentRow) return;
      const isCollapsed = button.textContent === '+';
      let nextSibling = commentRow.nextElementSibling;
      while (nextSibling && nextSibling.classList.contains('athing') && nextSibling.classList.contains('comtr')) {
        const indentOfSibling = parseInt(nextSibling.querySelector('.ind').getAttribute('indent') || '0', 10);
        const indentOfCurrent = parseInt(commentRow.querySelector('.ind').getAttribute('indent') || '0', 10);
        if (indentOfSibling > indentOfCurrent) {
          nextSibling.style.display = isCollapsed ? '' : 'none';
        } else { break; }
        nextSibling = nextSibling.nextElementSibling;
      }
      button.textContent = isCollapsed ? '–' : '+';
      button.setAttribute('aria-expanded', (!isCollapsed).toString());
    });
  });
})();
(function(){
  'use strict';
  const style1 = `
  <style>
    :root {
      --accent: #ff6600;
      --accent-light: rgba(255,102,0,0.1);
      --spacing: 0.5rem;
      --radius: 8px;
      --grey: #757575;
      --bg: #f9f9f9;
      --font-main: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif;
    }
    html, body, td, .title, .comment, .default { font-family: var(--font-main); }
    html, body { margin: 0; padding: 0; }
    body, td, .title, .pagetop, .comment { font-size: 1rem; line-height: 1.5; }
    .votelinks, html[op='news'] .title { vertical-align: inherit; }
    .comment-tree .votelinks,
    html[op='threads'] .votelinks,
    html[op='newcomments'] .votelinks { vertical-align: top; }
    span.titleline { font-size: 1rem; line-height: 1.2; margin: var(--spacing) 0; display: block; font-weight: bold; }
    html[op='item'] span.titleline { font-size: 1.0rem; }
    html[op='news'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='newest'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='ask'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='newcomments'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='shownew'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='submitted'] #hnmain>tbody>tr:nth-child(3)>td>table,
    html[op='favorites'] #hnmain>tbody>tr:nth-child(3)>td>table:nth-child(2),
    html[op='front'] #hnmain>tbody>tr:nth-child(3)>td>table:nth-child(2),
    html[op='show'] #hnmain>tbody>tr:nth-child(3)>td>table:nth-child(2) { margin-left: var(--spacing); }
    .sitebit.comhead { margin-left: var(--spacing); }
    .subtext, .subline { font-size: 0.75rem; }
    #hnmain { background: var(--bg); }
    #hnmain>tbody>tr:first-child>td { padding: var(--spacing); }
    #hnmain>tbody>tr:first-child>td>table>tbody>tr:first-child>td { padding-right: var(--spacing)!important; }
    .hnname { font-size: 1.4em; font-weight: bold; }
    .comment, .toptext { max-width: 40em; margin: auto; }
    .toptext, a, a:visited { color: #333; text-decoration: none; transition: color 0.2s; }
    input { padding: var(--spacing); }
    input, textarea { background: white; border: 1px solid var(--grey); padding: 6px; border-radius: var(--radius); transition: box-shadow 0.2s; }
    input:focus, textarea:focus { box-shadow: 0 0 5px var(--accent); }
    input[type="text"]:hover { text-decoration: none; }
    input[type='button'] { cursor: pointer; }
    .downvoted .commtext { color: var(--grey); }
    .quote { margin: 16px; border: 1px solid var(--accent-light); border-left: 6px solid var(--accent); padding: 16px; color: var(--grey); background: var(--accent-light); border-radius: var(--radius); font-size: 0.9em; }
    .hidden { display: none; }
    .showComment a, .hideComment, .hideComment:link, .hideComment:visited { color: var(--accent); text-decoration: underline; }
    .hideComment { margin-left: var(--spacing); }
    .votelinks { min-width: 32px; }
    .votearrow { background: var(--accent-light); border-radius: var(--radius); color: var(--accent); display: block; width: 24px; height: 24px; font-size: 16px; position: relative; top: 2px; transition: background 0.1s, color 0.2s; }
    .votearrow:hover { background: var(--accent); color: white; }
    .votearrow:after { content: "⇧"; }
    body:has(form[action="login"]) { padding: 32px; }
  </style>`;
  document.head.insertAdjacentHTML("beforeend", style1);
  const style2 = `
  <style>
    .modern-downvoted {
      background: linear-gradient(135deg, #eef6ff, #f7fbff);
      padding: 12px 16px;
      border-left: 4px solid #007BFF;
      border-radius: 4px;
      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      font-size: 16px;
      color: #333;
    }
    .modern-downvoted a {
      color: #007BFF;
      text-decoration: none;
      font-weight: 500;
    }
    .modern-downvoted a:hover {
      text-decoration: underline;
    }
    pre.modern-code {
      position: relative;
      background: #f7f7f7;
      color: #333;
      padding: 16px;
      border: 1px solid #ddd;
      border-radius: 4px;
      overflow: auto;
      font-family: 'Fira Code', Consolas, "Courier New", monospace;
      font-size: 15px;
      line-height: 1.6;
      margin-bottom: 1em;
    }
    pre.modern-code code {
      font-family: 'Fira Code', Consolas, "Courier New", monospace;
      color: inherit;
    }
    .copy-button {
      position: absolute;
      top: 8px;
      right: 8px;
      background: transparent;
      border: none;
      cursor: pointer;
      padding: 4px;
      transition: color 0.2s;
      color: #666;
    }
    .copy-button:hover {
      color: #000;
    }
    .copy-button.svg-icon {
      width: 20px;
      height: 20px;
      display: block;
    }
  </style>`;
  document.head.insertAdjacentHTML("beforeend", style2);
  document.querySelectorAll('.commtext a').forEach(link => {
    link.setAttribute('target', '_blank');
    link.setAttribute('rel', 'noopener noreferrer');
  });
  document.querySelectorAll('.commtext:not(.c00)').forEach(e => {
    e.parentElement.classList.add('downvoted');
  });
  const quotes = document.evaluate("//p[starts-with(., '>')] | //span[starts-with(., '>')]", document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  for (let i = 0; i < quotes.snapshotLength; i++){
    let n = quotes.snapshotItem(i),
        t = Array.from(n.childNodes).find(x => x.nodeType === Node.TEXT_NODE);
    if (t){
      let p = document.createElement('p');
      p.className = 'quote';
      p.innerText = t.data.replace(">", "").trim();
      n.replaceChild(p, n.firstChild);
    } else {
      n.classList.add('quote');
      n.innerText = n.innerText.replace(">", "").trim();
    }
  }
  const copyIcon = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path fill-rule="evenodd" clip-rule="evenodd" d="M7 5C7 3.34315 8.34315 2 10 2H19C20.6569 2 22 3.34315 22 5V14C22 15.6569 20.6569 17 19 17H17V19C17 20.6569 15.6569 22 14 22H5C3.34315 22 2 20.6569 2 19V10C2 8.34315 3.34315 7 5 7H7V5ZM9 7H14C15.6569 7 17 8.34315 17 10V15H19C19.5523 15 20 14.5523 20 14V5C20 4.44772 19.5523 4 19 4H10C9.44772 4 9 4.44772 9 5V7ZM5 9C4.44772 9 4 9.44772 4 10V19C4 19.5523 4.44772 20 5 20H14C14.5523 20 15 19.5523 15 19V10C15 9.44772 14.5523 9 14 9H5Z" fill="currentColor"></path></svg>`;
  const checkIcon = `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="svg-icon"><path d="M20.285 5.715a1 1 0 00-1.414 0L9 15.586l-3.871-3.87a1 1 0 00-1.414 1.414l4.578 4.578a1 1 0 001.414 0l10.578-10.578a1 1 0 000-1.414z" fill="currentColor"/></svg>`;
  document.querySelectorAll('pre').forEach(block => {
    block.classList.add('modern-code');
    const btn = document.createElement('button');
    btn.className = 'copy-button svg-icon';
    btn.innerHTML = copyIcon;
    btn.addEventListener('click', () => {
      navigator.clipboard.writeText(block.innerText).then(() => {
        btn.classList.add('copied');
        btn.innerHTML = checkIcon;
        setTimeout(() => {
          btn.classList.remove('copied');
          btn.innerHTML = copyIcon;
        }, 2000);
      });
    });
    block.style.position = 'relative';
    block.appendChild(btn);
  });
  document.querySelectorAll('.comment.downvoted .commtext').forEach(e => {
    if (!e.querySelector('.modern-downvoted')) {
      const wrapper = document.createElement('div');
      wrapper.className = 'modern-downvoted';
      wrapper.innerHTML = e.innerHTML;
      e.innerHTML = '';
      e.appendChild(wrapper);
    }
  });
})();