@brett_lamy/docstream 1.8.0 → 1.9.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 +26 -0
- package/dist/demo/DemoViewer.d.ts.map +1 -1
- package/dist/demo/DemoViewer.js +2 -2
- package/dist/demo/DemoViewer.js.map +1 -1
- package/dist/docs/DocsRenderer.d.ts +9 -2
- package/dist/docs/DocsRenderer.d.ts.map +1 -1
- package/dist/docs/DocsRenderer.js +13 -6
- package/dist/docs/DocsRenderer.js.map +1 -1
- package/dist/docs/DocumentProperties.d.ts +11 -0
- package/dist/docs/DocumentProperties.d.ts.map +1 -0
- package/dist/docs/DocumentProperties.js +63 -0
- package/dist/docs/DocumentProperties.js.map +1 -0
- package/dist/docs/TaskCheckbox.d.ts +24 -0
- package/dist/docs/TaskCheckbox.d.ts.map +1 -0
- package/dist/docs/TaskCheckbox.js +103 -0
- package/dist/docs/TaskCheckbox.js.map +1 -0
- package/dist/docs/properties.css +28 -0
- package/dist/docs/tasks.css +13 -0
- package/dist/gitbook/ast.d.ts +14 -0
- package/dist/gitbook/ast.d.ts.map +1 -1
- package/dist/gitbook/ast.js.map +1 -1
- package/dist/gitbook/index.d.ts +3 -0
- package/dist/gitbook/index.d.ts.map +1 -1
- package/dist/gitbook/index.js +2 -0
- package/dist/gitbook/index.js.map +1 -1
- package/dist/gitbook/parse.d.ts.map +1 -1
- package/dist/gitbook/parse.js +17 -3
- package/dist/gitbook/parse.js.map +1 -1
- package/dist/gitbook/properties.d.ts +15 -0
- package/dist/gitbook/properties.d.ts.map +1 -0
- package/dist/gitbook/properties.js +45 -0
- package/dist/gitbook/properties.js.map +1 -0
- package/dist/gitbook/serialize.d.ts.map +1 -1
- package/dist/gitbook/serialize.js +5 -2
- package/dist/gitbook/serialize.js.map +1 -1
- package/dist/gitbook/tasks.d.ts +11 -0
- package/dist/gitbook/tasks.d.ts.map +1 -0
- package/dist/gitbook/tasks.js +21 -0
- package/dist/gitbook/tasks.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/playground/ReactCodePreview.d.ts.map +1 -1
- package/dist/playground/ReactCodePreview.js +7 -0
- package/dist/playground/ReactCodePreview.js.map +1 -1
- package/dist/streamdown.d.ts +1 -1
- package/dist/streamdown.d.ts.map +1 -1
- package/dist/streamdown.js +2 -2
- package/dist/streamdown.js.map +1 -1
- package/dist/styles.css +2 -0
- package/package.json +15 -1
- package/src/demo/DemoViewer.tsx +4 -2
- package/src/docs/DocsRenderer.tsx +22 -5
- package/src/docs/DocumentProperties.tsx +84 -0
- package/src/docs/TaskCheckbox.tsx +111 -0
- package/src/docs/properties.css +28 -0
- package/src/docs/tasks.css +13 -0
- package/src/gitbook/ast.ts +16 -0
- package/src/gitbook/index.ts +3 -0
- package/src/gitbook/parse.ts +15 -3
- package/src/gitbook/properties.ts +49 -0
- package/src/gitbook/serialize.ts +5 -2
- package/src/gitbook/tasks.ts +25 -0
- package/src/index.ts +7 -0
- package/src/playground/ReactCodePreview.tsx +8 -0
- package/src/streamdown.tsx +4 -0
- package/src/styles.css +2 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { isMap, isScalar, parseDocument } from "yaml"
|
|
2
|
+
import type { DocumentPropertiesNode } from "./ast"
|
|
3
|
+
|
|
4
|
+
export type DocumentPropertyValue = string | number | boolean | null | DocumentPropertyValue[] | { [key: string]: DocumentPropertyValue }
|
|
5
|
+
export interface ParsedDocumentProperties {
|
|
6
|
+
values: Record<string, DocumentPropertyValue>
|
|
7
|
+
error?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Read properties without throwing on an incomplete or invalid document. YAML is never executed. */
|
|
11
|
+
export function readDocumentProperties(properties: DocumentPropertiesNode): ParsedDocumentProperties {
|
|
12
|
+
try {
|
|
13
|
+
const doc = parseDocument(properties.yaml)
|
|
14
|
+
if (doc.errors.length) return { values: {}, error: doc.errors[0].message }
|
|
15
|
+
if (doc.contents && !isMap(doc.contents)) return { values: {}, error: "Document properties must be a YAML mapping." }
|
|
16
|
+
const values = (doc.toJS({ maxAliasCount: 100 }) ?? {}) as Record<string, DocumentPropertyValue>
|
|
17
|
+
// Cyclic aliases can be preserved as YAML, but cannot be represented by editable JSON-like values.
|
|
18
|
+
JSON.stringify(values)
|
|
19
|
+
return { values }
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return { values: {}, error: error instanceof Error ? error.message : String(error) }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Change one property while retaining the other keys, comments, scalar styles and frontmatter delimiters. */
|
|
26
|
+
export function setDocumentProperty(properties: DocumentPropertiesNode, key: string, value: DocumentPropertyValue): DocumentPropertiesNode {
|
|
27
|
+
const doc = parseDocument(properties.yaml)
|
|
28
|
+
const parsed = readDocumentProperties(properties)
|
|
29
|
+
if (parsed.error) throw new Error(parsed.error)
|
|
30
|
+
const previous = doc.get(key, true)
|
|
31
|
+
const range = isScalar(previous) ? previous.range : undefined
|
|
32
|
+
doc.set(key, value)
|
|
33
|
+
const yaml = doc.toString().replace(/\n$/, "")
|
|
34
|
+
const next = parseDocument(yaml).get(key, true)
|
|
35
|
+
// A scalar edit is a source-range patch, leaving every other byte (including flow-list spacing) alone.
|
|
36
|
+
if (range && isScalar(next) && next.range) {
|
|
37
|
+
return { ...properties, yaml: properties.yaml.slice(0, range[0]) + yaml.slice(next.range[0], next.range[1]) + properties.yaml.slice(range[1]) }
|
|
38
|
+
}
|
|
39
|
+
return { ...properties, yaml }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Remove one property; the frontmatter remains available for adding more. */
|
|
43
|
+
export function removeDocumentProperty(properties: DocumentPropertiesNode, key: string): DocumentPropertiesNode {
|
|
44
|
+
const doc = parseDocument(properties.yaml)
|
|
45
|
+
const parsed = readDocumentProperties(properties)
|
|
46
|
+
if (parsed.error) throw new Error(parsed.error)
|
|
47
|
+
doc.delete(key)
|
|
48
|
+
return { ...properties, yaml: doc.toString().replace(/\n$/, "") }
|
|
49
|
+
}
|
package/src/gitbook/serialize.ts
CHANGED
|
@@ -27,6 +27,7 @@ import { attr } from "./attrs"
|
|
|
27
27
|
import { normalizeLabel, serializeInline, serializeReference, withDocumentDefinitions } from "./inline"
|
|
28
28
|
import { plainText } from "./inline"
|
|
29
29
|
import { parseBlocks } from "./parse"
|
|
30
|
+
import { taskMarker } from "./tasks"
|
|
30
31
|
|
|
31
32
|
/** A `{% tag %}…{% endtag %}` container, block or item. */
|
|
32
33
|
export type TaggedNode = HintNode | TabsNode | TabNode | StepperNode | StepNode | ColumnsNode | ColumnNode | UpdatesNode | UpdateNode
|
|
@@ -114,7 +115,9 @@ export function serializeMarkdown(doc: DocumentNode): string {
|
|
|
114
115
|
documentCitations = citations
|
|
115
116
|
try {
|
|
116
117
|
return withDocumentDefinitions(defs, () => {
|
|
117
|
-
|
|
118
|
+
const properties = doc.properties
|
|
119
|
+
let out = properties ? `${properties.opening ?? "---"}\n${properties.yaml}${properties.yaml ? "\n" : ""}${properties.closing ?? "---"}\n` : ""
|
|
120
|
+
out += doc.children.map((b, k) => `${blankLines(b, k ? 1 : 0)}${serializeBlock(b)}\n`).join("")
|
|
118
121
|
// Footnote definitions not placed in the document end it, as a group.
|
|
119
122
|
const trailing = (doc.citations ?? []).filter((c) => !placed.has(c.id))
|
|
120
123
|
if (trailing.length) {
|
|
@@ -537,7 +540,7 @@ function inlineToHtml(nodes: Inline[]): string {
|
|
|
537
540
|
|
|
538
541
|
function serializeListItem(item: ListItemNode, list: ListNode, idx: number): string {
|
|
539
542
|
const marker = item.marker ?? (list.ordered ? `${(list.start ?? 1) + idx}${list.delimiter ?? "."}` : (list.bullet ?? "-"))
|
|
540
|
-
const check = list.task ? `[${
|
|
543
|
+
const check = list.task ? `[${taskMarker(item)}] ` : ""
|
|
541
544
|
const [first, ...rest] = item.children
|
|
542
545
|
const firstText = first?.type === "paragraph" ? serializeInline(first.children) : first ? serializeBlock(first) : ""
|
|
543
546
|
const restText = rest.length ? "\n" + indent(serializeBlocks(rest), " ".repeat(item.indent ?? 2)) : ""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ListItemNode, TaskState } from "./ast"
|
|
2
|
+
|
|
3
|
+
export const TASK_STATES: ReadonlyArray<{ id: TaskState; marker: string; label: string }> = [
|
|
4
|
+
{ id: "pending", marker: " ", label: "Pending" },
|
|
5
|
+
{ id: "in-progress", marker: "~", label: "In progress" },
|
|
6
|
+
{ id: "implemented", marker: "?", label: "Implemented · evidence needed" },
|
|
7
|
+
{ id: "blocked", marker: "b", label: "Blocked" },
|
|
8
|
+
{ id: "parked", marker: "d", label: "Parked · verification debt" },
|
|
9
|
+
{ id: "refuted", marker: "!", label: "Refuted" },
|
|
10
|
+
{ id: "verified", marker: "x", label: "Verified" },
|
|
11
|
+
{ id: "cancelled", marker: "-", label: "Cancelled" },
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
export function taskStateFromMarker(marker: string): TaskState | undefined {
|
|
15
|
+
return TASK_STATES.find((state) => state.marker === (marker === "X" ? "x" : marker))?.id
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function getTaskState(item: Pick<ListItemNode, "state" | "checked">): TaskState {
|
|
19
|
+
return item.state ?? (item.checked ? "verified" : "pending")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Explicit state wins; untouched uppercase `[X]` tasks retain their spelling. */
|
|
23
|
+
export function taskMarker(item: Pick<ListItemNode, "state" | "checked" | "checkMark">): string {
|
|
24
|
+
return item.state ? TASK_STATES.find((state) => state.id === item.state)?.marker ?? " " : item.checked ? item.checkMark ?? "x" : " "
|
|
25
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
export { GitbookStreamdown } from "./streamdown"
|
|
4
4
|
export type { GitbookStreamdownProps } from "./streamdown"
|
|
5
5
|
export { DocsRenderer, MarkdownContent } from "./docs/DocsRenderer"
|
|
6
|
+
export { TaskCheckbox, TASK_STATE_ICONS } from "./docs/TaskCheckbox"
|
|
7
|
+
export type { TaskCheckboxProps, TaskStateChangeHandler } from "./docs/TaskCheckbox"
|
|
8
|
+
export { TASK_STATES, getTaskState, taskMarker, taskStateFromMarker } from "./gitbook/tasks"
|
|
9
|
+
export { DocumentProperties } from "./docs/DocumentProperties"
|
|
10
|
+
export type { DocumentPropertiesProps } from "./docs/DocumentProperties"
|
|
11
|
+
export { readDocumentProperties, removeDocumentProperty, setDocumentProperty } from "./gitbook/properties"
|
|
12
|
+
export type { DocumentPropertyValue, ParsedDocumentProperties } from "./gitbook/properties"
|
|
6
13
|
export type { DocRenderOptions, LivePreviewProps, LivePreviewRenderer, SourceReferenceRenderer } from "./docs/DocsRenderer"
|
|
7
14
|
export { DocPageActions } from "./docs/PageActions"
|
|
8
15
|
export type { DocPageAction, DocPageActionsOptions, DocPageActionsProps } from "./docs/PageActions"
|
|
@@ -128,6 +128,14 @@ export function useAlmostNodeServer({ files, port = 4173, run, workspaceOptions,
|
|
|
128
128
|
},
|
|
129
129
|
})
|
|
130
130
|
|
|
131
|
+
// The iframe's virtual URL only works once the host page is controlled. Do not
|
|
132
|
+
// reload the host when a reader opens its first live example.
|
|
133
|
+
await workspace.container.serverBridge.ensureServiceWorkerReady({ reloadOnFirstControl: false })
|
|
134
|
+
if (cancelled) {
|
|
135
|
+
workspace.dispose()
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
131
139
|
const result = await workspace.container.run(`vite --port ${port}`, {
|
|
132
140
|
env: DETACHED_SERVER_ENV,
|
|
133
141
|
})
|
package/src/streamdown.tsx
CHANGED
|
@@ -35,6 +35,8 @@ export function GitbookStreamdown({
|
|
|
35
35
|
demoDependencies,
|
|
36
36
|
demoVFS,
|
|
37
37
|
pageActions,
|
|
38
|
+
documentProperties,
|
|
39
|
+
onTaskStateChange,
|
|
38
40
|
}: GitbookStreamdownProps) {
|
|
39
41
|
const content = markdown ?? children ?? ""
|
|
40
42
|
const gitbookDoc = useMemo(
|
|
@@ -59,6 +61,8 @@ export function GitbookStreamdown({
|
|
|
59
61
|
demoDependencies={demoDependencies}
|
|
60
62
|
demoVFS={demoVFS}
|
|
61
63
|
pageActions={isStreaming ? false : pageActions}
|
|
64
|
+
documentProperties={documentProperties}
|
|
65
|
+
onTaskStateChange={onTaskStateChange}
|
|
62
66
|
/>
|
|
63
67
|
</div>
|
|
64
68
|
)
|