nnw-theme 1.1.0 → 2.0.1
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/README.md +21 -11
- package/assets/fixtures/kitchen-sink.toml +1 -0
- package/assets/guide/fixtures.md +1 -1
- package/assets/guide/publishing.md +3 -3
- package/assets/guide/skill.md +8 -8
- package/assets/guide/theme-format.md +2 -1
- package/assets/stubs/.agents/skills/creating-nnw-themes/SKILL.md +6 -6
- package/assets/stubs/.github/workflows/check.yml +2 -2
- package/assets/stubs/.github/workflows/pages.yml +2 -2
- package/assets/stubs/.github/workflows/release.yml +2 -2
- package/assets/stubs/.github/workflows/screenshot.yml +2 -2
- package/assets/stubs/AGENTS.md +6 -6
- package/dist/browser.js +23 -1
- package/dist/commands/capture.js +1 -1
- package/dist/commands/init.js +5 -5
- package/dist/commands/marketplace.js +2 -2
- package/dist/commands/update.js +1 -1
- package/dist/commands.js +1 -1
- package/dist/completion.js +5 -5
- package/dist/main.js +1 -1
- package/dist/render.js +2 -2
- package/dist/stubs.js +6 -6
- package/dist/validate.js +19 -1
- package/lldb/nnwdump.py +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,15 +16,15 @@ Not affiliated with or endorsed by Ranchero Software or the NetNewsWire project.
|
|
|
16
16
|
Run it with `npx`, from anywhere inside the theme repository. It needs Node 24 or newer.
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
npx nnw-theme@
|
|
20
|
-
npx nnw-theme@
|
|
21
|
-
npx nnw-theme@
|
|
22
|
-
npx nnw-theme@
|
|
19
|
+
npx nnw-theme@2 init # once: name the theme and choose its permanent identifier
|
|
20
|
+
npx nnw-theme@2 setup # once per machine: install the WebKit build checks use
|
|
21
|
+
npx nnw-theme@2 preview # live gallery that reloads on save
|
|
22
|
+
npx nnw-theme@2 check # the release gate: package, render, and test every case
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
`@
|
|
25
|
+
`@2` picks the newest 2.x release, so fixes arrive on their own and a breaking 3.0 never
|
|
26
26
|
does. Don't install it globally or add it to a theme repository. To use one exact
|
|
27
|
-
version, name it: `npx nnw-theme@
|
|
27
|
+
version, name it: `npx nnw-theme@2.0.0 check`.
|
|
28
28
|
|
|
29
29
|
| Command | What it does |
|
|
30
30
|
| --- | --- |
|
|
@@ -46,16 +46,16 @@ Every command takes `--help`.
|
|
|
46
46
|
## Shell completion
|
|
47
47
|
|
|
48
48
|
`completion` prints a script that completes commands, options, and fixture names. It
|
|
49
|
-
also defines an `nnw-theme` command that runs `npx --yes nnw-theme@
|
|
49
|
+
also defines an `nnw-theme` command that runs `npx --yes nnw-theme@2`, so you can type
|
|
50
50
|
`nnw-theme check`. The wrapper is skipped when a real `nnw-theme` is already installed.
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
53
|
# fish
|
|
54
|
-
npx nnw-theme@
|
|
54
|
+
npx nnw-theme@2 completion fish > ~/.config/fish/conf.d/nnw-theme.fish
|
|
55
55
|
# zsh (~/.zshrc, after compinit)
|
|
56
|
-
source <(npx --yes nnw-theme@
|
|
56
|
+
source <(npx --yes nnw-theme@2 completion zsh)
|
|
57
57
|
# bash (~/.bashrc)
|
|
58
|
-
source <(npx --yes nnw-theme@
|
|
58
|
+
source <(npx --yes nnw-theme@2 completion bash)
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
Fish loads a file rather than running npx at every shell start. Zsh and bash users can
|
|
@@ -64,11 +64,21 @@ Regenerate it when commands change.
|
|
|
64
64
|
|
|
65
65
|
## GitHub workflows
|
|
66
66
|
|
|
67
|
-
Theme repositories call this repository's reusable workflows, pinned to `@
|
|
67
|
+
Theme repositories call this repository's reusable workflows, pinned to `@v2`:
|
|
68
68
|
`theme-check.yml` (the release gate on every push), `theme-pages.yml` (publishes the
|
|
69
69
|
checked gallery), `theme-release.yml` (manual release), and `theme-screenshot.yml`
|
|
70
70
|
(refreshes the marketplace image). The template carries the ten-line callers.
|
|
71
71
|
|
|
72
|
+
## Upgrading from 1.x
|
|
73
|
+
|
|
74
|
+
2.0 fails `check` when `template.html` writes a macro in double brackets inside an HTML
|
|
75
|
+
comment. NetNewsWire substitutes it there too, so an article containing `-->` ends the
|
|
76
|
+
comment early and the rest shows as text; the 1.x starter template did this. Write the
|
|
77
|
+
names without brackets. Then replace the stub files `check` warns about (`AGENTS.md`,
|
|
78
|
+
the agent skill, and the four workflow callers) with the template's current copies,
|
|
79
|
+
which run `npx nnw-theme@2` and call the workflows at `@v2`. 1.x and `@v1` keep working
|
|
80
|
+
unchanged.
|
|
81
|
+
|
|
72
82
|
## Developing
|
|
73
83
|
|
|
74
84
|
See [AGENTS.md](AGENTS.md). Report problems at
|
|
@@ -31,6 +31,7 @@ body = """
|
|
|
31
31
|
<figure><img alt="Abstract green and cream test pattern" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='675' viewBox='0 0 1200 675'%3E%3Crect width='1200' height='675' fill='%23d8d2c6'/%3E%3Ccircle cx='380' cy='337' r='240' fill='%232f6655'/%3E%3Cpath d='M0 560L1200 180V675H0Z' fill='%23f7f5f0'/%3E%3C/svg%3E"><figcaption>A synthetic image checks wide-media scaling.</figcaption></figure>
|
|
32
32
|
<p>The note marker uses common generated markup.<sup id="fnref:1"><a href="#fn:1" rel="footnote">1</a></sup></p>
|
|
33
33
|
<div class="footnotes"><ol><li id="fn:1"><p>Footnote content should remain readable in place and in NetNewsWire’s popover. <a href="#fnref:1" class="reversefootnote">↩</a></p></li></ol></div>
|
|
34
|
+
<!-- SC_OFF --><div class="md"><p>A comment-wrapped paragraph, as Reddit sends every post.</p></div><!-- SC_ON -->
|
|
34
35
|
<h3>Final heading</h3>
|
|
35
36
|
<p>End of the fixture.</p>
|
|
36
37
|
"""
|
package/assets/guide/fixtures.md
CHANGED
|
@@ -10,7 +10,7 @@ checked on macOS and iPhone in light and dark. Names used by check scenarios
|
|
|
10
10
|
(`large-text`, `article-javascript-off`) are reserved. Use `'''literal'''` strings for
|
|
11
11
|
HTML bodies so they need no escaping.
|
|
12
12
|
|
|
13
|
-
Prefer real articles for content a theme must handle. `npx nnw-theme@
|
|
13
|
+
Prefer real articles for content a theme must handle. `npx nnw-theme@2 capture`
|
|
14
14
|
prints the steps for saving the selected article from a NetNewsWire debug build with
|
|
15
15
|
the `nnwdump` lldb command; it needs Xcode and a NetNewsWire clone, so the person
|
|
16
16
|
usually runs it. Rendering swaps every image that would load over the network for a
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Read this before versioning, promoting the marketplace screenshot, changing GitHub
|
|
4
4
|
metadata, or releasing.
|
|
5
5
|
|
|
6
|
-
Run `npx nnw-theme@
|
|
7
|
-
`npx nnw-theme@
|
|
6
|
+
Run `npx nnw-theme@2 bump`, commit the new integer `Version`, and run
|
|
7
|
+
`npx nnw-theme@2 check`. The bundle filename and `ThemeIdentifier` cannot change
|
|
8
8
|
after the first release. Release tags may use any valid Git ref beginning with `v`;
|
|
9
9
|
semantic tags such as `v1.0.0` are the suggested default and are independent of the
|
|
10
10
|
plist version.
|
|
@@ -21,7 +21,7 @@ Marketplace participation is optional. Automatic discovery expects a public,
|
|
|
21
21
|
non-archived, non-fork repository; the `netnewswire-theme` or `netnewswire` topic; a
|
|
22
22
|
stable GitHub release; and the valid ZIP asset. A useful repository description and
|
|
23
23
|
`screenshots/theme-preview.png` improve the card. Missing optional marketplace/card
|
|
24
|
-
metadata warns but does not block publication. `npx nnw-theme@
|
|
24
|
+
metadata warns but does not block publication. `npx nnw-theme@2 marketplace enable`
|
|
25
25
|
adds the recommended topic through the author’s authenticated `gh` session.
|
|
26
26
|
|
|
27
27
|
The screenshot workflow pushes a branch and opens a pull request for review, so the
|
package/assets/guide/skill.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Create NetNewsWire themes
|
|
2
2
|
|
|
3
3
|
This guide is for people and agents building a theme with `nnw-theme`. Run the tool
|
|
4
|
-
as `npx nnw-theme@
|
|
5
|
-
repository. Read another topic with `npx nnw-theme@
|
|
4
|
+
as `npx nnw-theme@2 <command>`; never install it globally or add it to the theme
|
|
5
|
+
repository. Read another topic with `npx nnw-theme@2 guide <topic>`: `theme-format`,
|
|
6
6
|
`fixtures`, `design-checklist`, or `publishing`.
|
|
7
7
|
|
|
8
8
|
The upstream `dave-atx/netnewswire-theme-template` repository is a GitHub template.
|
|
@@ -11,7 +11,7 @@ upstream template or a fork. If they already have their own copy open, use it.
|
|
|
11
11
|
Otherwise, help them create one from the template if you can, or ask them to create
|
|
12
12
|
and open it; confirm before creating a GitHub repository. Then work on its single
|
|
13
13
|
root-level `*.nnwtheme` bundle. If `.nnw-theme-uninitialized` exists, run the guided
|
|
14
|
-
`npx nnw-theme@
|
|
14
|
+
`npx nnw-theme@2 init` before design work; let the person approve the permanent
|
|
15
15
|
identifier even when other identity details are already known.
|
|
16
16
|
|
|
17
17
|
Collect or infer a compact design brief: mood, typography, color direction, and any
|
|
@@ -24,18 +24,18 @@ footnotes, add a fixture as the `fixtures` topic describes.
|
|
|
24
24
|
Iterate with this loop:
|
|
25
25
|
|
|
26
26
|
1. Make a focused theme edit.
|
|
27
|
-
2. Run `npx nnw-theme@
|
|
28
|
-
`npx nnw-theme@
|
|
27
|
+
2. Run `npx nnw-theme@2 render` for fast generated pages or
|
|
28
|
+
`npx nnw-theme@2 screenshot` when a checked image is needed.
|
|
29
29
|
3. Inspect relevant light/dark and device previews under `build/preview/`.
|
|
30
30
|
4. Explain what changed in visual terms and incorporate the person’s reaction.
|
|
31
31
|
|
|
32
32
|
Use the `design-checklist` topic during final visual review. Always finish
|
|
33
|
-
implementation with `npx nnw-theme@
|
|
33
|
+
implementation with `npx nnw-theme@2 check`; do not substitute a Chromium result for
|
|
34
34
|
the required WebKit gate. Give the person a concrete preview or screenshot path they
|
|
35
|
-
can open. If `check` reports that WebKit is missing, run `npx nnw-theme@
|
|
35
|
+
can open. If `check` reports that WebKit is missing, run `npx nnw-theme@2 setup`.
|
|
36
36
|
|
|
37
37
|
Treat the root bundle filename and `ThemeIdentifier` as permanent after the first
|
|
38
|
-
release. Increase the integer `Version` with `npx nnw-theme@
|
|
38
|
+
release. Increase the integer `Version` with `npx nnw-theme@2 bump` for each release.
|
|
39
39
|
Read the `publishing` topic for publishing work. Local packaging is not publication.
|
|
40
40
|
Obtain clear user confirmation immediately before triggering a GitHub release or
|
|
41
41
|
another external mutation.
|
|
@@ -34,7 +34,8 @@ the size classes are inert on iOS, where the app never sets one. Do not reach fo
|
|
|
34
34
|
uses it: no desktop WebKit implements that property, so every preview resolves the iOS
|
|
35
35
|
branch as false and platform-conditional CSS is checked the wrong way round. Do not
|
|
36
36
|
write macro names in double brackets inside comments either, because substitution
|
|
37
|
-
rewrites those too
|
|
37
|
+
rewrites those too: a body containing `-->`, as every Reddit post does, closes the
|
|
38
|
+
comment early and shows the rest of it as text. `check` fails on them. NetNewsWire's stylesheet also sets `font: -apple-system-body` at
|
|
38
39
|
`:root`; a theme with its own typography should not, because that shorthand resets
|
|
39
40
|
`font-family` and `line-height`.
|
|
40
41
|
|
|
@@ -2,21 +2,21 @@
|
|
|
2
2
|
name: creating-nnw-themes
|
|
3
3
|
description: Create, restyle, preview, test, package, or publish the NetNewsWire theme in this repository. Do not use for generic web design or NetNewsWire app development.
|
|
4
4
|
---
|
|
5
|
-
<!-- nnw-theme-stub: skill
|
|
5
|
+
<!-- nnw-theme-stub: skill v2 -->
|
|
6
6
|
|
|
7
7
|
# Create NetNewsWire themes
|
|
8
8
|
|
|
9
|
-
Read the full guide first: `npx nnw-theme@
|
|
10
|
-
(`npx nnw-theme@
|
|
9
|
+
Read the full guide first: `npx nnw-theme@2 guide`. Details live in its topics
|
|
10
|
+
(`npx nnw-theme@2 guide theme-format`, `fixtures`, `design-checklist`, `publishing`),
|
|
11
11
|
which update with the tool.
|
|
12
12
|
|
|
13
|
-
1. If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@
|
|
13
|
+
1. If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@2 init` and let the person
|
|
14
14
|
approve the permanent theme identifier.
|
|
15
15
|
2. Collect a compact design brief: mood, typography, color, reading needs.
|
|
16
16
|
3. Edit `stylesheet.css` first; change `template.html` only when structure requires it.
|
|
17
|
-
4. Run `npx nnw-theme@
|
|
17
|
+
4. Run `npx nnw-theme@2 render` and inspect `build/preview/` in light and dark on each
|
|
18
18
|
device; explain the change and take the person's reaction.
|
|
19
|
-
5. Finish with `npx nnw-theme@
|
|
19
|
+
5. Finish with `npx nnw-theme@2 check` and give the person the preview path.
|
|
20
20
|
|
|
21
21
|
Never change the bundle name or `ThemeIdentifier` after the first release. Confirm
|
|
22
22
|
with the person immediately before any release or other GitHub mutation.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# nnw-theme-stub: check
|
|
1
|
+
# nnw-theme-stub: check v2
|
|
2
2
|
# Runs the nnw-theme release gate: https://github.com/dave-atx/nnw-theme
|
|
3
3
|
name: Theme checks
|
|
4
4
|
|
|
@@ -11,4 +11,4 @@ permissions:
|
|
|
11
11
|
|
|
12
12
|
jobs:
|
|
13
13
|
check:
|
|
14
|
-
uses: dave-atx/nnw-theme/.github/workflows/theme-check.yml@
|
|
14
|
+
uses: dave-atx/nnw-theme/.github/workflows/theme-check.yml@v2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# nnw-theme-stub: pages
|
|
1
|
+
# nnw-theme-stub: pages v2
|
|
2
2
|
# Publishes the checked preview gallery when Pages builds from GitHub Actions.
|
|
3
3
|
name: Publish preview pages
|
|
4
4
|
|
|
@@ -21,4 +21,4 @@ concurrency:
|
|
|
21
21
|
|
|
22
22
|
jobs:
|
|
23
23
|
pages:
|
|
24
|
-
uses: dave-atx/nnw-theme/.github/workflows/theme-pages.yml@
|
|
24
|
+
uses: dave-atx/nnw-theme/.github/workflows/theme-pages.yml@v2
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# nnw-theme-stub: release
|
|
1
|
+
# nnw-theme-stub: release v2
|
|
2
2
|
name: Publish theme
|
|
3
3
|
|
|
4
4
|
on:
|
|
@@ -18,7 +18,7 @@ permissions:
|
|
|
18
18
|
|
|
19
19
|
jobs:
|
|
20
20
|
release:
|
|
21
|
-
uses: dave-atx/nnw-theme/.github/workflows/theme-release.yml@
|
|
21
|
+
uses: dave-atx/nnw-theme/.github/workflows/theme-release.yml@v2
|
|
22
22
|
with:
|
|
23
23
|
tag: ${{ inputs.tag }}
|
|
24
24
|
notes: ${{ inputs.notes }}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# nnw-theme-stub: screenshot
|
|
1
|
+
# nnw-theme-stub: screenshot v2
|
|
2
2
|
name: Refresh marketplace screenshot
|
|
3
3
|
|
|
4
4
|
on:
|
|
@@ -21,7 +21,7 @@ permissions:
|
|
|
21
21
|
|
|
22
22
|
jobs:
|
|
23
23
|
screenshot:
|
|
24
|
-
uses: dave-atx/nnw-theme/.github/workflows/theme-screenshot.yml@
|
|
24
|
+
uses: dave-atx/nnw-theme/.github/workflows/theme-screenshot.yml@v2
|
|
25
25
|
with:
|
|
26
26
|
fixture: ${{ inputs.fixture }}
|
|
27
27
|
appearance: ${{ inputs.appearance }}
|
package/assets/stubs/AGENTS.md
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
<!-- nnw-theme-stub: agents
|
|
1
|
+
<!-- nnw-theme-stub: agents v2 -->
|
|
2
2
|
# Repository guidance
|
|
3
3
|
|
|
4
4
|
This repository is one NetNewsWire theme: the single root `*.nnwtheme` bundle,
|
|
5
5
|
its `fixtures/`, and `screenshots/`. The tooling is the `nnw-theme` npm package;
|
|
6
|
-
run it as `npx nnw-theme@
|
|
7
|
-
it to this repository. Read `npx nnw-theme@
|
|
6
|
+
run it as `npx nnw-theme@2 <command>`. Never install it globally and never add
|
|
7
|
+
it to this repository. Read `npx nnw-theme@2 guide` before theme work.
|
|
8
8
|
|
|
9
9
|
If this repository is `dave-atx/netnewswire-theme-template` itself (check
|
|
10
10
|
`git remote -v`), it is the GitHub template, not a theme: do not initialize,
|
|
@@ -13,13 +13,13 @@ fork, or design in it. Help the user create their own repository from it with
|
|
|
13
13
|
dave-atx/netnewswire-theme-template --clone`, confirming before you create it,
|
|
14
14
|
then work in that copy.
|
|
15
15
|
|
|
16
|
-
If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@
|
|
16
|
+
If `.nnw-theme-uninitialized` exists, run `npx nnw-theme@2 init` first and let
|
|
17
17
|
the user confirm the permanent theme identifier. Prefer CSS changes; change
|
|
18
18
|
`template.html` only when the requested structure or behavior requires it.
|
|
19
|
-
Finish theme work with `npx nnw-theme@
|
|
19
|
+
Finish theme work with `npx nnw-theme@2 check` and report the preview path.
|
|
20
20
|
|
|
21
21
|
Do not change `ThemeIdentifier` or the `.nnwtheme` bundle name after the first
|
|
22
|
-
release. Increase the plist `Version` (`npx nnw-theme@
|
|
22
|
+
release. Increase the plist `Version` (`npx nnw-theme@2 bump`) before every
|
|
23
23
|
release.
|
|
24
24
|
|
|
25
25
|
If the tooling misbehaves, do not work around it here: report it at
|
package/dist/browser.js
CHANGED
|
@@ -11,7 +11,7 @@ import { packagePath, ThemeError } from "./project.js";
|
|
|
11
11
|
import { pyReprList } from "./pyformat.js";
|
|
12
12
|
const FOOTNOTE_CHECK = readFileSync(packagePath("assets", "footnotes.js"), "utf8");
|
|
13
13
|
const LIVE_RELOAD = readFileSync(packagePath("assets", "live-reload.js"), "utf8");
|
|
14
|
-
const NOT_INSTALLED = "WebKit is not installed; run `npx nnw-theme@
|
|
14
|
+
const NOT_INSTALLED = "WebKit is not installed; run `npx nnw-theme@2 setup`";
|
|
15
15
|
const CONTENT_TYPES = {
|
|
16
16
|
".html": "text/html; charset=utf-8",
|
|
17
17
|
".png": "image/png",
|
|
@@ -154,6 +154,8 @@ export function failures(state) {
|
|
|
154
154
|
found.push(`broken images: ${pyReprList(state.brokenImages)}`);
|
|
155
155
|
if (state.collapsedImages.length)
|
|
156
156
|
found.push(`images drawn at zero size: ${pyReprList(state.collapsedImages)}`);
|
|
157
|
+
if (state.unloadedImages.length)
|
|
158
|
+
found.push(`images still loading: ${pyReprList(state.unloadedImages)}`);
|
|
157
159
|
found.push(...(state.footnotes ?? []));
|
|
158
160
|
if (state.blocked.length)
|
|
159
161
|
found.push(`external requests: ${pyReprList(state.blocked)}`);
|
|
@@ -161,6 +163,21 @@ export function failures(state) {
|
|
|
161
163
|
found.push(`page errors: ${pyReprList(state.pageErrors)}`);
|
|
162
164
|
return found;
|
|
163
165
|
}
|
|
166
|
+
// Runs in the page before its screenshot. A loading="lazy" image outside the viewport
|
|
167
|
+
// never starts loading in a full-page screenshot, so it would draw as blank space and
|
|
168
|
+
// escape the image checks; NetNewsWire loads it as the reader scrolls, so load every
|
|
169
|
+
// image now and wait (up to timeout ms) for each to finish or fail.
|
|
170
|
+
async function loadImages(timeout) {
|
|
171
|
+
const images = [...document.images];
|
|
172
|
+
for (const image of images)
|
|
173
|
+
if (image.loading === "lazy")
|
|
174
|
+
image.loading = "eager";
|
|
175
|
+
const settled = images.map((image) => image.decode().catch(() => { }));
|
|
176
|
+
await Promise.race([
|
|
177
|
+
Promise.all(settled),
|
|
178
|
+
new Promise((resolve) => setTimeout(resolve, timeout)),
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
164
181
|
// Runs in the page after its screenshot.
|
|
165
182
|
function pageState() {
|
|
166
183
|
const article = document.querySelector(".articleBody");
|
|
@@ -194,6 +211,10 @@ function pageState() {
|
|
|
194
211
|
return box.width < 1 || box.height < 1;
|
|
195
212
|
})
|
|
196
213
|
.map((image) => image.currentSrc || image.src),
|
|
214
|
+
// Still loading after loadImages: neither drawn nor covered by the checks above.
|
|
215
|
+
unloadedImages: [...document.images]
|
|
216
|
+
.filter((image) => !image.complete)
|
|
217
|
+
.map((image) => image.currentSrc || image.src),
|
|
197
218
|
};
|
|
198
219
|
}
|
|
199
220
|
/**
|
|
@@ -260,6 +281,7 @@ async function checkPage(browser, origin, url, target, screenshot, footnotes) {
|
|
|
260
281
|
const page = await context.newPage();
|
|
261
282
|
page.on("pageerror", (error) => pageErrors.push(String(error)));
|
|
262
283
|
await page.goto(url, { waitUntil: "networkidle" });
|
|
284
|
+
await page.evaluate(loadImages, 10_000);
|
|
263
285
|
await page.screenshot({ path: screenshot, fullPage: true });
|
|
264
286
|
const state = await page.evaluate(pageState);
|
|
265
287
|
// Last: it clicks footnotes open, so it must not disturb the screenshot.
|
package/dist/commands/capture.js
CHANGED
|
@@ -18,7 +18,7 @@ export default function capture() {
|
|
|
18
18
|
nnwdump ${output}
|
|
19
19
|
continue
|
|
20
20
|
|
|
21
|
-
4. Preview it with \`npx nnw-theme@
|
|
21
|
+
4. Preview it with \`npx nnw-theme@2 render my-article\`.
|
|
22
22
|
|
|
23
23
|
nnwdump embeds the feed's real icon; pass --no-icon to keep the generated one.
|
|
24
24
|
The debugger's working directory is /, so give nnwdump an absolute path.
|
package/dist/commands/init.js
CHANGED
|
@@ -75,7 +75,7 @@ function templateRoot() {
|
|
|
75
75
|
"clone it, and run init inside it. With the GitHub CLI:\n" +
|
|
76
76
|
` gh repo create my-theme --template ${TEMPLATE} --public --clone\n` +
|
|
77
77
|
" cd my-theme\n" +
|
|
78
|
-
" npx nnw-theme@
|
|
78
|
+
" npx nnw-theme@2 init");
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
async function ask(value, flag, label, fallback) {
|
|
@@ -89,7 +89,7 @@ export default async function init({ values }) {
|
|
|
89
89
|
const root = templateRoot();
|
|
90
90
|
let theme = findTheme(root);
|
|
91
91
|
if (!existsSync(join(root, PLACEHOLDER_MARKER))) {
|
|
92
|
-
throw new ThemeError("this repository is already initialized; run `npx nnw-theme@
|
|
92
|
+
throw new ThemeError("this repository is already initialized; run `npx nnw-theme@2 setup` to install preview tools");
|
|
93
93
|
}
|
|
94
94
|
const flag = (name) => values[name];
|
|
95
95
|
const prompting = !(flag("name") && flag("creator") && flag("homepage"));
|
|
@@ -143,7 +143,7 @@ export default async function init({ values }) {
|
|
|
143
143
|
rmSync(join(root, "screenshots", "theme-preview.png"), { force: true });
|
|
144
144
|
rmSync(join(root, PLACEHOLDER_MARKER));
|
|
145
145
|
console.log(`Initialized ${name}.nnwtheme with identifier ${identifier}.`);
|
|
146
|
-
console.log("Create a deliberate marketplace image with `npx nnw-theme@
|
|
146
|
+
console.log("Create a deliberate marketplace image with `npx nnw-theme@2 screenshot --promote`.");
|
|
147
147
|
let marketplace = flag("marketplace");
|
|
148
148
|
if (marketplace === undefined) {
|
|
149
149
|
marketplace =
|
|
@@ -165,8 +165,8 @@ export default async function init({ values }) {
|
|
|
165
165
|
}
|
|
166
166
|
catch (error) {
|
|
167
167
|
console.error(`Warning: initialization succeeded, but preview setup did not: ${error.message}\n` +
|
|
168
|
-
"Finish preview setup later with `npx nnw-theme@
|
|
168
|
+
"Finish preview setup later with `npx nnw-theme@2 setup`.");
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
console.log(`Next: work on your design in ${relative(process.cwd(), theme) || "."}, then run \`npx nnw-theme@
|
|
171
|
+
console.log(`Next: work on your design in ${relative(process.cwd(), theme) || "."}, then run \`npx nnw-theme@2 preview\`.`);
|
|
172
172
|
}
|
|
@@ -5,13 +5,13 @@ export function marketplaceEnable() {
|
|
|
5
5
|
encoding: "utf8",
|
|
6
6
|
});
|
|
7
7
|
if (result.error) {
|
|
8
|
-
console.error("Warning: GitHub CLI is not installed. Run `npx nnw-theme@
|
|
8
|
+
console.error("Warning: GitHub CLI is not installed. Run `npx nnw-theme@2 marketplace enable` " +
|
|
9
9
|
"after installing and authenticating gh.");
|
|
10
10
|
return false;
|
|
11
11
|
}
|
|
12
12
|
if (result.status) {
|
|
13
13
|
console.error("Warning: could not add the marketplace topic. " +
|
|
14
|
-
`Run \`npx nnw-theme@
|
|
14
|
+
`Run \`npx nnw-theme@2 marketplace enable\` later.\n${(result.stderr || result.stdout).trim()}`);
|
|
15
15
|
return false;
|
|
16
16
|
}
|
|
17
17
|
console.log("Added the netnewswire-theme GitHub topic.");
|
package/dist/commands/update.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ThemeError } from "../project.js";
|
|
2
2
|
export default function update() {
|
|
3
|
-
throw new ThemeError("update was removed: the tooling now runs from npm with `npx nnw-theme@
|
|
3
|
+
throw new ThemeError("update was removed: the tooling now runs from npm with `npx nnw-theme@2`, so " +
|
|
4
4
|
"fixes arrive without changing this repository. Delete src/, tests/, " +
|
|
5
5
|
"pyproject.toml, uv.lock, and .python-version, and replace the stubs `check` " +
|
|
6
6
|
"warns about.");
|
package/dist/commands.js
CHANGED
|
@@ -145,7 +145,7 @@ export const COMMANDS = [
|
|
|
145
145
|
{
|
|
146
146
|
name: "completion",
|
|
147
147
|
summary: "print shell completion and an npx wrapper for fish, zsh, or bash",
|
|
148
|
-
description: "Print a script that defines an nnw-theme command running `npx --yes nnw-theme@
|
|
148
|
+
description: "Print a script that defines an nnw-theme command running `npx --yes nnw-theme@2` " +
|
|
149
149
|
"(skipped when nnw-theme is already installed) and completes its commands, " +
|
|
150
150
|
"options, and fixture names.",
|
|
151
151
|
options: [],
|
package/dist/completion.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Shell completion scripts, generated from the same command spec the parser uses.
|
|
2
|
-
// Each script also defines an nnw-theme function that runs `npx --yes nnw-theme@
|
|
2
|
+
// Each script also defines an nnw-theme function that runs `npx --yes nnw-theme@2`
|
|
3
3
|
// unless a real nnw-theme is already on PATH. The scripts never call npx to complete,
|
|
4
4
|
// so completion is instant.
|
|
5
5
|
import { BUILT_IN_FIXTURE_NAMES, COMMANDS, PROGRAM, } from "./commands.js";
|
|
6
|
-
const RUN = "npx --yes nnw-theme@
|
|
6
|
+
const RUN = "npx --yes nnw-theme@2";
|
|
7
7
|
const visible = () => COMMANDS.filter((command) => !command.hidden);
|
|
8
8
|
function flags(option) {
|
|
9
9
|
return option.negatable ? [`--${option.name}`, `--no-${option.name}`] : [`--${option.name}`];
|
|
@@ -26,7 +26,7 @@ function fishQuote(text) {
|
|
|
26
26
|
}
|
|
27
27
|
export function fish() {
|
|
28
28
|
const lines = [
|
|
29
|
-
"# nnw-theme completion for fish. Install: npx nnw-theme@
|
|
29
|
+
"# nnw-theme completion for fish. Install: npx nnw-theme@2 completion fish > ~/.config/fish/conf.d/nnw-theme.fish",
|
|
30
30
|
`if not command -q ${PROGRAM}`,
|
|
31
31
|
` function ${PROGRAM} --description 'Run nnw-theme through npx'`,
|
|
32
32
|
` ${RUN} $argv`,
|
|
@@ -101,7 +101,7 @@ export function bash() {
|
|
|
101
101
|
positional = `COMPREPLY=($(compgen -W ${single(words.join(" "))} -- "$cur"))`;
|
|
102
102
|
cases.push(` ${command.name})`, ...(valueCases.length ? [' case "$prev" in', ...valueCases, " esac"] : []), ' if [[ "$cur" == -* ]]; then', ` COMPREPLY=($(compgen -W ${single(allFlags(command).join(" "))} -- "$cur"))`, " else", ` ${positional}`, " fi", " ;;");
|
|
103
103
|
}
|
|
104
|
-
return `# nnw-theme completion for bash. Install: source <(npx --yes nnw-theme@
|
|
104
|
+
return `# nnw-theme completion for bash. Install: source <(npx --yes nnw-theme@2 completion bash)
|
|
105
105
|
if ! type -P ${PROGRAM} >/dev/null 2>&1; then
|
|
106
106
|
${PROGRAM}() { ${RUN} "$@"; }
|
|
107
107
|
fi
|
|
@@ -163,7 +163,7 @@ export function zsh() {
|
|
|
163
163
|
specs.push(`'1:${command.positionals?.name ?? "command"}:(${words.join(" ")})'`);
|
|
164
164
|
cases.push(` ${command.name}) _arguments -s ${specs.join(" ")} ;;`);
|
|
165
165
|
}
|
|
166
|
-
return `# nnw-theme completion for zsh. Install: source <(npx --yes nnw-theme@
|
|
166
|
+
return `# nnw-theme completion for zsh. Install: source <(npx --yes nnw-theme@2 completion zsh)
|
|
167
167
|
if (( ! $+commands[${PROGRAM}] )); then
|
|
168
168
|
${PROGRAM}() { ${RUN} "$@"; }
|
|
169
169
|
fi
|
package/dist/main.js
CHANGED
package/dist/render.js
CHANGED
|
@@ -81,7 +81,7 @@ const SCENARIOS = [
|
|
|
81
81
|
"Stress test",
|
|
82
82
|
"The kitchen-sink fixture: a very long headline, byline, and feed name; inline " +
|
|
83
83
|
"formatting; nested lists; an unbroken identifier that exposes horizontal " +
|
|
84
|
-
"overflow; a quotation, code block, table, figure, and
|
|
84
|
+
"overflow; a quotation, code block, table, figure, footnote, and HTML comments in the body.",
|
|
85
85
|
],
|
|
86
86
|
[
|
|
87
87
|
"large-text",
|
|
@@ -437,7 +437,7 @@ export function writeGallery(site, themeName, targets, results = null) {
|
|
|
437
437
|
if (results === null) {
|
|
438
438
|
summary =
|
|
439
439
|
`<p class=summary>${targets.length} cases, not checked yet. Run ` +
|
|
440
|
-
"<code>npx nnw-theme@
|
|
440
|
+
"<code>npx nnw-theme@2 check</code> to verify them in WebKit.</p>";
|
|
441
441
|
}
|
|
442
442
|
else {
|
|
443
443
|
const failed = targets.filter((target) => results[target.slug]?.length);
|
package/dist/stubs.js
CHANGED
|
@@ -4,12 +4,12 @@ import { readFileSync } from "node:fs";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
export const TEMPLATE_BLOB = "https://github.com/dave-atx/netnewswire-theme-template/blob/main";
|
|
6
6
|
export const STUBS = [
|
|
7
|
-
{ path: "AGENTS.md", name: "agents", version:
|
|
8
|
-
{ path: ".agents/skills/creating-nnw-themes/SKILL.md", name: "skill", version:
|
|
9
|
-
{ path: ".github/workflows/check.yml", name: "check", version:
|
|
10
|
-
{ path: ".github/workflows/pages.yml", name: "pages", version:
|
|
11
|
-
{ path: ".github/workflows/release.yml", name: "release", version:
|
|
12
|
-
{ path: ".github/workflows/screenshot.yml", name: "screenshot", version:
|
|
7
|
+
{ path: "AGENTS.md", name: "agents", version: 2 },
|
|
8
|
+
{ path: ".agents/skills/creating-nnw-themes/SKILL.md", name: "skill", version: 2 },
|
|
9
|
+
{ path: ".github/workflows/check.yml", name: "check", version: 2 },
|
|
10
|
+
{ path: ".github/workflows/pages.yml", name: "pages", version: 2 },
|
|
11
|
+
{ path: ".github/workflows/release.yml", name: "release", version: 2 },
|
|
12
|
+
{ path: ".github/workflows/screenshot.yml", name: "screenshot", version: 2 },
|
|
13
13
|
];
|
|
14
14
|
const MARKER = /nnw-theme-stub:\s*([a-z-]+)\s+v(\d+)/;
|
|
15
15
|
/** The marker in a stub file: its first line, or the first after YAML front matter. */
|
package/dist/validate.js
CHANGED
|
@@ -20,6 +20,7 @@ const SAFE_BUNDLE_NAME = /^[^/\\\0]+$/;
|
|
|
20
20
|
const OPTIONAL_THEME_FILE = /^(?:LICENSE|NOTICE)(?:\.[A-Za-z0-9-]+)?$/;
|
|
21
21
|
const S_IFMT = 0o170000;
|
|
22
22
|
const S_IFLNK = 0o120000;
|
|
23
|
+
const MACRO = /\[\[[A-Za-z0-9_-]+\]\]/g;
|
|
23
24
|
export class ValidationReport {
|
|
24
25
|
errors = [];
|
|
25
26
|
warnings = [];
|
|
@@ -52,6 +53,15 @@ function resourceReferences(template) {
|
|
|
52
53
|
parser.end(template);
|
|
53
54
|
return references;
|
|
54
55
|
}
|
|
56
|
+
/** Macros written inside HTML comments, which NetNewsWire substitutes like any other. */
|
|
57
|
+
function commentedMacros(template) {
|
|
58
|
+
const found = new Set();
|
|
59
|
+
for (const comment of template.matchAll(/<!--[\s\S]*?-->/g)) {
|
|
60
|
+
for (const macro of comment[0].matchAll(MACRO))
|
|
61
|
+
found.add(macro[0]);
|
|
62
|
+
}
|
|
63
|
+
return [...found];
|
|
64
|
+
}
|
|
55
65
|
function isRemote(value) {
|
|
56
66
|
return value.startsWith("//") || ["http", "https"].includes(urlsplit(value).scheme);
|
|
57
67
|
}
|
|
@@ -113,7 +123,7 @@ export function validateSource(theme, { allowRemoteMedia = false } = {}) {
|
|
|
113
123
|
report.errors.push("theme bundle name contains path-unsafe characters");
|
|
114
124
|
}
|
|
115
125
|
if (existsSync(join(dirname(theme), PLACEHOLDER_MARKER))) {
|
|
116
|
-
report.errors.push("run `npx nnw-theme@
|
|
126
|
+
report.errors.push("run `npx nnw-theme@2 init` before packaging this theme");
|
|
117
127
|
}
|
|
118
128
|
const names = readdirSync(theme);
|
|
119
129
|
for (const required of REQUIRED_THEME_FILES) {
|
|
@@ -144,6 +154,14 @@ export function validateSource(theme, { allowRemoteMedia = false } = {}) {
|
|
|
144
154
|
if (!template.includes("[[")) {
|
|
145
155
|
report.warnings.push("template.html contains no NetNewsWire macros");
|
|
146
156
|
}
|
|
157
|
+
const commented = commentedMacros(template);
|
|
158
|
+
if (commented.length) {
|
|
159
|
+
report.errors.push(`template.html has macros inside an HTML comment (${commented[0]}` +
|
|
160
|
+
`${commented.length > 1 ? ` and ${commented.length - 1} more` : ""}). ` +
|
|
161
|
+
"NetNewsWire substitutes them there too, so an article containing --> " +
|
|
162
|
+
"ends the comment early and the rest shows as text; " +
|
|
163
|
+
"write macro names without double brackets");
|
|
164
|
+
}
|
|
147
165
|
const references = resourceReferences(template);
|
|
148
166
|
if (/<style\b[^>]*>[\s\S]*?@import\s/i.test(template)) {
|
|
149
167
|
report.errors.push("CSS @import is not allowed in template.html");
|
package/lldb/nnwdump.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Stopped at the final `return d` of ArticleRenderer.articleSubstitutions(), this
|
|
4
4
|
reads the substitution dictionary NetNewsWire built for the selected article and
|
|
5
|
-
writes it as a TOML fixture. `npx nnw-theme@
|
|
5
|
+
writes it as a TOML fixture. `npx nnw-theme@2 capture` prints the setup steps.
|
|
6
6
|
|
|
7
7
|
Values cross the debugger boundary base64-encoded, so nothing is truncated or
|
|
8
8
|
mis-escaped. HTML values are written as '''literal''' strings, so fixtures stay
|
|
@@ -30,7 +30,7 @@ def resolve_output(argument: str) -> Path:
|
|
|
30
30
|
"""Relative paths resolve against the working directory.
|
|
31
31
|
|
|
32
32
|
This file ships in the nnw-theme package, not the theme repository, and lldb's
|
|
33
|
-
working directory under Xcode is /, so `npx nnw-theme@
|
|
33
|
+
working directory under Xcode is /, so `npx nnw-theme@2 capture` prints an absolute
|
|
34
34
|
output path to pass instead.
|
|
35
35
|
"""
|
|
36
36
|
path = Path(argument).expanduser()
|
|
@@ -60,7 +60,7 @@ def fixture_text(pairs: list[tuple[str, str]], name: str) -> str:
|
|
|
60
60
|
ordered = sorted(pairs, key=lambda pair: (pair[0] in _HTML_KEYS, pair[0]))
|
|
61
61
|
header = (
|
|
62
62
|
"# NetNewsWire article captured with nnwdump.\n"
|
|
63
|
-
f"# Preview with: npx nnw-theme@
|
|
63
|
+
f"# Preview with: npx nnw-theme@2 render {name}\n\n"
|
|
64
64
|
)
|
|
65
65
|
return header + "\n".join(toml_line(key, value) for key, value in ordered) + "\n"
|
|
66
66
|
|