@invisibleloop/pulse 0.5.0 → 0.6.0
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/CLAUDE.md +36 -0
- package/docs/.claude/pulse-checklist.md +7 -5
- package/docs/public/.pulse-ui-version +1 -1
- package/docs/public/pulse-ui.css +27 -0
- package/docs/server.js +1 -0
- package/docs/src/lib/highlight.js +2 -299
- package/docs/src/lib/nav.js +1 -0
- package/docs/src/pages/accessibility.js +0 -1
- package/docs/src/pages/auth.js +0 -2
- package/docs/src/pages/caching.js +0 -1
- package/docs/src/pages/components/accordion.js +0 -1
- package/docs/src/pages/components/alert.js +0 -1
- package/docs/src/pages/components/app-badge.js +0 -1
- package/docs/src/pages/components/avatar.js +0 -1
- package/docs/src/pages/components/badge.js +0 -1
- package/docs/src/pages/components/banner.js +0 -1
- package/docs/src/pages/components/breadcrumbs.js +0 -1
- package/docs/src/pages/components/button.js +0 -1
- package/docs/src/pages/components/card.js +0 -1
- package/docs/src/pages/components/carousel.js +0 -1
- package/docs/src/pages/components/charts.js +0 -1
- package/docs/src/pages/components/checkbox.js +0 -1
- package/docs/src/pages/components/cluster.js +0 -1
- package/docs/src/pages/components/code-window.js +0 -1
- package/docs/src/pages/components/container.js +0 -1
- package/docs/src/pages/components/divider.js +0 -1
- package/docs/src/pages/components/empty.js +0 -1
- package/docs/src/pages/components/feature.js +0 -1
- package/docs/src/pages/components/fieldset.js +0 -1
- package/docs/src/pages/components/footer.js +0 -1
- package/docs/src/pages/components/grid.js +0 -1
- package/docs/src/pages/components/heading.js +0 -1
- package/docs/src/pages/components/icons.js +0 -1
- package/docs/src/pages/components/image.js +0 -1
- package/docs/src/pages/components/input.js +0 -1
- package/docs/src/pages/components/list.js +0 -1
- package/docs/src/pages/components/media.js +0 -1
- package/docs/src/pages/components/modal.js +0 -1
- package/docs/src/pages/components/nav.js +0 -1
- package/docs/src/pages/components/pricing.js +0 -1
- package/docs/src/pages/components/progress.js +0 -1
- package/docs/src/pages/components/prose.js +2 -3
- package/docs/src/pages/components/pullquote.js +0 -1
- package/docs/src/pages/components/radio.js +0 -1
- package/docs/src/pages/components/rating.js +0 -1
- package/docs/src/pages/components/search.js +0 -1
- package/docs/src/pages/components/section.js +0 -1
- package/docs/src/pages/components/segmented.js +0 -1
- package/docs/src/pages/components/select.js +0 -1
- package/docs/src/pages/components/spinner.js +0 -1
- package/docs/src/pages/components/stack.js +0 -1
- package/docs/src/pages/components/stat.js +0 -1
- package/docs/src/pages/components/stepper.js +0 -1
- package/docs/src/pages/components/table.js +0 -1
- package/docs/src/pages/components/testimonial.js +0 -1
- package/docs/src/pages/components/textarea.js +0 -1
- package/docs/src/pages/components/timeline.js +0 -1
- package/docs/src/pages/components/toggle.js +0 -1
- package/docs/src/pages/components/tooltip.js +0 -1
- package/docs/src/pages/components.js +0 -1
- package/docs/src/pages/config.js +0 -1
- package/docs/src/pages/deploy.js +0 -1
- package/docs/src/pages/extending.js +0 -1
- package/docs/src/pages/faq.js +0 -1
- package/docs/src/pages/getting-started.js +0 -1
- package/docs/src/pages/guard.js +0 -3
- package/docs/src/pages/how-it-works.js +0 -1
- package/docs/src/pages/images.js +0 -2
- package/docs/src/pages/markdown.js +155 -0
- package/docs/src/pages/meta.js +0 -2
- package/docs/src/pages/navigation.js +0 -1
- package/docs/src/pages/performance.js +0 -2
- package/docs/src/pages/project-structure.js +0 -2
- package/docs/src/pages/prompt-examples.js +0 -1
- package/docs/src/pages/raw-responses.js +0 -4
- package/docs/src/pages/routing.js +0 -2
- package/docs/src/pages/server-api.js +0 -2
- package/docs/src/pages/server-data.js +18 -1
- package/docs/src/pages/slash-commands.js +0 -1
- package/docs/src/pages/spec.js +2 -1
- package/docs/src/pages/streaming.js +0 -2
- package/docs/src/pages/testing.js +0 -1
- package/docs/src/pages/validation.js +0 -1
- package/package.json +5 -1
- package/public/.pulse-ui-version +1 -1
- package/public/pulse-ui.css +27 -0
- package/scripts/build.js +53 -2
- package/scripts/strip-server.test.js +74 -2
- package/src/agent/checklist.md +20 -0
- package/src/agent/guide-server.md +62 -0
- package/src/agent/guide-spec.md +5 -3
- package/src/agent/guide.md +105 -2
- package/src/cli/dev.js +4 -0
- package/src/cli/scaffold.js +0 -2
- package/src/cli/start.js +1 -0
- package/src/md/highlight.js +369 -0
- package/src/md/index.js +337 -0
- package/src/spec/schema.js +8 -1
- package/types/md.d.ts +66 -0
package/CLAUDE.md
CHANGED
|
@@ -448,6 +448,42 @@ Pulse views are plain JS template literals, not JSX. These React patterns are **
|
|
|
448
448
|
|
|
449
449
|
Also: mutations must be pure (no fetch, no DOM access). Only `actions` can be async.
|
|
450
450
|
|
|
451
|
+
## Markdown
|
|
452
|
+
|
|
453
|
+
Pulse has a built-in markdown parser. Use it for blog posts, docs pages, and any static content written in `.md` files. Import from `@invisibleloop/pulse/md`. All parsing is server-side — zero browser JS.
|
|
454
|
+
|
|
455
|
+
```js
|
|
456
|
+
import { md } from '@invisibleloop/pulse/md'
|
|
457
|
+
import { prose } from '@invisibleloop/pulse/ui'
|
|
458
|
+
|
|
459
|
+
// Static path
|
|
460
|
+
const page = md('content/about.md')
|
|
461
|
+
|
|
462
|
+
// Dynamic route — :param resolved from ctx.params
|
|
463
|
+
const post = md('content/blog/:slug.md')
|
|
464
|
+
|
|
465
|
+
export default {
|
|
466
|
+
route: '/blog/:slug',
|
|
467
|
+
meta: {
|
|
468
|
+
title: async (ctx) => (await post(ctx)).frontmatter.title,
|
|
469
|
+
description: async (ctx) => (await post(ctx)).frontmatter.description,
|
|
470
|
+
},
|
|
471
|
+
server: { post },
|
|
472
|
+
view: (state, { post }) => `
|
|
473
|
+
<main id="main-content">
|
|
474
|
+
${prose({ content: post.html })}
|
|
475
|
+
</main>
|
|
476
|
+
`,
|
|
477
|
+
onViewError: () => `<main id="main-content"><p>Post not found.</p></main>`,
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
- `md(path)` returns an async fetcher — call it in both `meta` and `server`, the file is read only once per request
|
|
482
|
+
- Result is `{ html, frontmatter }` — `html` goes into `prose()`, `frontmatter` holds the `---` key/value pairs
|
|
483
|
+
- Missing file throws `{ status: 404 }` — always add `onViewError` on dynamic markdown routes
|
|
484
|
+
- For markdown from a database/API use `parseMd(source)` directly: `import { parseMd } from '@invisibleloop/pulse/md'`
|
|
485
|
+
- Use `new URL('./content/file.md', import.meta.url)` to resolve relative to the spec file rather than `process.cwd()`
|
|
486
|
+
|
|
451
487
|
## Check Components Before Building
|
|
452
488
|
|
|
453
489
|
Before writing any UI HTML by hand, check `src/ui/index.js` — there are 50+ components available. Use them. Do not reinvent `button`, `card`, `alert`, `modal`, `spinner`, `badge`, `input`, etc.
|
|
@@ -4,11 +4,7 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
4
4
|
|
|
5
5
|
### Critical
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
```js
|
|
9
|
-
hydrate: '/src/pages/my-page.js', // browser-importable path to this file
|
|
10
|
-
```
|
|
11
|
-
Omit `hydrate` only for purely server-rendered pages with zero client interactivity.
|
|
7
|
+
- **Do not set `hydrate` in specs.** It is auto-derived by the framework from the URL entry passed to `createServer`. Specs with `mutations`, `actions`, or `persist` are hydrated automatically. Purely server-rendered specs get zero JavaScript — no configuration needed.
|
|
12
8
|
|
|
13
9
|
### Components first
|
|
14
10
|
|
|
@@ -81,6 +77,12 @@ Before finishing any spec, verify every point below. Fix anything that fails.
|
|
|
81
77
|
```
|
|
82
78
|
Supported selectors: `tag`, `.class`, `#id`, `[attr]`, `[attr="value"]`, and combinations (`button.primary[disabled]`).
|
|
83
79
|
|
|
80
|
+
### Markdown
|
|
81
|
+
|
|
82
|
+
- **Use `md()` from `@invisibleloop/pulse/md` for `.md` file content.** Never read and parse markdown manually. Pass the result's `html` to `prose()`.
|
|
83
|
+
- **Call the same `md()` fetcher in both `meta` and `server`** — it caches on `ctx._mdCache` so the file is only read once per request. Do not create two separate fetchers for the same file.
|
|
84
|
+
- **Always add `onViewError` on dynamic markdown routes** (routes with `:param` segments loading `.md` files). A missing file throws `{ status: 404 }` which must be caught gracefully.
|
|
85
|
+
|
|
84
86
|
### View error handling
|
|
85
87
|
|
|
86
88
|
- **Define `onViewError` on any page where the view could throw due to bad or missing data.** Without it, a runtime view error returns a 500 on the server and shows a generic inline message on the client. With it, the server returns 200 with your fallback HTML instead of a 500, and the client renders your fallback:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.0
|
package/docs/public/pulse-ui.css
CHANGED
|
@@ -2806,6 +2806,26 @@ fieldset[disabled] .ui-rating-stars .ui-rating-star:hover ~ .ui-rating-star { co
|
|
|
2806
2806
|
color: var(--ui-text);
|
|
2807
2807
|
}
|
|
2808
2808
|
|
|
2809
|
+
/* ─── Syntax highlighting tokens ────────────────────────────────────────────
|
|
2810
|
+
Used by ui-prose pre code blocks rendered via @invisibleloop/pulse/md.
|
|
2811
|
+
Dark theme defaults; light theme overrides below. */
|
|
2812
|
+
:root {
|
|
2813
|
+
--tok-kw: #c792ea;
|
|
2814
|
+
--tok-str: #c3e88d;
|
|
2815
|
+
--tok-cmt: #7a8a9a;
|
|
2816
|
+
--tok-num: #f78c6c;
|
|
2817
|
+
--tok-fn: #82aaff;
|
|
2818
|
+
--tok-op: #89ddff;
|
|
2819
|
+
--tok-punct: #7e8899;
|
|
2820
|
+
}
|
|
2821
|
+
.tok-kw { color: var(--tok-kw); }
|
|
2822
|
+
.tok-str { color: var(--tok-str); }
|
|
2823
|
+
.tok-cmt { color: var(--tok-cmt); font-style: italic; }
|
|
2824
|
+
.tok-num { color: var(--tok-num); }
|
|
2825
|
+
.tok-fn { color: var(--tok-fn); }
|
|
2826
|
+
.tok-op { color: var(--tok-op); }
|
|
2827
|
+
.tok-punct { color: var(--tok-punct); }
|
|
2828
|
+
|
|
2809
2829
|
/* ─── Light theme ────────────────────────────────────────────────────────────
|
|
2810
2830
|
Apply to any ancestor element: <body data-theme="light"> or .ui-theme-light
|
|
2811
2831
|
All --ui-* tokens are overridden; component markup stays identical. */
|
|
@@ -2829,4 +2849,11 @@ fieldset[disabled] .ui-rating-stars .ui-rating-star:hover ~ .ui-rating-star { co
|
|
|
2829
2849
|
--ui-blue: #1e40af;
|
|
2830
2850
|
--ui-blue-dim: rgba( 30, 64, 175, .12);
|
|
2831
2851
|
--ui-nav-sticky-bg: rgba(255, 255, 255, .85);
|
|
2852
|
+
--tok-kw: #7c3aed;
|
|
2853
|
+
--tok-str: #15803d;
|
|
2854
|
+
--tok-cmt: #6b7280;
|
|
2855
|
+
--tok-num: #c2410c;
|
|
2856
|
+
--tok-fn: #1d4ed8;
|
|
2857
|
+
--tok-op: #0891b2;
|
|
2858
|
+
--tok-punct: #6b7280;
|
|
2832
2859
|
}
|
package/docs/server.js
CHANGED
|
@@ -33,6 +33,7 @@ await createServer(
|
|
|
33
33
|
new URL('./src/pages/persist.js', import.meta.url),
|
|
34
34
|
// Server
|
|
35
35
|
new URL('./src/pages/server-data.js', import.meta.url),
|
|
36
|
+
new URL('./src/pages/markdown.js', import.meta.url),
|
|
36
37
|
new URL('./src/pages/store.js', import.meta.url),
|
|
37
38
|
new URL('./src/pages/routing.js', import.meta.url),
|
|
38
39
|
new URL('./src/pages/streaming.js', import.meta.url),
|
|
@@ -1,299 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Character-by-character tokeniser. No dependencies, no client JS.
|
|
5
|
-
* Returns an HTML string of <span> elements ready to drop inside <code>.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const KEYWORDS = new Set([
|
|
9
|
-
'export', 'default', 'import', 'from', 'as',
|
|
10
|
-
'async', 'await', 'const', 'let', 'var',
|
|
11
|
-
'return', 'if', 'else', 'for', 'while', 'of', 'in',
|
|
12
|
-
'true', 'false', 'null', 'undefined',
|
|
13
|
-
'function', 'class', 'new', 'this', 'typeof', 'instanceof',
|
|
14
|
-
'try', 'catch', 'finally', 'throw',
|
|
15
|
-
])
|
|
16
|
-
|
|
17
|
-
function esc(s) {
|
|
18
|
-
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function span(cls, text) {
|
|
22
|
-
return `<span class="${cls}">${esc(text)}</span>`
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function highlight(code, lang = 'js') {
|
|
26
|
-
if (lang === 'bash') return highlightBash(code)
|
|
27
|
-
if (lang === 'html') return highlightHtml(code)
|
|
28
|
-
return highlightJs(code)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
// JavaScript tokeniser
|
|
33
|
-
// ---------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
function highlightJs(code) {
|
|
36
|
-
let out = ''
|
|
37
|
-
let i = 0
|
|
38
|
-
const n = code.length
|
|
39
|
-
|
|
40
|
-
while (i < n) {
|
|
41
|
-
const ch = code[i]
|
|
42
|
-
const ch2 = code[i + 1]
|
|
43
|
-
|
|
44
|
-
// Single-line comment
|
|
45
|
-
if (ch === '/' && ch2 === '/') {
|
|
46
|
-
const end = code.indexOf('\n', i)
|
|
47
|
-
const tok = end === -1 ? code.slice(i) : code.slice(i, end)
|
|
48
|
-
out += span('tok-cmt', tok)
|
|
49
|
-
i += tok.length
|
|
50
|
-
continue
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Block comment
|
|
54
|
-
if (ch === '/' && ch2 === '*') {
|
|
55
|
-
const end = code.indexOf('*/', i + 2)
|
|
56
|
-
const tok = end === -1 ? code.slice(i) : code.slice(i, end + 2)
|
|
57
|
-
out += span('tok-cmt', tok)
|
|
58
|
-
i += tok.length
|
|
59
|
-
continue
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Template literal
|
|
63
|
-
if (ch === '`') {
|
|
64
|
-
let tok = '`'
|
|
65
|
-
i++
|
|
66
|
-
let depth = 0
|
|
67
|
-
while (i < n) {
|
|
68
|
-
if (code[i] === '\\') {
|
|
69
|
-
tok += code[i] + (code[i + 1] || '')
|
|
70
|
-
i += 2
|
|
71
|
-
continue
|
|
72
|
-
}
|
|
73
|
-
if (code[i] === '$' && code[i + 1] === '{') {
|
|
74
|
-
tok += '${'
|
|
75
|
-
i += 2
|
|
76
|
-
depth++
|
|
77
|
-
continue
|
|
78
|
-
}
|
|
79
|
-
if (code[i] === '}' && depth > 0) {
|
|
80
|
-
tok += '}'
|
|
81
|
-
i++
|
|
82
|
-
depth--
|
|
83
|
-
continue
|
|
84
|
-
}
|
|
85
|
-
if (code[i] === '`' && depth === 0) {
|
|
86
|
-
tok += '`'
|
|
87
|
-
i++
|
|
88
|
-
break
|
|
89
|
-
}
|
|
90
|
-
tok += code[i++]
|
|
91
|
-
}
|
|
92
|
-
out += span('tok-str', tok)
|
|
93
|
-
continue
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Single or double quoted string
|
|
97
|
-
if (ch === '"' || ch === "'") {
|
|
98
|
-
let tok = ch
|
|
99
|
-
i++
|
|
100
|
-
while (i < n && code[i] !== ch && code[i] !== '\n') {
|
|
101
|
-
if (code[i] === '\\') {
|
|
102
|
-
tok += code[i] + (code[i + 1] || '')
|
|
103
|
-
i += 2
|
|
104
|
-
continue
|
|
105
|
-
}
|
|
106
|
-
tok += code[i++]
|
|
107
|
-
}
|
|
108
|
-
if (i < n && code[i] === ch) tok += code[i++]
|
|
109
|
-
out += span('tok-str', tok)
|
|
110
|
-
continue
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Number
|
|
114
|
-
if (/\d/.test(ch) && (i === 0 || !/\w/.test(code[i - 1]))) {
|
|
115
|
-
let tok = ''
|
|
116
|
-
while (i < n && /[\d.xXa-fA-F]/.test(code[i])) tok += code[i++]
|
|
117
|
-
out += span('tok-num', tok)
|
|
118
|
-
continue
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Identifier / keyword
|
|
122
|
-
if (/[a-zA-Z_$]/.test(ch)) {
|
|
123
|
-
let tok = ''
|
|
124
|
-
while (i < n && /[\w$]/.test(code[i])) tok += code[i++]
|
|
125
|
-
|
|
126
|
-
if (KEYWORDS.has(tok)) {
|
|
127
|
-
out += span('tok-kw', tok)
|
|
128
|
-
} else {
|
|
129
|
-
// Look ahead: followed by ( → function call
|
|
130
|
-
let j = i
|
|
131
|
-
while (j < n && code[j] === ' ') j++
|
|
132
|
-
if (code[j] === '(') {
|
|
133
|
-
out += span('tok-fn', tok)
|
|
134
|
-
} else {
|
|
135
|
-
out += esc(tok)
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
continue
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// Operators
|
|
142
|
-
if (/[=!<>|&?.]/.test(ch)) {
|
|
143
|
-
let tok = ch
|
|
144
|
-
// Grab two-char operators
|
|
145
|
-
if (/[=!<>|&?]/.test(ch) && /[=|&>?]/.test(ch2)) {
|
|
146
|
-
tok = code.slice(i, i + 2)
|
|
147
|
-
// Three-char: ===, !==, ...
|
|
148
|
-
if (code[i + 2] === '=') tok = code.slice(i, i + 3)
|
|
149
|
-
}
|
|
150
|
-
out += span('tok-op', tok)
|
|
151
|
-
i += tok.length
|
|
152
|
-
continue
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Punctuation
|
|
156
|
-
if (/[{}()[\],;:]/.test(ch)) {
|
|
157
|
-
out += span('tok-punct', ch)
|
|
158
|
-
i++
|
|
159
|
-
continue
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Everything else (whitespace, newlines, etc.)
|
|
163
|
-
out += esc(ch)
|
|
164
|
-
i++
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return out
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ---------------------------------------------------------------------------
|
|
171
|
-
// Bash tokeniser (minimal)
|
|
172
|
-
// ---------------------------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
function highlightBash(code) {
|
|
175
|
-
return code.split('\n').map(line => {
|
|
176
|
-
// Comment line
|
|
177
|
-
if (/^\s*#/.test(line)) return span('tok-cmt', line)
|
|
178
|
-
|
|
179
|
-
let out = ''
|
|
180
|
-
let i = 0
|
|
181
|
-
while (i < line.length) {
|
|
182
|
-
// Inline comment
|
|
183
|
-
if (line[i] === '#') {
|
|
184
|
-
out += span('tok-cmt', line.slice(i))
|
|
185
|
-
break
|
|
186
|
-
}
|
|
187
|
-
// String
|
|
188
|
-
if (line[i] === '"' || line[i] === "'") {
|
|
189
|
-
const q = line[i]
|
|
190
|
-
let tok = q
|
|
191
|
-
i++
|
|
192
|
-
while (i < line.length && line[i] !== q) tok += line[i++]
|
|
193
|
-
tok += line[i] === q ? line[i++] : ''
|
|
194
|
-
out += span('tok-str', tok)
|
|
195
|
-
continue
|
|
196
|
-
}
|
|
197
|
-
// Flag/option
|
|
198
|
-
if (line[i] === '-' && /\w/.test(line[i + 1] || '')) {
|
|
199
|
-
let tok = '-'
|
|
200
|
-
i++
|
|
201
|
-
while (i < line.length && /[-\w]/.test(line[i])) tok += line[i++]
|
|
202
|
-
out += span('tok-op', tok)
|
|
203
|
-
continue
|
|
204
|
-
}
|
|
205
|
-
out += esc(line[i++])
|
|
206
|
-
}
|
|
207
|
-
return out
|
|
208
|
-
}).join('\n')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// ---------------------------------------------------------------------------
|
|
212
|
-
// HTML tokeniser (minimal)
|
|
213
|
-
// ---------------------------------------------------------------------------
|
|
214
|
-
|
|
215
|
-
function highlightHtmlTag(tag) {
|
|
216
|
-
// Single-pass tag highlighter — avoids the chained .replace() trap where a
|
|
217
|
-
// later pass re-processes class="tok-fn" strings already injected by an
|
|
218
|
-
// earlier pass, producing broken HTML like <span class=<span …>"tok-fn"</span>>.
|
|
219
|
-
let out = ''
|
|
220
|
-
let i = 0
|
|
221
|
-
|
|
222
|
-
// Opening < or </
|
|
223
|
-
out += '<'
|
|
224
|
-
i++ // skip <
|
|
225
|
-
if (tag[i] === '/') { out += '/'; i++ }
|
|
226
|
-
|
|
227
|
-
// Tag name
|
|
228
|
-
let name = ''
|
|
229
|
-
while (i < tag.length && /[\w-]/.test(tag[i])) name += tag[i++]
|
|
230
|
-
if (name) out += span('tok-kw', name)
|
|
231
|
-
|
|
232
|
-
// Attributes and remainder up to >
|
|
233
|
-
while (i < tag.length) {
|
|
234
|
-
const ch = tag[i]
|
|
235
|
-
if (ch === '>') { out += '>'; i++; break }
|
|
236
|
-
if (ch === '/') { out += '/'; i++; continue }
|
|
237
|
-
|
|
238
|
-
if (/[\w-]/.test(ch)) {
|
|
239
|
-
// Attribute name
|
|
240
|
-
let attr = ''
|
|
241
|
-
while (i < tag.length && /[\w-]/.test(tag[i])) attr += tag[i++]
|
|
242
|
-
|
|
243
|
-
if (tag[i] === '=') {
|
|
244
|
-
out += span('tok-fn', attr) + esc('=')
|
|
245
|
-
i++ // skip =
|
|
246
|
-
// Attribute value
|
|
247
|
-
const q = tag[i]
|
|
248
|
-
if (q === '"' || q === "'") {
|
|
249
|
-
let val = q
|
|
250
|
-
i++
|
|
251
|
-
while (i < tag.length && tag[i] !== q) {
|
|
252
|
-
if (tag[i] === '\\') { val += tag[i] + (tag[i + 1] || ''); i += 2; continue }
|
|
253
|
-
val += tag[i++]
|
|
254
|
-
}
|
|
255
|
-
if (i < tag.length) val += tag[i++] // closing quote
|
|
256
|
-
out += span('tok-str', val)
|
|
257
|
-
}
|
|
258
|
-
} else {
|
|
259
|
-
out += esc(attr)
|
|
260
|
-
}
|
|
261
|
-
continue
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
out += esc(ch)
|
|
265
|
-
i++
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return out
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function highlightHtml(code) {
|
|
272
|
-
let out = ''
|
|
273
|
-
let i = 0
|
|
274
|
-
const n = code.length
|
|
275
|
-
|
|
276
|
-
while (i < n) {
|
|
277
|
-
// Comment
|
|
278
|
-
if (code.slice(i, i + 4) === '<!--') {
|
|
279
|
-
const end = code.indexOf('-->', i + 4)
|
|
280
|
-
const tok = end === -1 ? code.slice(i) : code.slice(i, end + 3)
|
|
281
|
-
out += span('tok-cmt', tok)
|
|
282
|
-
i += tok.length
|
|
283
|
-
continue
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Tag
|
|
287
|
-
if (code[i] === '<') {
|
|
288
|
-
const end = code.indexOf('>', i)
|
|
289
|
-
if (end === -1) { out += esc(code[i++]); continue }
|
|
290
|
-
out += highlightHtmlTag(code.slice(i, end + 1))
|
|
291
|
-
i = end + 1
|
|
292
|
-
continue
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
out += esc(code[i++])
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return out
|
|
299
|
-
}
|
|
1
|
+
// Re-export from the framework's canonical highlight implementation.
|
|
2
|
+
export { highlight } from '../../../src/md/highlight.js'
|
package/docs/src/lib/nav.js
CHANGED
|
@@ -38,6 +38,7 @@ export const NAV = [
|
|
|
38
38
|
section: 'Server',
|
|
39
39
|
items: [
|
|
40
40
|
{ label: 'Server Data', href: '/server-data' },
|
|
41
|
+
{ label: 'Markdown', href: '/markdown' },
|
|
41
42
|
{ label: 'Global Store', href: '/store' },
|
|
42
43
|
{ label: 'Routing', href: '/routing' },
|
|
43
44
|
{ label: 'Streaming SSR', href: '/streaming' },
|
package/docs/src/pages/auth.js
CHANGED
|
@@ -11,7 +11,6 @@ export default {
|
|
|
11
11
|
description: 'Integrating Auth0 OAuth authentication with Pulse using guard functions, ctx.setCookie, and raw response specs.',
|
|
12
12
|
styles: ['/docs.css'],
|
|
13
13
|
},
|
|
14
|
-
state: {},
|
|
15
14
|
view: () => renderLayout({
|
|
16
15
|
currentHref: '/auth',
|
|
17
16
|
prev,
|
|
@@ -141,7 +140,6 @@ export default {
|
|
|
141
140
|
profile: async (ctx) => fetchUserProfile(ctx.cookies.session),
|
|
142
141
|
},
|
|
143
142
|
|
|
144
|
-
state: {},
|
|
145
143
|
view: (state, server) => \`
|
|
146
144
|
<main id="main-content">
|
|
147
145
|
<h1>Welcome, \${server.profile.name}</h1>
|
|
@@ -12,7 +12,6 @@ export default {
|
|
|
12
12
|
description: 'macOS-style window chrome around a code block. Accepts pre-highlighted HTML as the content slot.',
|
|
13
13
|
styles: ['/pulse-ui.css', '/docs.css'],
|
|
14
14
|
},
|
|
15
|
-
state: {},
|
|
16
15
|
view: () => renderComponentPage({
|
|
17
16
|
currentHref: '/components/code-window',
|
|
18
17
|
prev,
|