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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.16.22
|
|
4
|
+
|
|
5
|
+
Three site options asked for by adopters, and a review pass over the documentation.
|
|
6
|
+
|
|
7
|
+
- **`home`: choose the page served at `/`.** `defineSiteConfig({ home: 'host' })` (a slug or
|
|
8
|
+
filename) makes that page the site root, for a site that is an app. README then has its own
|
|
9
|
+
page at `/readme/`, and the nav, canonical links, sitemap, `llms.txt` and books follow. A
|
|
10
|
+
doc can claim the root itself with `{"home": true}` (or `home: true` in YAML frontmatter).
|
|
11
|
+
The build fails when `home` names no page or a hidden one, or when two docs claim the root.
|
|
12
|
+
**README's URL and canonical link change** when you turn it on; Doc-Site System says so. An
|
|
13
|
+
ePub takes its slugs from the whole site, so README keeps `/readme/` in a book even when the
|
|
14
|
+
home doc is not in that book. closes #225
|
|
15
|
+
- **`{"header": false}`: a page without the site header.** It removes the header bar on that
|
|
16
|
+
page, before hydration and after, and gives its space to the page. With
|
|
17
|
+
`layout: "full-screen"` the page has no site chrome at all, for an app page with its own
|
|
18
|
+
toolbar or an installed PWA. The page links back to the docs its own way. closes #226
|
|
19
|
+
- **YAML frontmatter reads `header: false` and `home: true`**, as the JSON metadata block does.
|
|
20
|
+
- **Doc-Site System documents `header: false` and `home`**, including that home moves
|
|
21
|
+
README's URL.
|
|
22
|
+
- **The build warns (fails under `strict`) on a link written as a bare page name.**
|
|
23
|
+
`[valueRenderer](value-renderer)` resolves under the page it is on and reaches nothing;
|
|
24
|
+
three shipped that way here.
|
|
25
|
+
- **`naturalCompare`, `naturalSorter` and `isBlank` are exported from the package root.**
|
|
26
|
+
The naturalCompare page imported `naturalCompare` from `'tosijs-ui'`, got `undefined`, and
|
|
27
|
+
showed the lexical sort it warns against.
|
|
28
|
+
- **The documentation site is regrouped.** A new Documentation System section holds every
|
|
29
|
+
page about the doc system (adopting, One Source, Doc-Site System, doc-system, doc-browser,
|
|
30
|
+
docs, highlight-block, highlight-styles, the layout demos, kitchen sink). Icons moves under
|
|
31
|
+
Components, with Icon Composition, iconSvg and make-icon-data beneath it; naturalCompare
|
|
32
|
+
joins Helper Libraries; Appendices keeps migrating and release-notes.
|
|
33
|
+
- **Documentation fixes.**
|
|
34
|
+
- The doc-browser and docs pages showed a CLI that does not exist, imports from the wrong
|
|
35
|
+
module and wrong option names. Both are rewritten against the real API.
|
|
36
|
+
- Three links (data-table → value-renderer, pocket-bar → icon-composition and popFloat)
|
|
37
|
+
reached nothing.
|
|
38
|
+
- Migrating: the ACE-era `theme`/`options` props warn and do nothing, rather than being
|
|
39
|
+
gone; the 1.11.0 edit-link note points to the 1.14.0 change; new notices for 1.16.17
|
|
40
|
+
(tjs-lang 0.14), 1.16.19 (`iconSvg` throws) and 1.16.21 (tosijs `^1.10.9`).
|
|
41
|
+
- Adopting and Doc-Site System: install `chokidar` with the build peers; display-only
|
|
42
|
+
fences use `:static`; a detached test block shows its results.
|
|
43
|
+
- Icons: the class added is `tosi-icon` (no `icon-chevron-down`); how icons are coloured
|
|
44
|
+
replaces the old icomoon paragraph.
|
|
45
|
+
- Live examples: `ide` mode is described as what it is today (it runs like `iframe`).
|
|
46
|
+
- Smaller: size-break's container-query intro, the iife size (~484KB gzip), code-editor's
|
|
47
|
+
mode list (`ajs`), a stray CSS line in editable-rect's example, the menu demo's link, and
|
|
48
|
+
typos across six pages.
|
|
49
|
+
- `dist/iife.js` grows by about 0.2KB gzip.
|
|
50
|
+
|
|
51
|
+
### Dev-server access: `trustLan` (opt-in)
|
|
52
|
+
|
|
53
|
+
Who may read and write source through the dev server changed, but only for a project that
|
|
54
|
+
sets the new option; with it unset the rules are exactly 1.16.21's.
|
|
55
|
+
|
|
56
|
+
- **`trustLan`: LAN devices may read and edit source.** With `editableSources` on, a direct
|
|
57
|
+
peer at a private address (RFC 1918, IPv6 unique-local or link-local) may read and save
|
|
58
|
+
source as one at the dev machine can, so a phone or headset on the LAN uses "Edit page
|
|
59
|
+
source" without the tunnel (a live example's "Save to source" is still offered only on
|
|
60
|
+
`localhost`). closes #227
|
|
61
|
+
- **What is never trusted:** tunnel traffic (it still needs a session), a request with
|
|
62
|
+
forwarding headers, and a write from any origin but the dev server's own. Agent driving,
|
|
63
|
+
invite links, builds and test reports stay this machine only; agent driving no longer
|
|
64
|
+
inherits the write rule's options, so LAN trust cannot widen it.
|
|
65
|
+
- **Anyone on the network can then edit your working tree**, which includes code that runs on
|
|
66
|
+
this machine: use it on a network you control. The dev server prints a warning at every
|
|
67
|
+
launch while it is on, and says so when it is set without `editableSources` (it then does
|
|
68
|
+
nothing).
|
|
69
|
+
- The edit and save alerts tell a LAN reader that invite links cannot help them, instead of
|
|
70
|
+
sending them round that loop.
|
|
71
|
+
|
|
72
|
+
|
|
3
73
|
## 1.16.21
|
|
4
74
|
|
|
5
75
|
- **Expanding or collapsing a table row's detail no longer corrects `scrollTop` itself.**
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
></tosi-lottie>
|
|
15
15
|
</center>
|
|
16
16
|
|
|
17
|
-
Copyright ©2023-
|
|
17
|
+
Copyright ©2023-2026 Tonio Loewald
|
|
18
18
|
|
|
19
19
|
> **Upgrading from an older version?** `<tosi-code>` changed editors in 1.7.0, and
|
|
20
20
|
> 1.3.0 renamed everything from `xinjs-ui`. Both are covered in
|
|
@@ -53,7 +53,7 @@ The `tosijs-ui` iife build bundles `tosijs`, `tosijs-ui`, `marked` and — as of
|
|
|
53
53
|
**CodeMirror** into a single minified javascript source file. You can access `xinjs` and
|
|
54
54
|
`xinjsui` as globals which contain all the things exported by `tosijs` and `tosijs-ui`.
|
|
55
55
|
|
|
56
|
-
> **Size (1.
|
|
56
|
+
> **Size (1.16.21):** ~484KB gzip, up from ~120KB in 1.6. Two things dominate it, and
|
|
57
57
|
> `bun build --format iife` cannot
|
|
58
58
|
> code-split, so `<tosi-code>`'s lazy CodeMirror import is flattened into this bundle
|
|
59
59
|
> whether or not the page uses an editor. **If you don't need `<tosi-code>`, use the ESM
|
package/bin/make-icon-data.js
CHANGED
|
@@ -86,7 +86,7 @@ byte-identical files, so it never shows up as spurious diff noise.
|
|
|
86
86
|
> your prettier config, which it now honours) — not the output.
|
|
87
87
|
*/
|
|
88
88
|
|
|
89
|
-
/*{ "parent": "
|
|
89
|
+
/*{ "parent": "icons", "order": 30 }*/
|
|
90
90
|
|
|
91
91
|
import fs from 'fs'
|
|
92
92
|
import path from 'path'
|
package/dist/code-editor.js
CHANGED
|
@@ -6,7 +6,7 @@ A [CodeMirror 6](https://codemirror.net/) wrapper.
|
|
|
6
6
|
Sometimes, it's nice to be able to just toss a code-editor in a web-page.
|
|
7
7
|
|
|
8
8
|
`<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the
|
|
9
|
-
language (`javascript`, `typescript`, `tjs`, `css`, `html`, `markdown`).
|
|
9
|
+
language (`javascript`, `typescript`, `tjs`, `ajs`, `css`, `html`, `markdown`).
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
12
|
<tosi-code style="width: 100%; height: 100%" mode="css">
|
|
@@ -69,8 +69,7 @@ CodeMirror is a **lazy chunk**: with a bundler (ESM), a page that never uses
|
|
|
69
69
|
`<tosi-code>` doesn't load it. **This is not true of the IIFE** (`dist/iife.js`) —
|
|
70
70
|
bun's IIFE format cannot code-split, so CodeMirror is inlined there. That is a
|
|
71
71
|
deliberate trade: the doc-system's editor (and its save-to-source flow) is the
|
|
72
|
-
point of the IIFE, so it carries the editor
|
|
73
|
-
~118KB in 1.6.x.
|
|
72
|
+
point of the IIFE, so it carries the editor; see the README for what the IIFE weighs.
|
|
74
73
|
|
|
75
74
|
## Migrating from the ACE editor (pre-1.7)
|
|
76
75
|
|
package/dist/data-table.js
CHANGED
|
@@ -205,7 +205,7 @@ export interface ColumnOptions {
|
|
|
205
205
|
## Column value types
|
|
206
206
|
|
|
207
207
|
Give a column a `type` and it's formatted (and aligned) automatically — no hand-rolled
|
|
208
|
-
`dataCell`. The `type` is a [`valueRenderer`](value-renderer) string:
|
|
208
|
+
`dataCell`. The `type` is a [`valueRenderer`](/value-renderer/) string:
|
|
209
209
|
|
|
210
210
|
```
|
|
211
211
|
[
|
package/dist/doc-browser.d.ts
CHANGED
|
@@ -38,6 +38,10 @@ export interface Doc {
|
|
|
38
38
|
path: string;
|
|
39
39
|
pin?: string;
|
|
40
40
|
hidden?: boolean;
|
|
41
|
+
/** this doc is the site root instead of README (the site's `home` option) */
|
|
42
|
+
home?: boolean;
|
|
43
|
+
/** `false` drops the site header on this page (see the site `Doc.header`) */
|
|
44
|
+
header?: boolean;
|
|
41
45
|
/** `'full-width'` drops the reading measure; `'full-screen'` also puts the nav away. */
|
|
42
46
|
layout?: 'full-width' | 'full-screen';
|
|
43
47
|
testStatus?: 'passed' | 'failed' | 'pending';
|
package/dist/doc-browser.js
CHANGED
|
@@ -1,26 +1,34 @@
|
|
|
1
1
|
/*#
|
|
2
2
|
# doc-browser
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
to set up and maintain.
|
|
4
|
+
`createDocBrowser()` renders a documentation corpus as a browsable, searchable testbed with
|
|
5
|
+
live examples, like the [tosijs-ui documentation site](https://ui.tosijs.net). It's like
|
|
6
|
+
Storybook but much simpler to set up and maintain.
|
|
7
|
+
|
|
8
|
+
For a whole documentation site (static, pre-rendered pages, SEO, ePub, a dev server), use
|
|
9
|
+
[`tosijs-ui/site`](/adopting-the-doc-system/) instead; it builds on this component. Reach for
|
|
10
|
+
`createDocBrowser` directly to embed a doc browser in an app of your own.
|
|
7
11
|
|
|
8
12
|
## Quick Start
|
|
9
13
|
|
|
10
|
-
### 1. Extract
|
|
14
|
+
### 1. Extract the documentation
|
|
11
15
|
|
|
12
|
-
|
|
16
|
+
Run once at build time (it reads files, so it runs under bun or node, not in the browser):
|
|
13
17
|
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
```
|
|
18
|
+
```typescript
|
|
19
|
+
import { extractDocs } from 'tosijs-ui/site'
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
extractDocs({
|
|
22
|
+
paths: ['src', 'README.md'],
|
|
23
|
+
output: 'docs.json',
|
|
24
|
+
})
|
|
25
|
+
```
|
|
19
26
|
|
|
20
|
-
|
|
21
|
-
|
|
27
|
+
This collects every `.md` file and every `/*#` comment block in your sources. The format,
|
|
28
|
+
metadata and which code fences run are described under
|
|
29
|
+
[Doc format](/doc-site-system/#doc-format).
|
|
22
30
|
|
|
23
|
-
### 2. Create
|
|
31
|
+
### 2. Create your doc browser
|
|
24
32
|
|
|
25
33
|
```typescript
|
|
26
34
|
import { createDocBrowser } from 'tosijs-ui/doc-browser'
|
|
@@ -40,20 +48,16 @@ const browser = createDocBrowser({
|
|
|
40
48
|
document.body.append(browser)
|
|
41
49
|
```
|
|
42
50
|
|
|
43
|
-
### 3. Add
|
|
51
|
+
### 3. Add live examples to your docs
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
example.
|
|
53
|
+
Consecutive `html`, `css`, `js` (or `ts`, `tjs`) and `test` code fences become one live,
|
|
54
|
+
editable example. `context` supplies the modules those examples import:
|
|
48
55
|
|
|
49
56
|
/*#
|
|
50
57
|
# My Component
|
|
51
58
|
|
|
52
59
|
This component does amazing things!
|
|
53
60
|
|
|
54
|
-
```html
|
|
55
|
-
<my-component></my-component>
|
|
56
|
-
```
|
|
57
61
|
```js
|
|
58
62
|
import { myComponent } from 'mylib'
|
|
59
63
|
preview.append(myComponent({ value: 'Hello!' }))
|
|
@@ -65,94 +69,33 @@ example.
|
|
|
65
69
|
```
|
|
66
70
|
*/
|
|
67
71
|
|
|
68
|
-
export class MyComponent extends Component {
|
|
69
|
-
// ...
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export const myComponent = MyComponent.elementCreator({
|
|
73
|
-
tag: 'my-component'
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
## Documentation Format
|
|
77
|
-
|
|
78
|
-
### Inline Comments
|
|
79
|
-
|
|
80
|
-
Start multi-line comments with `/*#` to mark them as documentation:
|
|
81
|
-
|
|
82
|
-
```typescript
|
|
83
|
-
/*#
|
|
84
|
-
# Component Name
|
|
85
|
-
|
|
86
|
-
Description and examples go here...
|
|
87
|
-
*/
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Metadata
|
|
91
|
-
|
|
92
|
-
Control sort order with JSON metadata:
|
|
93
|
-
|
|
94
|
-
```
|
|
95
|
-
<!--{ "pin": "bottom" }-->
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
or
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
/*{ "pin": "bottom" }*/
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
## Programmatic API
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
import { extractDocs, saveDocsJSON } from 'tosijs-ui'
|
|
108
|
-
|
|
109
|
-
const docs = extractDocs({
|
|
110
|
-
dirs: ['src', 'README.md'],
|
|
111
|
-
ignore: ['node_modules', 'dist'],
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
saveDocsJSON(docs, './docs.json')
|
|
115
|
-
|
|
116
|
-
// Or use the docs directly
|
|
117
|
-
import { createDocBrowser } from 'tosijs-ui/doc-browser'
|
|
118
|
-
const browser = createDocBrowser({ docs, context: { mylib } })
|
|
119
|
-
```
|
|
120
|
-
|
|
121
72
|
## createDocBrowser Options
|
|
122
73
|
|
|
123
74
|
```typescript
|
|
124
75
|
interface DocBrowserOptions {
|
|
125
|
-
docs: Doc[] //
|
|
126
|
-
context?: Record<string, any> //
|
|
127
|
-
projectName?: string //
|
|
128
|
-
projectLinks?: ProjectLinks //
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
blog?: string
|
|
138
|
-
tosijs?: string
|
|
139
|
-
bundle?: string
|
|
140
|
-
cdn?: string
|
|
141
|
-
[key: string]: string | undefined
|
|
76
|
+
docs: Doc[] // the extracted corpus
|
|
77
|
+
context?: Record<string, any> // modules live examples can import
|
|
78
|
+
projectName?: string // display name
|
|
79
|
+
projectLinks?: ProjectLinks // links in the header
|
|
80
|
+
logo?: string // header logo
|
|
81
|
+
navSize?: number // nav width (default: 200)
|
|
82
|
+
minSize?: number // width below which the nav goes compact (default: 600)
|
|
83
|
+
routing?: 'query' | 'path' | 'memory' // see below (default: 'query')
|
|
84
|
+
initialRoute?: string // memory routing: the first page
|
|
85
|
+
onRouteChange?: (slug: string) => void // memory routing: page changes
|
|
86
|
+
navbarLinks?: LinkItem[] // icon links in the header bar
|
|
87
|
+
contentElement?: HTMLElement // adopt this element as the content area
|
|
142
88
|
}
|
|
143
89
|
```
|
|
144
90
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
- `/tosijs-site.config.ts` - How this repo configures its doc site (`defineSiteConfig` / `tosijs-ui/site`)
|
|
150
|
-
- `/src/doc-system/site/docs.ts` - The extraction tool (`extractDocs`, exported from `tosijs-ui/site`)
|
|
151
|
-
- `/src/doc-browser.ts` - The createDocBrowser implementation
|
|
91
|
+
`routing` picks how pages map to URLs. `'query'` links pages as `?filename`. `'path'` uses
|
|
92
|
+
clean `/slug/` URLs, as the static site does. `'memory'` never touches the page's URL or
|
|
93
|
+
history, for a browser embedded in another page: drive it with `initialRoute`,
|
|
94
|
+
`onRouteChange` and the element's `navigate(slug)` method.
|
|
152
95
|
*/
|
|
153
|
-
/*{"
|
|
96
|
+
/*{ "parent": "Documentation System", "order": 50 }*/
|
|
154
97
|
import { elements, vars, varDefault, bindings, touch, getListItem, debounce, tosi, StyleSheet, } from 'tosijs';
|
|
155
|
-
import { buildSlugMap, pathForSlug, filenameForPath, } from './doc-system/routing.js';
|
|
98
|
+
import { buildSlugMap, pathForSlug, filenameForPath, homeDoc, } from './doc-system/routing.js';
|
|
156
99
|
import { buildNavTree } from './doc-system/nav-tree.js';
|
|
157
100
|
import { renderDocMarkdown } from './doc-system/render.js';
|
|
158
101
|
import { pageTitle } from './doc-system/doc-title.js';
|
|
@@ -476,7 +419,7 @@ export function createDocBrowser(options) {
|
|
|
476
419
|
}
|
|
477
420
|
return null;
|
|
478
421
|
};
|
|
479
|
-
const docName = filenameFromLocation() || docs[0]?.filename || 'README.md';
|
|
422
|
+
const docName = filenameFromLocation() || homeDoc(docs) || docs[0]?.filename || 'README.md';
|
|
480
423
|
const currentDoc = docs.find((doc) => doc.filename === docName) || docs[0];
|
|
481
424
|
// tosi() registers its top-level keys in a GLOBAL observable registry, so two
|
|
482
425
|
// doc browsers on one page (e.g. a page whose live example embeds a nested
|
|
@@ -1053,6 +996,7 @@ export function createDocBrowser(options) {
|
|
|
1053
996
|
layoutHost.setAttribute('data-layout', doc.layout);
|
|
1054
997
|
else
|
|
1055
998
|
layoutHost.removeAttribute('data-layout');
|
|
999
|
+
layoutHost.toggleAttribute('data-no-header', doc.header === false);
|
|
1056
1000
|
}
|
|
1057
1001
|
/*
|
|
1058
1002
|
`full-screen` also collapses the nav, and that has to go through the COMPONENT rather than
|
|
@@ -1347,7 +1291,7 @@ export function createDocBrowser(options) {
|
|
|
1347
1291
|
window.alert('This workspace needs an invite link before it can save.\n\n' +
|
|
1348
1292
|
'Run `tosijs-tunnel --link` on the machine hosting it, open the link once, ' +
|
|
1349
1293
|
'then try again. Your edit is still here.\n\n' +
|
|
1350
|
-
'If you opened this over the LAN (a `.local` address),
|
|
1294
|
+
'If you opened this over the LAN (a `.local` or local IP address), invite links will not help: set `trustLan: true` in the site config, or use its tunnel URL instead.');
|
|
1351
1295
|
}
|
|
1352
1296
|
else {
|
|
1353
1297
|
// Genuinely no write endpoint (a deployed static site) — hand the file back.
|
|
@@ -1361,7 +1305,8 @@ export function createDocBrowser(options) {
|
|
|
1361
1305
|
if (content === null) {
|
|
1362
1306
|
window.alert(sourceUnauthorized
|
|
1363
1307
|
? 'This workspace needs an invite link before you can edit.\n\n' +
|
|
1364
|
-
'Run `tosijs-tunnel --link` on the machine hosting it and open the link once
|
|
1308
|
+
'Run `tosijs-tunnel --link` on the machine hosting it and open the link once.\n\n' +
|
|
1309
|
+
'If you opened this over the LAN (a `.local` or local IP address), invite links will not help: set `trustLan: true` in the site config, or use its tunnel URL instead.'
|
|
1365
1310
|
: `Could not load source for ${doc.path}`);
|
|
1366
1311
|
return;
|
|
1367
1312
|
}
|
|
@@ -159,6 +159,17 @@ export function docSystemStyleSpec(theme = {}) {
|
|
|
159
159
|
'tosi-doc-system[data-layout="full-screen"] > .doc-nav.doc-nav, tosi-doc-system[data-layout="full-screen"] > .doc-navbar.doc-navbar': {
|
|
160
160
|
display: 'none',
|
|
161
161
|
},
|
|
162
|
+
/*
|
|
163
|
+
`header: false` — no header bar on this page (#226). One variable takes the bar's space
|
|
164
|
+
away everywhere it is reserved (the pre-hydration inset, the nav's top, the hydrated
|
|
165
|
+
column), and the bar itself is hidden before hydration (`::before`) and after (`header`).
|
|
166
|
+
*/
|
|
167
|
+
'tosi-doc-system[data-no-header]': {
|
|
168
|
+
_headerHeight: '0px',
|
|
169
|
+
},
|
|
170
|
+
'tosi-doc-system[data-no-header]::before, tosi-doc-system[data-no-header] header': {
|
|
171
|
+
display: 'none',
|
|
172
|
+
},
|
|
162
173
|
'.darkmode': {
|
|
163
174
|
...invertLuminance(colors),
|
|
164
175
|
/*
|
|
@@ -55,7 +55,7 @@ A self-contained, controllable embed (e.g. docs in a floating panel):
|
|
|
55
55
|
</tosi-doc-system>
|
|
56
56
|
```
|
|
57
57
|
*/
|
|
58
|
-
/*{ "parent": "
|
|
58
|
+
/*{ "parent": "Documentation System", "order": 40 }*/
|
|
59
59
|
import { StyleSheet, elements, tosi, vars, withAttributes, } from 'tosijs';
|
|
60
60
|
import * as tosijsModule from 'tosijs';
|
|
61
61
|
import { createDocBrowser, } from '../doc-browser.js';
|
|
@@ -1,17 +1,11 @@
|
|
|
1
|
+
/*{ "parent": "Documentation System", "order": 80 }*/
|
|
1
2
|
/*#
|
|
2
3
|
# highlight-styles
|
|
3
4
|
|
|
4
|
-
The syntax-highlighting token palette, on its own.
|
|
5
|
+
The syntax-highlighting token palette, on its own. The package ships no CSS files (every
|
|
6
|
+
style is a `StyleSheet()` call), so this is how token colours reach a page.
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
files at all** — every style is a `StyleSheet()` call — so before this existed, the only way
|
|
8
|
-
to get token colours was to import the whole doc-site chrome. A standalone
|
|
9
|
-
[`<tosi-highlight>`](/highlight-block/) on an ordinary page therefore produced perfectly
|
|
10
|
-
correct `<span class="token …">` markup rendering in flat black, which is the one thing that
|
|
11
|
-
component is for. The doc block even told you to "import the doc-system CSS", naming an
|
|
12
|
-
import that did not exist.
|
|
13
|
-
|
|
14
|
-
`<tosi-highlight>` now injects this itself on first use, so the standalone case needs nothing.
|
|
8
|
+
[`<tosi-highlight>`](/highlight-block/) injects it on first use, so that needs nothing.
|
|
15
9
|
Import it directly only if you are highlighting markup you produced some other way:
|
|
16
10
|
|
|
17
11
|
```typescript
|
|
@@ -27,14 +21,15 @@ by setting its variable:
|
|
|
27
21
|
:root { --token-keyword: rebeccapurple; }
|
|
28
22
|
```
|
|
29
23
|
|
|
30
|
-
Two palettes
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
24
|
+
Two palettes: a light default and a dark set under `.darkmode`, each meeting WCAG AA
|
|
25
|
+
(4.5:1) against its own background. Light on `#fdfdfd` is at worst **5.32:1** (comment); dark
|
|
26
|
+
on `#020202` at worst **6.01:1** (punctuation). Tests check both.
|
|
27
|
+
*/
|
|
28
|
+
/*
|
|
36
29
|
Dark uses DESCENDANT selectors rather than a nested `.darkmode` block, because the doc-system
|
|
37
30
|
spec already has one and a duplicate key loses silently to the last writer instead of merging.
|
|
31
|
+
Deriving the light palette from the dark literals is how this once shipped failing WCAG AA on
|
|
32
|
+
every token type; doc-system-styles.test.ts asserts both palettes.
|
|
38
33
|
*/
|
|
39
34
|
import { StyleSheet, varDefault } from 'tosijs';
|
|
40
35
|
/** The `.token.*` rules — light default plus a dark set under `.darkmode`. */
|
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
export interface DocLike {
|
|
2
2
|
filename: string;
|
|
3
|
+
/** this doc is the site root (`/`) instead of README; see `homeDoc` */
|
|
4
|
+
home?: boolean;
|
|
3
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The doc served at the site root: the one doc marked `home`, or README when none is.
|
|
8
|
+
* Marked by the build from the site's `home` option, or by a doc's own `{"home": true}`.
|
|
9
|
+
* The build refuses a corpus with more than one, so a second mark is ignored here rather
|
|
10
|
+
* than giving two docs the root.
|
|
11
|
+
*/
|
|
12
|
+
export declare function homeDoc(docs: DocLike[]): string | null;
|
|
4
13
|
/**
|
|
5
14
|
* Build a deterministic { filename -> slug } map for a set of docs.
|
|
6
15
|
*
|
|
@@ -11,10 +11,25 @@ function here is a pure, order-independent function of the docs array, so the tw
|
|
|
11
11
|
sides always produce identical slugs.
|
|
12
12
|
*/
|
|
13
13
|
const README = /^readme\.md$/i;
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/**
|
|
15
|
+
* The doc served at the site root: the one doc marked `home`, or README when none is.
|
|
16
|
+
* Marked by the build from the site's `home` option, or by a doc's own `{"home": true}`.
|
|
17
|
+
* The build refuses a corpus with more than one, so a second mark is ignored here rather
|
|
18
|
+
* than giving two docs the root.
|
|
19
|
+
*/
|
|
20
|
+
export function homeDoc(docs) {
|
|
21
|
+
const marked = docs.filter((doc) => doc.home);
|
|
22
|
+
return marked.length === 1 ? marked[0].filename : null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* filename without its final extension. The home doc maps to '' (the site root); README
|
|
26
|
+
* does too unless another doc is home, and is then `readme`.
|
|
27
|
+
*/
|
|
28
|
+
function baseSlug(filename, home) {
|
|
29
|
+
if (filename === home)
|
|
17
30
|
return '';
|
|
31
|
+
if (README.test(filename))
|
|
32
|
+
return home == null ? '' : 'readme';
|
|
18
33
|
return filename.replace(/\.[^.]+$/, '');
|
|
19
34
|
}
|
|
20
35
|
/**
|
|
@@ -26,15 +41,16 @@ function baseSlug(filename) {
|
|
|
26
41
|
* on array order.
|
|
27
42
|
*/
|
|
28
43
|
export function buildSlugMap(docs) {
|
|
44
|
+
const home = homeDoc(docs);
|
|
29
45
|
const counts = {};
|
|
30
46
|
for (const doc of docs) {
|
|
31
|
-
const base = baseSlug(doc.filename);
|
|
47
|
+
const base = baseSlug(doc.filename, home);
|
|
32
48
|
counts[base] = (counts[base] || 0) + 1;
|
|
33
49
|
}
|
|
34
50
|
const map = {};
|
|
35
51
|
for (const doc of docs) {
|
|
36
|
-
const base = baseSlug(doc.filename);
|
|
37
|
-
//
|
|
52
|
+
const base = baseSlug(doc.filename, home);
|
|
53
|
+
// The root ('' base) is unique by construction; never disambiguate it.
|
|
38
54
|
map[doc.filename] =
|
|
39
55
|
base !== '' && counts[base] > 1 ? doc.filename.replace(/\./g, '-') : base;
|
|
40
56
|
}
|
|
@@ -22,3 +22,13 @@ export declare function liveFenceLanguages(docs: Array<{
|
|
|
22
22
|
text?: string;
|
|
23
23
|
}>, policy?: ExamplePolicy): Set<string>;
|
|
24
24
|
export declare function deadInPageAnchors(html: string): string[];
|
|
25
|
+
/**
|
|
26
|
+
* Links in a RENDERED page written as a bare page name (`[valueRenderer](value-renderer)`).
|
|
27
|
+
*
|
|
28
|
+
* Every page is served at `/slug/`, so a relative `value-renderer` resolves to
|
|
29
|
+
* `/this-page/value-renderer`, which is never a page: the link is broken with or without
|
|
30
|
+
* script. It is the natural thing to write and reads correctly in the source, which is how
|
|
31
|
+
* three of them shipped. Only a name with no `.` and no `/` is reported, so a relative asset
|
|
32
|
+
* (`diagram.svg`, `img/x.png`) is left alone. The fix is the root-relative `/value-renderer/`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function bareNameLinks(html: string): string[];
|
|
@@ -104,3 +104,27 @@ export function deadInPageAnchors(html) {
|
|
|
104
104
|
}
|
|
105
105
|
return [...dead];
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Links in a RENDERED page written as a bare page name (`[valueRenderer](value-renderer)`).
|
|
109
|
+
*
|
|
110
|
+
* Every page is served at `/slug/`, so a relative `value-renderer` resolves to
|
|
111
|
+
* `/this-page/value-renderer`, which is never a page: the link is broken with or without
|
|
112
|
+
* script. It is the natural thing to write and reads correctly in the source, which is how
|
|
113
|
+
* three of them shipped. Only a name with no `.` and no `/` is reported, so a relative asset
|
|
114
|
+
* (`diagram.svg`, `img/x.png`) is left alone. The fix is the root-relative `/value-renderer/`.
|
|
115
|
+
*/
|
|
116
|
+
export function bareNameLinks(html) {
|
|
117
|
+
const found = new Set();
|
|
118
|
+
for (const tag of html.matchAll(START_TAG)) {
|
|
119
|
+
if (tag[1].toLowerCase() !== 'a')
|
|
120
|
+
continue;
|
|
121
|
+
for (const attr of tag[2].matchAll(ATTRIBUTE)) {
|
|
122
|
+
if (attr[1].toLowerCase() !== 'href')
|
|
123
|
+
continue;
|
|
124
|
+
const value = unescapeAttr(attr[2] ?? attr[3] ?? attr[4] ?? '');
|
|
125
|
+
if (/^[A-Za-z0-9_-]+\/?$/.test(value))
|
|
126
|
+
found.add(value);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return [...found];
|
|
130
|
+
}
|
|
@@ -287,6 +287,13 @@ export declare function mayWriteSource(opts: {
|
|
|
287
287
|
* never be the permissive-by-accident case for anything but the status quo.
|
|
288
288
|
*/
|
|
289
289
|
sessionMayWrite?: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* The project opted in to trusting its LAN (`trustLan`, #227): a direct, unproxied peer at a
|
|
292
|
+
* private address counts as this machine. Off unless the caller says so.
|
|
293
|
+
*/
|
|
294
|
+
trustLan?: boolean;
|
|
295
|
+
/** did the request carry forwarding headers? A proxied request is never LAN trust */
|
|
296
|
+
proxied?: boolean;
|
|
290
297
|
}): boolean;
|
|
291
298
|
/**
|
|
292
299
|
* May the holder of this session token write?
|
|
@@ -329,6 +336,12 @@ export declare function mayDriveWithAgent(opts: {
|
|
|
329
336
|
* dual-stack listener, and missing it would lock out the local machine on some setups.
|
|
330
337
|
*/
|
|
331
338
|
export declare function isLoopbackAddressForAuth(address: string | undefined | null): boolean;
|
|
339
|
+
/**
|
|
340
|
+
* A private-network peer: RFC 1918 IPv4 (10/8, 172.16/12, 192.168/16), IPv6 unique-local
|
|
341
|
+
* (fc00::/7) or link-local (fe80::/10), including IPv4 mapped into IPv6. Loopback is not
|
|
342
|
+
* included; ask `isLoopbackAddressForAuth` for that. Used ONLY behind the `trustLan` opt-in.
|
|
343
|
+
*/
|
|
344
|
+
export declare function isPrivateNetworkAddress(address: string | undefined | null): boolean;
|
|
332
345
|
export declare function isProxiedRequest(headers: {
|
|
333
346
|
get(name: string): string | null;
|
|
334
347
|
}): boolean;
|
|
@@ -646,7 +646,17 @@ export function isSameOriginRequest(request) {
|
|
|
646
646
|
export function mayWriteSource(opts) {
|
|
647
647
|
if (opts.viaTunnel)
|
|
648
648
|
return opts.hasValidSession && opts.sessionMayWrite !== false;
|
|
649
|
-
|
|
649
|
+
if (isLoopbackAddressForAuth(opts.peer))
|
|
650
|
+
return true;
|
|
651
|
+
/*
|
|
652
|
+
LAN trust leans on the same-origin check the caller applies (`isSameOriginRequest`), and that
|
|
653
|
+
check rules out DNS rebinding from a LAN browser only because the dev server is HTTPS-only,
|
|
654
|
+
with a certificate for localhost and <host>.local: a rebound hostname fails TLS. A plain-HTTP
|
|
655
|
+
listener would turn this into a rebinding path. Do not add one without revisiting this.
|
|
656
|
+
*/
|
|
657
|
+
return (opts.trustLan === true &&
|
|
658
|
+
opts.proxied !== true &&
|
|
659
|
+
isPrivateNetworkAddress(opts.peer));
|
|
650
660
|
}
|
|
651
661
|
/**
|
|
652
662
|
* May the holder of this session token write?
|
|
@@ -682,7 +692,10 @@ export function cookieMayWrite(state, cookieValue) {
|
|
|
682
692
|
* diverge, this is the one place to change, and every caller already says which it means.
|
|
683
693
|
*/
|
|
684
694
|
export function mayDriveWithAgent(opts) {
|
|
685
|
-
|
|
695
|
+
// LAN trust (#227) is for reading and editing source, and is never passed here: driving a
|
|
696
|
+
// page with an agent stays this machine and authorized tunnel sessions only.
|
|
697
|
+
const { viaTunnel, peer, hasValidSession } = opts;
|
|
698
|
+
return mayWriteSource({ viaTunnel, peer, hasValidSession });
|
|
686
699
|
}
|
|
687
700
|
/**
|
|
688
701
|
* THE loopback test. dev-server re-exports this as `isLoopbackAddress`; it used to
|
|
@@ -703,6 +716,31 @@ export function isLoopbackAddressForAuth(address) {
|
|
|
703
716
|
const mapped = a.startsWith('::ffff:') ? a.slice(7) : a;
|
|
704
717
|
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(mapped);
|
|
705
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* A private-network peer: RFC 1918 IPv4 (10/8, 172.16/12, 192.168/16), IPv6 unique-local
|
|
721
|
+
* (fc00::/7) or link-local (fe80::/10), including IPv4 mapped into IPv6. Loopback is not
|
|
722
|
+
* included; ask `isLoopbackAddressForAuth` for that. Used ONLY behind the `trustLan` opt-in.
|
|
723
|
+
*/
|
|
724
|
+
export function isPrivateNetworkAddress(address) {
|
|
725
|
+
if (!address)
|
|
726
|
+
return false;
|
|
727
|
+
const a = address
|
|
728
|
+
.trim()
|
|
729
|
+
.toLowerCase()
|
|
730
|
+
.replace(/^\[|\]$/g, '')
|
|
731
|
+
.replace(/%.*$/, ''); // an IPv6 zone id (fe80::1%en0)
|
|
732
|
+
const v4 = (a.startsWith('::ffff:') ? a.slice(7) : a).match(/^(\d{1,3})\.(\d{1,3})\.\d{1,3}\.\d{1,3}$/);
|
|
733
|
+
if (v4) {
|
|
734
|
+
const [first, second] = [Number(v4[1]), Number(v4[2])];
|
|
735
|
+
return (first === 10 ||
|
|
736
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
737
|
+
(first === 192 && second === 168));
|
|
738
|
+
}
|
|
739
|
+
if (!a.includes(':'))
|
|
740
|
+
return false;
|
|
741
|
+
const head = parseInt(a.split(':')[0] || '0', 16);
|
|
742
|
+
return (head & 0xfe00) === 0xfc00 || (head & 0xffc0) === 0xfe80;
|
|
743
|
+
}
|
|
706
744
|
/*
|
|
707
745
|
Was this request proxied (i.e. did it arrive through something in front of us)?
|
|
708
746
|
|