nnw-theme 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/footnotes.js +9 -1
- package/dist/render.js +34 -3
- package/package.json +3 -3
package/assets/footnotes.js
CHANGED
|
@@ -31,7 +31,15 @@ async ({notes = null, plain_links = [], keep_with_word = false} = {}) => {
|
|
|
31
31
|
if (document.querySelectorAll(`[id="${CSS.escape(id)}"]`).length > 1) {
|
|
32
32
|
fail(`footnote ${label(marker)}: more than one element has id ${id}`);
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
// newsfoot shows a Substack note from the elements after its backlink
|
|
35
|
+
// <a id="footnote-N">N</a>, and any other note from the target itself.
|
|
36
|
+
let note = text(target);
|
|
37
|
+
if (marker.matches("a[id^='footnote-anchor-']")) {
|
|
38
|
+
let content = "";
|
|
39
|
+
for (let node = target.nextElementSibling; node && !node.matches("a[id^='footnote-']");
|
|
40
|
+
node = node.nextElementSibling) content += node.textContent;
|
|
41
|
+
note = content.replace(/\s+/g, " ").trim();
|
|
42
|
+
}
|
|
35
43
|
if (note) noteFor.set(marker, note);
|
|
36
44
|
else fail(`footnote ${label(marker)}: note is empty`);
|
|
37
45
|
}
|
package/dist/render.js
CHANGED
|
@@ -2,11 +2,11 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { decodeHTML } from "entities";
|
|
5
|
+
import { Parser } from "htmlparser2";
|
|
5
6
|
import { renderingInputs } from "./netnewswire.js";
|
|
6
7
|
import { fixturePath, readFixture, repositoryFixtures, ThemeError, themeStem, } from "./project.js";
|
|
7
8
|
import { formatG, pyStr } from "./pyformat.js";
|
|
8
9
|
const MACRO_RE = /\[\[([a-zA-Z0-9_-]+)\]\]/g;
|
|
9
|
-
const SCRIPT_RE = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
10
10
|
// A whole <img> or <source> tag; quoted attribute values may contain ">".
|
|
11
11
|
const MEDIA_TAG_RE = /<(img|source)\b((?:[^>"']|"[^"]*"|'[^']*')*)>/gi;
|
|
12
12
|
const ATTRIBUTE_RE = /([^\s"'=<>/]+)(?:\s*=\s*("[^"]*"|'[^']*'|[^\s"'=<>`]+))?/g;
|
|
@@ -301,6 +301,37 @@ function injectScripts(page, inputs, platform) {
|
|
|
301
301
|
const end = page.lastIndexOf("</body>");
|
|
302
302
|
return `${page.slice(0, end)}${bundle}\n${page.slice(end)}`;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Remove every <script> element as WebKit's tokenizer finds them, including end
|
|
306
|
+
* tags like "</script >" and a script left open to the end of the document.
|
|
307
|
+
*/
|
|
308
|
+
export function removeScripts(html) {
|
|
309
|
+
const ranges = [];
|
|
310
|
+
let start = -1;
|
|
311
|
+
const parser = new Parser({
|
|
312
|
+
onopentag(name) {
|
|
313
|
+
if (name === "script")
|
|
314
|
+
start = parser.startIndex;
|
|
315
|
+
},
|
|
316
|
+
onclosetag(name, isImplied) {
|
|
317
|
+
if (name !== "script" || start < 0)
|
|
318
|
+
return;
|
|
319
|
+
const end = isImplied
|
|
320
|
+
? html.length
|
|
321
|
+
: html.indexOf(">", parser.endIndex) + 1 || html.length;
|
|
322
|
+
ranges.push([start, end]);
|
|
323
|
+
start = -1;
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
parser.end(html);
|
|
327
|
+
let result = "";
|
|
328
|
+
let offset = 0;
|
|
329
|
+
for (const [from, to] of ranges) {
|
|
330
|
+
result += html.slice(offset, from);
|
|
331
|
+
offset = to;
|
|
332
|
+
}
|
|
333
|
+
return result + html.slice(offset);
|
|
334
|
+
}
|
|
304
335
|
function isFile(path) {
|
|
305
336
|
try {
|
|
306
337
|
return statSync(path).isFile();
|
|
@@ -319,7 +350,7 @@ export function renderPage(theme, fixture, target, snapshot = renderingInputs().
|
|
|
319
350
|
if (!required.every(isFile)) {
|
|
320
351
|
throw new ThemeError("bundled NetNewsWire rendering inputs are incomplete");
|
|
321
352
|
}
|
|
322
|
-
const skeleton = read(pagePath)
|
|
353
|
+
const skeleton = removeScripts(read(pagePath));
|
|
323
354
|
const stylesheet = read(join(theme, "stylesheet.css"));
|
|
324
355
|
// styleSubstitutions() is empty on macOS, so the macro must stay literal there.
|
|
325
356
|
const styleSubstitutions = {};
|
|
@@ -331,7 +362,7 @@ export function renderPage(theme, fixture, target, snapshot = renderingInputs().
|
|
|
331
362
|
const template = read(join(theme, "template.html"));
|
|
332
363
|
let body = substitute(template, fixtureMapping(fixture, target));
|
|
333
364
|
if (!target.themeScripts)
|
|
334
|
-
body = body
|
|
365
|
+
body = removeScripts(body);
|
|
335
366
|
let page = substitute(skeleton, {
|
|
336
367
|
title: fixture.title ?? "Theme preview",
|
|
337
368
|
style,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nnw-theme",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Create, preview, check, and publish a NetNewsWire theme.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Dave Marquard",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"entities": "8.1.0",
|
|
45
45
|
"fflate": "0.8.3",
|
|
46
46
|
"htmlparser2": "12.0.0",
|
|
47
|
-
"playwright-core": "1.64.0
|
|
48
|
-
"smol-toml": "1.
|
|
47
|
+
"playwright-core": "1.64.0",
|
|
48
|
+
"smol-toml": "1.9.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@biomejs/biome": "2.5.14",
|