Repository navigation
Expand file tree
/
Copy pathcontent.js
More file actions
97 lines (83 loc) · 3.19 KB
/
Copy pathcontent.js
File metadata and controls
97 lines (83 loc) · 3.19 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/**
* HN Modern v2.1 — content.js
*
* KEY FIX: Instead of injecting separate <link> tags (which race),
* we fetch all CSS text at startup and inject a single <style> element.
* Swapping themes is instant: just replace the <style> innerHTML.
*/
(function () {
'use strict';
if (window.__hnModernRan) return;
window.__hnModernRan = true;
const STYLE_ID = 'hn-modern-styles';
/* ── Fetch CSS text from extension bundle ─────────────── */
async function fetchCSS(filename) {
const url = chrome.runtime.getURL(filename);
const res = await fetch(url);
return res.text();
}
/* ── Build combined CSS for a theme ─────────────────── */
async function buildCSS(theme) {
if (theme === 'default') return '';
const [base, palette] = await Promise.all([
fetchCSS('styles/base.css'),
fetchCSS(`styles/${theme}.css`)
]);
// Palette first so its :root vars are defined before base rules use them
return palette + '\n' + base;
}
/* ── Inject or update the <style> tag ───────────────── */
async function applyTheme(theme) {
// Set attribute immediately for any selectors that key off it
if (theme === 'default') {
document.documentElement.removeAttribute('data-hn-theme');
} else {
document.documentElement.setAttribute('data-hn-theme', theme);
}
const css = await buildCSS(theme);
let el = document.getElementById(STYLE_ID);
if (!el) {
el = document.createElement('style');
el.id = STYLE_ID;
// Append to <head> if ready, else <html>
(document.head || document.documentElement).appendChild(el);
}
el.textContent = css;
}
/* ── Boot: read saved theme and apply ────────────────── */
chrome.storage.sync.get('theme', ({ theme }) => {
applyTheme(theme || 'dark');
});
/* ── Live switching: listen for popup messages ───────── */
chrome.runtime.onMessage.addListener((msg) => {
if (msg.type === 'HN_SET_THEME') {
applyTheme(msg.theme);
}
});
/* ── "points" colorizer (runs after DOM ready) ───────── */
function colorPoints(node) {
if (node.nodeType === Node.TEXT_NODE) {
if (!node.textContent.includes('points')) return;
if (node.parentNode && node.parentNode.dataset && node.parentNode.dataset.hnColored) return;
const span = document.createElement('span');
span.dataset.hnColored = '1';
span.innerHTML = node.textContent.replace(
/\b(points)\b/g,
'<span style="color:var(--text-tertiary,#8b949e);font-family:var(--font-sans,sans-serif)">$1</span>'
);
node.parentNode.replaceChild(span, node);
} else if (
node.nodeType === Node.ELEMENT_NODE &&
node.tagName !== 'SCRIPT' &&
node.tagName !== 'STYLE' &&
!node.dataset.hnColored
) {
Array.from(node.childNodes).forEach(colorPoints);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => colorPoints(document.body), { once: true });
} else {
colorPoints(document.body);
}
})();