pf2e-spellbook 1.1.0 → 1.2.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 +15 -0
- package/bin/cli.mjs +86 -0
- package/dist/index.html +7 -0
- package/dist/sw.js +1 -1
- package/package.json +8 -1
- package/src/styles.css +2 -0
- package/src/template.html +5 -0
package/README.md
CHANGED
|
@@ -76,6 +76,21 @@ curse, companions/familiars).
|
|
|
76
76
|
|
|
77
77
|
## Using it
|
|
78
78
|
|
|
79
|
+
### Quick start (npm)
|
|
80
|
+
|
|
81
|
+
With [Node.js](https://nodejs.org) installed:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
npx pf2e-spellbook
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
That downloads the latest release, serves it at `http://localhost:8724`, and opens
|
|
88
|
+
your browser. Characters are saved in that browser (per origin), so they survive
|
|
89
|
+
package updates. `--port N` picks another port (a different port is a different
|
|
90
|
+
origin, i.e. a separate set of characters); `--no-open` skips the browser launch.
|
|
91
|
+
|
|
92
|
+
### The HTML file itself
|
|
93
|
+
|
|
79
94
|
Open `dist/index.html` in any browser — no server, no install, works offline. State
|
|
80
95
|
is saved in the browser's localStorage per device.
|
|
81
96
|
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Serves the built spellbook from dist/ on localhost and opens the browser.
|
|
3
|
+
// The port is fixed by default: localStorage (where all characters live) is
|
|
4
|
+
// keyed by origin, so a stable http://localhost:8724 keeps characters across
|
|
5
|
+
// package updates and install locations. localhost is a secure context, so
|
|
6
|
+
// the service worker and the "Install app" PWA button work too.
|
|
7
|
+
import http from 'node:http';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { spawn } from 'node:child_process';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_PORT = 8724;
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
console.log(`Usage: pf2e-spellbook [--port N] [--no-open]
|
|
17
|
+
|
|
18
|
+
Serves the PF2e Spellbook at http://localhost:${DEFAULT_PORT} and opens your browser.
|
|
19
|
+
|
|
20
|
+
--port N listen on port N instead of ${DEFAULT_PORT} (note: characters are
|
|
21
|
+
stored per-origin, so a different port is a different spellbook)
|
|
22
|
+
--no-open don't open the browser, just serve`);
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
const portIdx = args.indexOf('--port');
|
|
26
|
+
const port = portIdx !== -1 ? Number(args[portIdx + 1]) : DEFAULT_PORT;
|
|
27
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
28
|
+
console.error(`pf2e-spellbook: invalid --port ${args[portIdx + 1]}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const noOpen = args.includes('--no-open');
|
|
32
|
+
|
|
33
|
+
const distDir = new URL('../dist/', import.meta.url);
|
|
34
|
+
const FILES = {
|
|
35
|
+
'/': ['index.html', 'text/html; charset=utf-8'],
|
|
36
|
+
'/index.html': ['index.html', 'text/html; charset=utf-8'],
|
|
37
|
+
'/sw.js': ['sw.js', 'text/javascript; charset=utf-8'],
|
|
38
|
+
'/manifest.webmanifest': ['manifest.webmanifest', 'application/manifest+json'],
|
|
39
|
+
'/icon.svg': ['icon.svg', 'image/svg+xml'],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const server = http.createServer(async (req, res) => {
|
|
43
|
+
const entry = FILES[new URL(req.url, 'http://localhost').pathname];
|
|
44
|
+
if (!entry || (req.method !== 'GET' && req.method !== 'HEAD')) {
|
|
45
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' }).end('Not found');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const [file, type] = entry;
|
|
49
|
+
try {
|
|
50
|
+
const body = await readFile(new URL(file, distDir));
|
|
51
|
+
// no-cache so package updates (new sw.js/index.html) are picked up on reload
|
|
52
|
+
res.writeHead(200, {
|
|
53
|
+
'Content-Type': type,
|
|
54
|
+
'Content-Length': body.byteLength,
|
|
55
|
+
'Cache-Control': 'no-cache',
|
|
56
|
+
});
|
|
57
|
+
res.end(req.method === 'HEAD' ? undefined : body);
|
|
58
|
+
} catch {
|
|
59
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' }).end('Read error');
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function openBrowser(url) {
|
|
64
|
+
const [cmd, cmdArgs] =
|
|
65
|
+
process.platform === 'darwin' ? ['open', [url]]
|
|
66
|
+
: process.platform === 'win32' ? ['cmd', ['/c', 'start', '', url]]
|
|
67
|
+
: ['xdg-open', [url]];
|
|
68
|
+
spawn(cmd, cmdArgs, { stdio: 'ignore', detached: true }).on('error', () => {
|
|
69
|
+
console.error(`Couldn't open a browser — go to ${url}`);
|
|
70
|
+
}).unref();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const url = `http://localhost:${port}/`;
|
|
74
|
+
server.on('error', (err) => {
|
|
75
|
+
if (err.code === 'EADDRINUSE') {
|
|
76
|
+
console.log(`Port ${port} is in use — assuming the spellbook is already running at ${url}`);
|
|
77
|
+
if (!noOpen) openBrowser(url);
|
|
78
|
+
process.exit(0);
|
|
79
|
+
}
|
|
80
|
+
console.error(`pf2e-spellbook: ${err.message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
|
83
|
+
server.listen(port, '127.0.0.1', () => {
|
|
84
|
+
console.log(`PF2e Spellbook running at ${url} (Ctrl+C to stop)`);
|
|
85
|
+
if (!noOpen) openBrowser(url);
|
|
86
|
+
});
|
package/dist/index.html
CHANGED
|
@@ -195,6 +195,8 @@ button{font-family:inherit;font-size:1.05rem;cursor:pointer;}
|
|
|
195
195
|
}
|
|
196
196
|
.btn.secondary{background:var(--surface-2);color:var(--accent-text);border:1px solid var(--accent-dim);box-shadow:none;}
|
|
197
197
|
.btn:active{transform:translateY(1px);}
|
|
198
|
+
a.btn{display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;text-align:center;}
|
|
199
|
+
a.btn .icn{width:1.15em;height:1.15em;}
|
|
198
200
|
.linklike{background:none;border:none;color:var(--info);font-weight:700;text-decoration:underline;padding:0;font-size:inherit;}
|
|
199
201
|
|
|
200
202
|
label.field{display:block;margin:14px 0;}
|
|
@@ -502,6 +504,11 @@ nav.bottom button.on{color:var(--accent-text);}
|
|
|
502
504
|
<button class="btn secondary" onclick="importCharacter()">Import code</button>
|
|
503
505
|
</div>
|
|
504
506
|
<textarea id="menuIO" rows="3" placeholder="Character code appears here on Export — or paste one here, then tap Import."></textarea>
|
|
507
|
+
|
|
508
|
+
<h2 style="margin-top:24px">Support</h2>
|
|
509
|
+
<p class="meta">This spellbook is a free fan project. If it has earned a place at your table, you can help keep it going:</p>
|
|
510
|
+
<a class="btn secondary kofi" href="https://ko-fi.com/whytevoyd" target="_blank" rel="noopener"><svg class="icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8h13v6.5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4z"/><path d="M16.5 9h1.75a2.75 2.75 0 1 1 0 5.5H16.5"/><path d="M10 15.2c-1.8-1.1-2.7-2.2-2.7-3.2 0-.9.7-1.6 1.6-1.6.5 0 .9.2 1.1.5.2-.3.6-.5 1.1-.5.9 0 1.6.7 1.6 1.6 0 1-.9 2.1-2.7 3.2z" fill="currentColor" stroke-width="0"/></svg> Support on Ko-fi</a>
|
|
511
|
+
|
|
505
512
|
<button class="btn secondary" onclick="closeMenu()">← Back</button>
|
|
506
513
|
<p class="meta" style="text-align:center;margin-top:18px;font-size:.8rem">Spell data: Paizo (OGL/ORC) via the Foundry VTT pf2e project · unofficial fan tool<br><span id="dataStamp"></span></p>
|
|
507
514
|
</section>
|
package/dist/sw.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Network-first for the app page (fresh when online, cached when offline),
|
|
3
3
|
cache-first for static assets. Cache version is stamped at build time so a
|
|
4
4
|
redeploy refreshes clients automatically. */
|
|
5
|
-
const CACHE = "pf2e-spellbook-
|
|
5
|
+
const CACHE = "pf2e-spellbook-20260708225220";
|
|
6
6
|
const SHELL = ["./", "./index.html", "./manifest.webmanifest", "./icon.svg"];
|
|
7
7
|
|
|
8
8
|
self.addEventListener("install", (e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pf2e-spellbook",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Interactive Pathfinder 2e spellbook (single self-contained HTML).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pathfinder",
|
|
@@ -20,7 +20,14 @@
|
|
|
20
20
|
"author": "Whyte",
|
|
21
21
|
"license": "SEE LICENSE IN LICENSE",
|
|
22
22
|
"type": "module",
|
|
23
|
+
"bin": {
|
|
24
|
+
"pf2e-spellbook": "bin/cli.mjs"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
23
29
|
"files": [
|
|
30
|
+
"bin/",
|
|
24
31
|
"src/",
|
|
25
32
|
"tools/",
|
|
26
33
|
"data/",
|
package/src/styles.css
CHANGED
|
@@ -166,6 +166,8 @@ button{font-family:inherit;font-size:1.05rem;cursor:pointer;}
|
|
|
166
166
|
}
|
|
167
167
|
.btn.secondary{background:var(--surface-2);color:var(--accent-text);border:1px solid var(--accent-dim);box-shadow:none;}
|
|
168
168
|
.btn:active{transform:translateY(1px);}
|
|
169
|
+
a.btn{display:flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;text-align:center;}
|
|
170
|
+
a.btn .icn{width:1.15em;height:1.15em;}
|
|
169
171
|
.linklike{background:none;border:none;color:var(--info);font-weight:700;text-decoration:underline;padding:0;font-size:inherit;}
|
|
170
172
|
|
|
171
173
|
label.field{display:block;margin:14px 0;}
|
package/src/template.html
CHANGED
|
@@ -73,6 +73,11 @@
|
|
|
73
73
|
<button class="btn secondary" onclick="importCharacter()">Import code</button>
|
|
74
74
|
</div>
|
|
75
75
|
<textarea id="menuIO" rows="3" placeholder="Character code appears here on Export — or paste one here, then tap Import."></textarea>
|
|
76
|
+
|
|
77
|
+
<h2 style="margin-top:24px">Support</h2>
|
|
78
|
+
<p class="meta">This spellbook is a free fan project. If it has earned a place at your table, you can help keep it going:</p>
|
|
79
|
+
<a class="btn secondary kofi" href="https://ko-fi.com/whytevoyd" target="_blank" rel="noopener"><svg class="icn" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 8h13v6.5a4 4 0 0 1-4 4h-5a4 4 0 0 1-4-4z"/><path d="M16.5 9h1.75a2.75 2.75 0 1 1 0 5.5H16.5"/><path d="M10 15.2c-1.8-1.1-2.7-2.2-2.7-3.2 0-.9.7-1.6 1.6-1.6.5 0 .9.2 1.1.5.2-.3.6-.5 1.1-.5.9 0 1.6.7 1.6 1.6 0 1-.9 2.1-2.7 3.2z" fill="currentColor" stroke-width="0"/></svg> Support on Ko-fi</a>
|
|
80
|
+
|
|
76
81
|
<button class="btn secondary" onclick="closeMenu()">← Back</button>
|
|
77
82
|
<p class="meta" style="text-align:center;margin-top:18px;font-size:.8rem">Spell data: Paizo (OGL/ORC) via the Foundry VTT pf2e project · unofficial fan tool<br><span id="dataStamp"></span></p>
|
|
78
83
|
</section>
|