tosijs-ui 1.16.21 → 1.16.22
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/CHANGELOG.md +70 -0
- package/README.md +2 -2
- package/bin/make-icon-data.js +1 -1
- package/dist/code-editor.js +2 -3
- package/dist/data-table.js +1 -1
- package/dist/doc-browser.d.ts +4 -0
- package/dist/doc-browser.js +46 -101
- package/dist/doc-system/doc-system-styles.js +11 -0
- package/dist/doc-system/doc-system.js +1 -1
- package/dist/doc-system/highlight-styles.js +11 -16
- package/dist/doc-system/routing.d.ts +9 -0
- package/dist/doc-system/routing.js +22 -6
- package/dist/doc-system/site/build-warnings.d.ts +10 -0
- package/dist/doc-system/site/build-warnings.js +24 -0
- package/dist/doc-system/site/dev-auth.d.ts +13 -0
- package/dist/doc-system/site/dev-auth.js +40 -2
- package/dist/doc-system/site/dev-server.js +13 -1
- package/dist/doc-system/site/docs.d.ts +14 -0
- package/dist/doc-system/site/docs.js +61 -81
- package/dist/doc-system/site/epub.js +6 -2
- package/dist/doc-system/site/generate-site.js +15 -5
- package/dist/doc-system/site/orchestrator.js +1 -0
- package/dist/doc-system/site/site-config.d.ts +25 -0
- package/dist/editable-rect.js +1 -3
- package/dist/highlight-block.js +3 -10
- package/dist/icon-rules.d.ts +10 -0
- package/dist/icon-rules.js +13 -5
- package/dist/icon-svg.js +13 -19
- package/dist/icons.js +12 -15
- package/dist/iife.js +91 -95
- package/dist/iife.js.map +23 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/live-example/component.js +3 -4
- package/dist/menu.js +2 -2
- package/dist/natural-compare.js +6 -2
- package/dist/password-strength.js +1 -1
- package/dist/pocket-bar.js +2 -2
- package/dist/size-break.js +4 -3
- package/dist/track-drag.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +12 -8
- package/package.json +1 -1
|
@@ -1899,6 +1899,8 @@ export async function devServer(config, opts = {}) {
|
|
|
1899
1899
|
// A share link's session may browse and may not save (asked AFTER validity, which
|
|
1900
1900
|
// is the call-order `cookieMayWrite` documents).
|
|
1901
1901
|
sessionMayWrite: cookieMayWrite(auth, session),
|
|
1902
|
+
trustLan: config.trustLan === true,
|
|
1903
|
+
proxied: isProxiedRequest(request.headers),
|
|
1902
1904
|
}) &&
|
|
1903
1905
|
/*
|
|
1904
1906
|
CSRF (#90). The direct path is peer-address-only, so without this any page the
|
|
@@ -1908,7 +1910,9 @@ export async function devServer(config, opts = {}) {
|
|
|
1908
1910
|
*/
|
|
1909
1911
|
csrfOk(request, viaTunnel);
|
|
1910
1912
|
if (!authorized) {
|
|
1911
|
-
console.warn(`⚠️ refused ${request.method} /__docstore/source from ${peer ?? 'unknown'} — ` +
|
|
1913
|
+
console.warn(`⚠️ refused ${request.method} /__docstore/source from ${peer ?? 'unknown'} — ` +
|
|
1914
|
+
`no session (use \`${TUNNEL_LINK_CMD}\` for an edit link` +
|
|
1915
|
+
`${config.trustLan ? '' : ', or set trustLan: true for LAN devices'}).`);
|
|
1912
1916
|
return new Response('not authorized to edit source', {
|
|
1913
1917
|
status: 403,
|
|
1914
1918
|
headers: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
@@ -2225,6 +2229,14 @@ export async function devServer(config, opts = {}) {
|
|
|
2225
2229
|
process.exit(code);
|
|
2226
2230
|
};
|
|
2227
2231
|
console.log(`Listening on https://localhost:${PORT}`);
|
|
2232
|
+
// trustLan widens who may write; say so every launch, never silently (#227).
|
|
2233
|
+
if (config.trustLan && config.editableSources) {
|
|
2234
|
+
console.warn(`⚠️ trustLan: any device on a private network that can reach this machine may read and ` +
|
|
2235
|
+
`edit source. Use it on a network you control.`);
|
|
2236
|
+
}
|
|
2237
|
+
else if (config.trustLan) {
|
|
2238
|
+
console.warn(`⚠️ trustLan does nothing without editableSources: true (there is no source endpoint to trust).`);
|
|
2239
|
+
}
|
|
2228
2240
|
if (SINK_PATH) {
|
|
2229
2241
|
console.log(`Debug sink: POST /__debug-sink -> tail -f ${SINK_PATH}`);
|
|
2230
2242
|
}
|
|
@@ -22,6 +22,18 @@ export interface Doc {
|
|
|
22
22
|
* pre-rendered public page — so "drafts don't ship" was false twice over.
|
|
23
23
|
*/
|
|
24
24
|
hidden?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* This doc is the site root (`/`) instead of README, which then gets `/readme/`. Set by the
|
|
27
|
+
* site's `home` option, or by the doc's own `{"home": true}`. At most one doc may be home.
|
|
28
|
+
*/
|
|
29
|
+
home?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* `false` drops the site's header bar on this page, as `layout: "full-screen"` drops the
|
|
32
|
+
* nav: for an app page with its own toolbar, above all as an installed PWA, where the bar
|
|
33
|
+
* is the only thing between the app and the whole screen. The page links back to the docs
|
|
34
|
+
* its own way; the builder adds nothing. Unset means the header shows.
|
|
35
|
+
*/
|
|
36
|
+
header?: boolean;
|
|
25
37
|
/**
|
|
26
38
|
* Which book(s) this doc binds into. A name, a list of names, or `'none'` to keep it
|
|
27
39
|
* on the site and out of every book. Unset means the default book; `'default'` names
|
|
@@ -91,6 +103,8 @@ export interface ExtractDocsOptions {
|
|
|
91
103
|
paths: string[];
|
|
92
104
|
ignore?: string[];
|
|
93
105
|
output?: string;
|
|
106
|
+
/** the doc served at `/` instead of README, by slug or filename (the site's `home`) */
|
|
107
|
+
home?: string;
|
|
94
108
|
}
|
|
95
109
|
/**
|
|
96
110
|
* The title a markdown file implies, when nothing declares one.
|
|
@@ -1,97 +1,43 @@
|
|
|
1
1
|
/*#
|
|
2
2
|
# docs
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
This is used by the `doc-browser` component to build searchable, navigable documentation
|
|
10
|
-
from your project's source files.
|
|
4
|
+
`extractDocs` collects documentation from markdown files and `/*#` comment blocks in source
|
|
5
|
+
code into one corpus, the `docs.json` that the [doc-browser](/doc-browser/) and the static
|
|
6
|
+
site render. `tosijs-ui/site` runs it for you; call it yourself only to build a corpus
|
|
7
|
+
outside that pipeline. It reads files, so it runs under bun or node.
|
|
11
8
|
|
|
12
9
|
## Usage
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
extractDocs({
|
|
17
|
-
paths: ['src', 'README.md'],
|
|
18
|
-
ignore: ['node_modules', 'dist', 'build']
|
|
19
|
-
path: 'public/docs.json'
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
## API
|
|
23
|
-
|
|
24
|
-
### `extractDocs(options)`
|
|
25
|
-
|
|
26
|
-
Scans directories for markdown files and source code comments.
|
|
27
|
-
|
|
28
|
-
**Options:**
|
|
29
|
-
- `paths`: Array of directory paths or file paths to scan
|
|
30
|
-
- `ignore`: Array of directory names to ignore (default: ['node_modules', 'dist'])
|
|
31
|
-
- `output`: if provided, path to write json result.
|
|
32
|
-
|
|
33
|
-
**Returns:** Array of `Doc` objects
|
|
34
|
-
|
|
35
|
-
### `Doc` object structure
|
|
36
|
-
|
|
37
|
-
{
|
|
38
|
-
text: string, // Markdown content
|
|
39
|
-
title: string, // First heading or filename
|
|
40
|
-
filename: string, // Just the filename
|
|
41
|
-
path: string, // Full file path
|
|
42
|
-
pin?: 'top' | 'bottom' // Optional pinning for sort order
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
## Documentation Format
|
|
46
|
-
|
|
47
|
-
### Markdown files
|
|
48
|
-
|
|
49
|
-
Any `.md` file will be included in its entirety.
|
|
50
|
-
|
|
51
|
-
### Source code comments
|
|
11
|
+
```typescript
|
|
12
|
+
import { extractDocs } from 'tosijs-ui/site'
|
|
52
13
|
|
|
53
|
-
|
|
14
|
+
const docs = extractDocs({
|
|
15
|
+
paths: ['src', 'README.md'],
|
|
16
|
+
output: 'public/docs.json',
|
|
17
|
+
})
|
|
18
|
+
```
|
|
54
19
|
|
|
55
|
-
|
|
56
|
-
# My Component
|
|
20
|
+
## `extractDocs(options)`
|
|
57
21
|
|
|
58
|
-
|
|
22
|
+
- `paths`: directories to scan, and individual files (list root `.md` files explicitly)
|
|
23
|
+
- `ignore`: directory names to skip (default `['node_modules', 'dist', 'build']`)
|
|
24
|
+
- `output`: if given, the path the corpus is written to as JSON
|
|
25
|
+
- `home`: the doc served at `/` instead of README, by slug or filename
|
|
59
26
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```
|
|
63
|
-
```js
|
|
64
|
-
console.log('hello world')
|
|
65
|
-
```
|
|
66
|
-
```css
|
|
67
|
-
my-componet {
|
|
68
|
-
color: blue
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
*/
|
|
72
|
-
|
|
73
|
-
export class MyComponent extends Component {
|
|
74
|
-
// implementation
|
|
75
|
-
}
|
|
76
|
-
...
|
|
77
|
-
|
|
78
|
-
The [doc-browser](/?doc-browser.ts) will render the output as a test-bed project with documentation and live examples.
|
|
27
|
+
Returns an array of `Doc` objects: `text` (the markdown), `title`, `filename`, `path`, and
|
|
28
|
+
whatever metadata the doc declares (`pin`, `order`, `parent`, `layout`, `description`, …).
|
|
79
29
|
|
|
80
|
-
|
|
30
|
+
## What is collected
|
|
81
31
|
|
|
82
|
-
|
|
32
|
+
- **`.md` files**, whole. YAML frontmatter is read and removed.
|
|
33
|
+
- **`/*#` … `*/` blocks** in `.ts`, `.tjs`, `.js` and `.css` files, as markdown. The first heading
|
|
34
|
+
is the page title. A block counts only when `/*#` starts a line.
|
|
35
|
+
- **Files whose name starts with `_`** are skipped: use the prefix for drafts and fragments.
|
|
83
36
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
ts, js, css:
|
|
88
|
-
/*{ "pin": "bottom" }*/
|
|
89
|
-
|
|
90
|
-
This pins the document to the top or bottom of the navigation list. Within a
|
|
91
|
-
pin bucket, add `"order"` (a number, lower first; default 500) to rank items —
|
|
92
|
-
e.g. `{ "pin": "top", "order": 1 }` above `{ "pin": "top", "order": 2 }`.
|
|
37
|
+
Metadata blocks (`<!--{ … }-->` in markdown, `/*{ … }*/` in code), nav order, sections and
|
|
38
|
+
which code fences run live are described under [Doc format](/doc-site-system/#doc-format).
|
|
93
39
|
*/
|
|
94
|
-
/*{"
|
|
40
|
+
/*{ "parent": "Documentation System", "order": 60 }*/
|
|
95
41
|
// TODO CLI options
|
|
96
42
|
import * as fs from 'fs';
|
|
97
43
|
import * as path from 'path';
|
|
@@ -208,6 +154,14 @@ export function parseFrontmatter(content) {
|
|
|
208
154
|
else if (val)
|
|
209
155
|
console.error(`unknown layout ${JSON.stringify(val)} in frontmatter`);
|
|
210
156
|
}
|
|
157
|
+
else if (key === 'header') {
|
|
158
|
+
if (/^(false|no|0)$/i.test(val))
|
|
159
|
+
data.header = false;
|
|
160
|
+
}
|
|
161
|
+
else if (key === 'home') {
|
|
162
|
+
if (/^(true|yes|1)$/i.test(val))
|
|
163
|
+
data.home = true;
|
|
164
|
+
}
|
|
211
165
|
}
|
|
212
166
|
if (!matched)
|
|
213
167
|
return { data: {}, body: content }; // a bare `---`, not frontmatter
|
|
@@ -513,7 +467,7 @@ walked) {
|
|
|
513
467
|
return markdownFiles.sort(pinnedSort);
|
|
514
468
|
}
|
|
515
469
|
export function extractDocs(options) {
|
|
516
|
-
const { paths, ignore = ['node_modules', 'dist', 'build'], output } = options;
|
|
470
|
+
const { paths, ignore = ['node_modules', 'dist', 'build'], output, home, } = options;
|
|
517
471
|
const walked = new Set();
|
|
518
472
|
const found = findMarkdownFiles(paths, ignore, new Set(DEFAULT_DOC_IGNORES), walked);
|
|
519
473
|
/*
|
|
@@ -529,7 +483,18 @@ export function extractDocs(options) {
|
|
|
529
483
|
Filtering at extraction makes it structural: hidden docs do not exist as far as the
|
|
530
484
|
rest of the pipeline is concerned. Descendants of a hidden doc go with it.
|
|
531
485
|
*/
|
|
486
|
+
if (home != null)
|
|
487
|
+
markHome(found, home);
|
|
488
|
+
const homes = found.filter((doc) => doc.home);
|
|
489
|
+
if (homes.length > 1) {
|
|
490
|
+
throw new Error(`more than one doc is the home page: ${homes
|
|
491
|
+
.map((doc) => doc.path)
|
|
492
|
+
.join(', ')}. Mark one, with the site's \`home\` option or {"home": true}.`);
|
|
493
|
+
}
|
|
532
494
|
const docs = withoutHidden(found, buildSlugMap(found));
|
|
495
|
+
if (homes.length === 1 && !docs.includes(homes[0])) {
|
|
496
|
+
throw new Error(`the home page ${homes[0].path} is hidden, so the site would have no root page.`);
|
|
497
|
+
}
|
|
533
498
|
/*
|
|
534
499
|
Insets and conditional text are resolved HERE, for the same reason hidden docs are dropped
|
|
535
500
|
here: the corpus is the thing that ships, so anything assembled later is assembled by each
|
|
@@ -550,6 +515,21 @@ export function extractDocs(options) {
|
|
|
550
515
|
}
|
|
551
516
|
return docs;
|
|
552
517
|
}
|
|
518
|
+
/*
|
|
519
|
+
The site's `home` option names the root doc by slug or filename. Matched against the slugs
|
|
520
|
+
the corpus would have with README as root, which are the names an author sees in the nav
|
|
521
|
+
today. Naming nothing fails the build: a site whose root is not the page its config asks for
|
|
522
|
+
is the silent-failure class this system refuses.
|
|
523
|
+
*/
|
|
524
|
+
function markHome(docs, home) {
|
|
525
|
+
const slugs = buildSlugMap(docs.map(({ filename }) => ({ filename })));
|
|
526
|
+
const wanted = home.replace(/^\/+|\/+$/g, '');
|
|
527
|
+
const match = docs.filter((doc) => doc.filename === wanted || slugs[doc.filename] === wanted);
|
|
528
|
+
if (match.length !== 1) {
|
|
529
|
+
throw new Error(`site option home: "${home}" names ${match.length === 0 ? 'no doc' : `${match.length} docs`}. Use a page's slug (as in its URL) or its filename.`);
|
|
530
|
+
}
|
|
531
|
+
match[0].home = true;
|
|
532
|
+
}
|
|
553
533
|
export function saveDocsJSON(docs, outputPath) {
|
|
554
534
|
const jsonData = JSON.stringify(docs, null, 2);
|
|
555
535
|
fs.writeFileSync(outputPath, jsonData, 'utf8');
|
|
@@ -620,7 +620,8 @@ export async function buildEpub(config, opts = {}) {
|
|
|
620
620
|
docs and anything marked `book: "none"`, so a doc that belongs to no volume cannot leak
|
|
621
621
|
into the default one just by not naming a book.
|
|
622
622
|
*/
|
|
623
|
-
const
|
|
623
|
+
const siteSlugs = buildSlugMap(corpus);
|
|
624
|
+
const volumes = partitionByBook(corpus, siteSlugs);
|
|
624
625
|
const visible = volumes.get(opts.bookTarget ?? DEFAULT_BOOK) ?? [];
|
|
625
626
|
/*
|
|
626
627
|
A volume with no chapters is a failure, not a book.
|
|
@@ -659,7 +660,10 @@ export async function buildEpub(config, opts = {}) {
|
|
|
659
660
|
}
|
|
660
661
|
// Curate/reorder for the book (a no-op when config.book is absent).
|
|
661
662
|
const docs = selectBookDocs(placed, config.book);
|
|
662
|
-
|
|
663
|
+
// Slugs come from the whole published corpus, as the site's do: computed from this book's
|
|
664
|
+
// subset, README would take the root whenever the site's `home` doc is outside the book,
|
|
665
|
+
// and the book's links would disagree with the site's URLs.
|
|
666
|
+
const slugMap = siteSlugs;
|
|
663
667
|
const roots = buildNavTree(docs, slugMap);
|
|
664
668
|
const fileFor = (d) => `${slugMap[d.filename] || 'index'}.xhtml`;
|
|
665
669
|
// slug → chapter filename, for the docs actually IN this book — the target set for
|
|
@@ -15,7 +15,7 @@ import { highlightHtml } from '../highlight.js';
|
|
|
15
15
|
import { buildSlugMap, pathForSlug, rewriteDocLinks, withBase, } from '../routing.js';
|
|
16
16
|
import { buildNavTree, navOpenPath } from '../nav-tree.js';
|
|
17
17
|
import { renderDocMarkdown, docDescription, } from '../render.js';
|
|
18
|
-
import { deadInPageAnchors, misconfigured } from './build-warnings.js';
|
|
18
|
+
import { bareNameLinks, deadInPageAnchors, misconfigured, } from './build-warnings.js';
|
|
19
19
|
const escapeAttr = (s) => s
|
|
20
20
|
.replace(/&/g, '&')
|
|
21
21
|
.replace(/</g, '<')
|
|
@@ -127,9 +127,8 @@ async function pageHtml(doc, config, slugMap, configAttr) {
|
|
|
127
127
|
flicker the nav-hydration fix chased in 1.7.3. An attribute in the served markup means the
|
|
128
128
|
first paint is already correct, and the doc-browser keeps it in step on SPA navigation.
|
|
129
129
|
*/
|
|
130
|
-
const layoutAttr = doc.layout
|
|
131
|
-
?
|
|
132
|
-
: '';
|
|
130
|
+
const layoutAttr = (doc.layout ? ` data-layout="${escapeAttr(doc.layout)}"` : '') +
|
|
131
|
+
(doc.header === false ? ' data-no-header' : '');
|
|
133
132
|
// ONE rule, shared with the doc-browser's hydration path — see doc-title.ts. Writing
|
|
134
133
|
// it twice is what made the title change on hydration (issue #6).
|
|
135
134
|
const title = pageTitle(doc, projectName);
|
|
@@ -311,10 +310,16 @@ export async function generateSite(config) {
|
|
|
311
310
|
// ./generate-css.ts; pages here just <link> to it.
|
|
312
311
|
let count = 0;
|
|
313
312
|
const deadAnchors = [];
|
|
313
|
+
const bareLinks = [];
|
|
314
314
|
for (const doc of docs) {
|
|
315
315
|
const slug = slugMap[doc.filename];
|
|
316
316
|
const dir = slug === '' ? outputDir : `${outputDir}/${slug}`;
|
|
317
|
-
const
|
|
317
|
+
const rendered = renderDocMarkdown(doc.text);
|
|
318
|
+
const dead = deadInPageAnchors(rendered);
|
|
319
|
+
// On the root page (`/`) a bare name resolves to `/name`, which works; elsewhere it does not.
|
|
320
|
+
const bare = slug === '' ? [] : bareNameLinks(rendered);
|
|
321
|
+
if (bare.length)
|
|
322
|
+
bareLinks.push(` ${doc.path || doc.filename}: ${bare.join(', ')}`);
|
|
318
323
|
if (dead.length)
|
|
319
324
|
deadAnchors.push(` ${doc.path || doc.filename}: ${dead
|
|
320
325
|
.map((d) => `#${d}`)
|
|
@@ -326,6 +331,11 @@ export async function generateSite(config) {
|
|
|
326
331
|
}
|
|
327
332
|
count += 1;
|
|
328
333
|
}
|
|
334
|
+
if (bareLinks.length) {
|
|
335
|
+
misconfigured(config.strict, `\n⚠️ Links written as a bare page name. Every page is served at /slug/, so these\n` +
|
|
336
|
+
` resolve under the page they are on and reach nothing; write /name/ instead:\n` +
|
|
337
|
+
`${bareLinks.join('\n')}\n`);
|
|
338
|
+
}
|
|
329
339
|
if (deadAnchors.length) {
|
|
330
340
|
misconfigured(config.strict, `\n⚠️ In-page links that point at nothing on their page. A heading's id is its text,\n` +
|
|
331
341
|
` lowercased, punctuation dropped, spaces as "-" (a repeat gets -1, -2, …):\n` +
|
|
@@ -53,6 +53,13 @@ export interface SiteConfig {
|
|
|
53
53
|
* files explicitly, e.g. ['src', 'README.md', 'Building-Apps.md'].
|
|
54
54
|
*/
|
|
55
55
|
docPaths?: string[];
|
|
56
|
+
/**
|
|
57
|
+
* The page served at the site root (`/`), by slug or filename, e.g. `home: 'host'` for a
|
|
58
|
+
* site that is an app. Default: README. README then has its own page at `/readme/`, and
|
|
59
|
+
* the nav, canonical links, sitemap, llms.txt and books follow. The build fails if it
|
|
60
|
+
* names no page. A doc can also claim the root itself with `{"home": true}`.
|
|
61
|
+
*/
|
|
62
|
+
home?: string;
|
|
56
63
|
/**
|
|
57
64
|
* Directories to skip while scanning `docPaths` — by basename (skipped wherever it
|
|
58
65
|
* appears) or by path (skipped only at that location). Added to the built-in
|
|
@@ -603,6 +610,24 @@ export interface SiteConfig {
|
|
|
603
610
|
* are confined to the repo root as correctness hygiene. Off by default; opt in.
|
|
604
611
|
*/
|
|
605
612
|
editableSources?: boolean;
|
|
613
|
+
/**
|
|
614
|
+
* Trust the local network for reading and editing source (#227). With `editableSources` on,
|
|
615
|
+
* a browser on another device on your LAN (a phone, a headset) can open and save source
|
|
616
|
+
* directly, as one at this keyboard can, instead of needing the tunnel and an invite link.
|
|
617
|
+
* That is "Edit page source"; a live example's "Save to source" is still offered only on
|
|
618
|
+
* `localhost`.
|
|
619
|
+
*
|
|
620
|
+
* Trusted: a direct peer at a private address (RFC 1918 IPv4, IPv6 unique-local or
|
|
621
|
+
* link-local). Never trusted: tunnel traffic (it needs a session as before) or a request
|
|
622
|
+
* carrying forwarding headers. Writes still have to come from the dev server's own origin,
|
|
623
|
+
* so a page another site serves cannot write through a LAN browser.
|
|
624
|
+
*
|
|
625
|
+
* What it means: ANYONE on the network can edit your working tree. Turn it on for a network
|
|
626
|
+
* you control, never on shared wifi. And a reverse proxy on your LAN that forwards outside
|
|
627
|
+
* traffic WITHOUT forwarding headers would make that traffic look like a LAN peer. Agent
|
|
628
|
+
* driving, invite links, builds and test reports stay this machine only. Off by default.
|
|
629
|
+
*/
|
|
630
|
+
trustLan?: boolean;
|
|
606
631
|
/**
|
|
607
632
|
* Give a coding agent (Claude) eyes + hands on your running dev page via
|
|
608
633
|
* [haltija](https://github.com/tonioloewald/haltija). When on, the dev server
|
package/dist/editable-rect.js
CHANGED
|
@@ -53,13 +53,11 @@ preview.addEventListener('change', event => console.log(event))
|
|
|
53
53
|
background: transparent;
|
|
54
54
|
box-shadow: none;
|
|
55
55
|
}
|
|
56
|
-
|
|
57
|
-
.previw button
|
|
58
56
|
```
|
|
59
57
|
|
|
60
58
|
## Snapping
|
|
61
59
|
|
|
62
|
-
When `EditableRect.snapToGrid === true` or the shift-key is
|
|
60
|
+
When `EditableRect.snapToGrid === true` or the shift-key is depressed, position will snap to `EditableRect.gridSize` pixels (default = 8).
|
|
63
61
|
|
|
64
62
|
Similarly `EditableRect.snapAngle === true` or the shift-key will snap rotation to increments of `EditableRect.angleSize` degrees (default = 15).
|
|
65
63
|
|
package/dist/highlight-block.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/*{ "parent": "Documentation System", "order": 70 }*/
|
|
1
2
|
/*#
|
|
2
3
|
# highlight-block
|
|
3
4
|
|
|
@@ -30,16 +31,8 @@ and an unknown language renders as plain, readable code rather than failing.
|
|
|
30
31
|
**Colours work out of the box, anywhere.** The element injects its token palette on first use,
|
|
31
32
|
so a `<tosi-highlight>` on an ordinary page is coloured without importing anything.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
call — and the palette used to live inside the doc-system stylesheet. So a standalone element
|
|
36
|
-
produced perfectly correct `<span class="token …">` markup rendering in flat black, and this
|
|
37
|
-
very paragraph told you to "import the doc-system CSS", which was not an import that existed.
|
|
38
|
-
|
|
39
|
-
There are **two palettes** — a light set (the default) and a dark set under `.darkmode` — each
|
|
40
|
-
contrast-checked against the `--code-bg` it actually sits on. Not one recomputed from the
|
|
41
|
-
other: a light-mode default derived from dark-mode literals is unreadable, which is how this
|
|
42
|
-
shipped failing WCAG AA on every token type.
|
|
34
|
+
There are **two palettes**, a light set (the default) and a dark set under `.darkmode`, each
|
|
35
|
+
contrast-checked against the `--code-bg` it sits on (see [highlight-styles](/highlight-styles/)).
|
|
43
36
|
|
|
44
37
|
Every colour is a `varDefault`, so retheme one token type without touching the rest:
|
|
45
38
|
|
package/dist/icon-rules.d.ts
CHANGED
|
@@ -3,6 +3,16 @@ export interface IconRule {
|
|
|
3
3
|
prefix: string | RegExp;
|
|
4
4
|
apply: (baseName: string, match: RegExpMatchArray | string, parts: ElementPart[]) => Element | string | null;
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* Style suffixes on a registered icon (`checkCircle90r`), split, or null. Such a name is
|
|
8
|
+
* that icon styled, and it wins over a composition rule (#3295): `checkCircle90r` is
|
|
9
|
+
* `checkCircle` turned, not the `check` rule over `circle90r`. Both the DOM renderer and
|
|
10
|
+
* `iconSvg` resolve in that order by asking this.
|
|
11
|
+
*/
|
|
12
|
+
export declare function suffixedIcon(name: string): {
|
|
13
|
+
baseName: string;
|
|
14
|
+
tokens: string[];
|
|
15
|
+
} | null;
|
|
6
16
|
/** A registered icon, or style suffixes on one (`chevronRight50o`). */
|
|
7
17
|
export declare function canResolve(name: string): boolean;
|
|
8
18
|
/**
|
package/dist/icon-rules.js
CHANGED
|
@@ -10,13 +10,21 @@ before the name is asked for.
|
|
|
10
10
|
*/
|
|
11
11
|
import iconData from './icon-data.js';
|
|
12
12
|
import { splitSuffixes } from './icon-suffixes.js';
|
|
13
|
+
/**
|
|
14
|
+
* Style suffixes on a registered icon (`checkCircle90r`), split, or null. Such a name is
|
|
15
|
+
* that icon styled, and it wins over a composition rule (#3295): `checkCircle90r` is
|
|
16
|
+
* `checkCircle` turned, not the `check` rule over `circle90r`. Both the DOM renderer and
|
|
17
|
+
* `iconSvg` resolve in that order by asking this.
|
|
18
|
+
*/
|
|
19
|
+
export function suffixedIcon(name) {
|
|
20
|
+
const split = splitSuffixes(name);
|
|
21
|
+
return split != null && iconData[split.baseName]
|
|
22
|
+
? split
|
|
23
|
+
: null;
|
|
24
|
+
}
|
|
13
25
|
/** A registered icon, or style suffixes on one (`chevronRight50o`). */
|
|
14
26
|
export function canResolve(name) {
|
|
15
|
-
|
|
16
|
-
if (data[name])
|
|
17
|
-
return true;
|
|
18
|
-
const split = splitSuffixes(name);
|
|
19
|
-
return split != null && !!data[split.baseName];
|
|
27
|
+
return (!!iconData[name] || suffixedIcon(name) != null);
|
|
20
28
|
}
|
|
21
29
|
/**
|
|
22
30
|
* `spinN<Icon>` animates, so it is drawn by the DOM renderer, which installs `apply` when
|
package/dist/icon-svg.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*{"parent":"
|
|
1
|
+
/*{ "parent": "icons", "order": 20 }*/
|
|
2
2
|
/*#
|
|
3
3
|
# `iconSvg` — icon markup without a DOM
|
|
4
4
|
|
|
@@ -27,21 +27,17 @@ other style suffix (`check50o`), a redirect to a name that is not an icon, or a
|
|
|
27
27
|
cycle. Render those with `icons.<name>()` where there is a DOM; the suffixes and rules are
|
|
28
28
|
described on the [[icons]] page. Only a name that nothing would draw returns `undefined`.
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
server-rendered template had no way to get markup except by parsing
|
|
38
|
-
`src/icons/data/*.ts` out of the installed package — which is exactly what this project
|
|
39
|
-
resorted to when putting its own logo on a generated page. There is nothing to protect:
|
|
40
|
-
the map ships in the bundle either way.
|
|
30
|
+
It needs no DOM, so it works in a build script, a static-page generator, an ePub pass or a
|
|
31
|
+
server-rendered template, where `icons.<name>()` (which builds DOM elements) cannot run.
|
|
32
|
+
*/
|
|
33
|
+
/*
|
|
34
|
+
Deliberately a separate module from `icons.ts`: that one imports tosijs to build elements, so
|
|
35
|
+
importing it drags in code that needs `HTMLElement` and throws in a build script, the exact
|
|
36
|
+
context that wanted the markup. This module imports nothing but the data and the grammars.
|
|
41
37
|
*/
|
|
42
38
|
import iconData from './icon-data.js';
|
|
43
|
-
import {
|
|
44
|
-
import { canResolve, ruleDraws } from './icon-rules.js';
|
|
39
|
+
import { suffixNumber } from './icon-suffixes.js';
|
|
40
|
+
import { canResolve, ruleDraws, suffixedIcon } from './icon-rules.js';
|
|
45
41
|
const data = iconData;
|
|
46
42
|
// Rotation and flip, the only suffixes the built-in redirects use (`chevronDown` is
|
|
47
43
|
// `chevronRight90r`). Anything else needs the DOM renderer.
|
|
@@ -77,11 +73,9 @@ function resolve(asked, name, seen) {
|
|
|
77
73
|
return resolve(asked, value, seen);
|
|
78
74
|
}
|
|
79
75
|
// The renderer's order for a name that is not registered: a stack, then suffixes on a
|
|
80
|
-
// registered icon (
|
|
81
|
-
const split = key.includes('$') ? null :
|
|
82
|
-
if (split &&
|
|
83
|
-
data[split.baseName] !== undefined &&
|
|
84
|
-
split.tokens.every(isGeometry)) {
|
|
76
|
+
// registered icon (suffixedIcon, #3295), then a rule.
|
|
77
|
+
const split = key.includes('$') ? null : suffixedIcon(key);
|
|
78
|
+
if (split && split.tokens.every(isGeometry)) {
|
|
85
79
|
return transformed(asked, resolve(asked, split.baseName, seen), split.tokens);
|
|
86
80
|
}
|
|
87
81
|
const what = key === stripEscape(asked) ? `"${key}"` : `it resolves to "${key}", which`;
|
package/dist/icons.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/*{ "parent": "Components" }*/
|
|
1
2
|
/*#
|
|
2
3
|
# icons
|
|
3
4
|
|
|
@@ -8,8 +9,8 @@
|
|
|
8
9
|
</div>
|
|
9
10
|
|
|
10
11
|
`icons` is a proxy that generates an `ElementCreator` for a given icon on demand,
|
|
11
|
-
e.g. `icons.chevronDown()` produces an `<svg>` element containing a
|
|
12
|
-
|
|
12
|
+
e.g. `icons.chevronDown()` produces an `<svg>` element (class `tosi-icon`) containing a
|
|
13
|
+
downward-pointing chevron.
|
|
13
14
|
|
|
14
15
|
Most of the built-in icons come from [Feather](https://feathericons.com). If you want more
|
|
15
16
|
icons in the same style, [lucide](https://lucide.dev) is a fork and expansion of Feather and a
|
|
@@ -200,12 +201,12 @@ test('filter input and prefix select exist', () => {
|
|
|
200
201
|
})
|
|
201
202
|
```
|
|
202
203
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
(
|
|
206
|
-
|
|
204
|
+
Icons take their colour from the text around them (`currentColor`). Override fill and stroke
|
|
205
|
+
with `--tosi-icon-fill` and `--tosi-icon-stroke`, or per icon with the `F` and `S` suffixes
|
|
206
|
+
(see [Icon modifier suffixes](#icon-modifier-suffixes)). The icon data is generated from a
|
|
207
|
+
directory of SVGs (see [Building icon data](#building-icon-data)).
|
|
207
208
|
|
|
208
|
-
|
|
209
|
+
## Need More Icons?
|
|
209
210
|
|
|
210
211
|
[lucide](https://lucide.dev) is a fork and expansion of feather that maintains the style and
|
|
211
212
|
adds a lot more options. Obviously it's completely compatible with this icon system and generously licensed. A
|
|
@@ -571,7 +572,7 @@ copies or substantial portions of the Software.
|
|
|
571
572
|
import { elements, svgElements, Color, vars, varDefault, withAttributes, } from 'tosijs';
|
|
572
573
|
import iconData from './icon-data.js';
|
|
573
574
|
import { splitSuffixes, suffixNumber } from './icon-suffixes.js';
|
|
574
|
-
import { iconRules, spinRule, matchRule, canResolve } from './icon-rules.js';
|
|
575
|
+
import { iconRules, spinRule, matchRule, canResolve, suffixedIcon, } from './icon-rules.js';
|
|
575
576
|
export { iconRules } from './icon-rules.js';
|
|
576
577
|
export const defineIcons = (newIcons) => {
|
|
577
578
|
const data = iconData;
|
|
@@ -901,14 +902,10 @@ export function resolveIcon(prop, parts) {
|
|
|
901
902
|
});
|
|
902
903
|
return wrapIcon(prop, parts, base, ...overlays);
|
|
903
904
|
}
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
is `checkCircle` turned, not the `check` rule over `circle90r`. A rule still applies
|
|
907
|
-
when its suffixes are on an icon that only the rule makes (`spin90Loader50o`).
|
|
908
|
-
*/
|
|
905
|
+
// Suffixes on a registered icon win over a rule (suffixedIcon, #3295). A rule still
|
|
906
|
+
// applies when its suffixes are on an icon only the rule makes (`spin90Loader50o`).
|
|
909
907
|
const parsed = parseStyleSuffixes(prop);
|
|
910
|
-
|
|
911
|
-
if (!native) {
|
|
908
|
+
if (!suffixedIcon(prop)) {
|
|
912
909
|
const composed = composeIcon(prop, parts);
|
|
913
910
|
if (composed)
|
|
914
911
|
return composed;
|