Skip to content

Instantly share code, notes, and snippets.

@Klaster1
Last active August 18, 2023 06:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Klaster1/052cd314ae2928770a538765d4e89340 to your computer and use it in GitHub Desktop.
Save Klaster1/052cd314ae2928770a538765d4e89340 to your computer and use it in GitHub Desktop.
Hacker News better arrow quotes
// ==UserScript==
// @name HackerNews better arrow quotes
// @version 1.6
// @grant none
// @match https://news.ycombinator.com/*
// @icon https://news.ycombinator.com/favicon.ico
// @downloadURL https://gist.github.com/Klaster1/052cd314ae2928770a538765d4e89340/raw/hackernews-better-arrow-quotes.user.js
// @updateURL https://gist.github.com/Klaster1/052cd314ae2928770a538765d4e89340/raw/hackernews-better-arrow-quotes.user.js
// ==/UserScript==
/**
* @typedef {Array<ChildNode>} Quote
*/
/**
* @typedef {(node: ChildNode) => boolean} NodePredicateFn
*/
/** @type {NodePredicateFn} */
const startsWithArrow = (node) => node?.textContent.startsWith(">");
/** @type {NodePredicateFn} */
const isParagraph = (node) => node?.nodeName === "P";
/** @type {NodePredicateFn} */
const isCode = (node) => node?.nodeName === "PRE";
/** @type {NodePredicateFn} */
const isReply = (node) => node?.nodeName === "DIV";
/**
* @param {Quotes[]} quotes
* @param {ChildNode} node
* @return {Quotes[]}
*/
const addToLastQuote = (quotes = [], node) => {
const [last, ...rest] = [...quotes].reverse();
return [...rest, [...last, node]];
};
/**
* @param {Quotes[]} quotes
* @param {ChildNode} node
* @return {Quotes[]}
*/
const addNewQuote = (quotes = [], node) => {
return [...quotes, [node]];
};
/**
* @returns {Quote[]}
*/
const getQuotes = () =>
Array.from(document.querySelectorAll(".commtext"))
.map((node) =>
Array.from(node.childNodes).reduce(
(/** @type {Quote[]} */ acc, node, index, nodes) => {
const next = nodes.at(index + 1);
const prev = nodes.at(index - 1);
if (isReply(node)) return acc;
// First "paragraph" has no <p> wrapper
if (!(isParagraph(node) || isCode(node))) {
if (startsWithArrow(node)) return addNewQuote(acc, node);
if (acc.at(-1)?.length) return addToLastQuote(acc, node);
} else {
if (startsWithArrow(node)) {
if (acc.at(-1)?.includes(prev)) {
return addToLastQuote(acc, node);
} else {
return addNewQuote(acc, node);
}
}
}
return acc;
},
/** @type {Quote[]} */ []
)
)
.filter((quote) => quote.length)
.flat(1);
/**
* @param {Quote} group
* @returns {void}
*/
const transformQuote = (group) => {
const quote = document.createElement("blockquote");
quote.classList.add("klaster-1__quote");
group.at(0).replaceWith(quote);
group.forEach((node) => {
quote.appendChild(node);
node.textContent = node.textContent.replace(/^\>/, "");
});
};
/**
* @param {string} styleText
* @returns {void}
*/
const injectStyles = (styleText) => {
const style = document.createElement("style");
style.textContent = styleText;
document.querySelector("head")?.appendChild(style);
};
getQuotes().forEach(transformQuote);
injectStyles(`
blockquote.klaster-1__quote {
margin: 0.5;
border-color: gray;
border-width: 1px 1px 1px 3px;
border-style: solid;
border-radius: 5px;
padding: 8px 12px;
margin-top: 0.5em;
}
blockquote.klaster-1__quote > *:first-child {
margin-top: 0;
}
`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment