@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.
Files changed (68) hide show
  1. package/README.md +26 -0
  2. package/dist/demo/DemoViewer.d.ts.map +1 -1
  3. package/dist/demo/DemoViewer.js +2 -2
  4. package/dist/demo/DemoViewer.js.map +1 -1
  5. package/dist/docs/DocsRenderer.d.ts +9 -2
  6. package/dist/docs/DocsRenderer.d.ts.map +1 -1
  7. package/dist/docs/DocsRenderer.js +13 -6
  8. package/dist/docs/DocsRenderer.js.map +1 -1
  9. package/dist/docs/DocumentProperties.d.ts +11 -0
  10. package/dist/docs/DocumentProperties.d.ts.map +1 -0
  11. package/dist/docs/DocumentProperties.js +63 -0
  12. package/dist/docs/DocumentProperties.js.map +1 -0
  13. package/dist/docs/TaskCheckbox.d.ts +24 -0
  14. package/dist/docs/TaskCheckbox.d.ts.map +1 -0
  15. package/dist/docs/TaskCheckbox.js +103 -0
  16. package/dist/docs/TaskCheckbox.js.map +1 -0
  17. package/dist/docs/properties.css +28 -0
  18. package/dist/docs/tasks.css +13 -0
  19. package/dist/gitbook/ast.d.ts +14 -0
  20. package/dist/gitbook/ast.d.ts.map +1 -1
  21. package/dist/gitbook/ast.js.map +1 -1
  22. package/dist/gitbook/index.d.ts +3 -0
  23. package/dist/gitbook/index.d.ts.map +1 -1
  24. package/dist/gitbook/index.js +2 -0
  25. package/dist/gitbook/index.js.map +1 -1
  26. package/dist/gitbook/parse.d.ts.map +1 -1
  27. package/dist/gitbook/parse.js +17 -3
  28. package/dist/gitbook/parse.js.map +1 -1
  29. package/dist/gitbook/properties.d.ts +15 -0
  30. package/dist/gitbook/properties.d.ts.map +1 -0
  31. package/dist/gitbook/properties.js +45 -0
  32. package/dist/gitbook/properties.js.map +1 -0
  33. package/dist/gitbook/serialize.d.ts.map +1 -1
  34. package/dist/gitbook/serialize.js +5 -2
  35. package/dist/gitbook/serialize.js.map +1 -1
  36. package/dist/gitbook/tasks.d.ts +11 -0
  37. package/dist/gitbook/tasks.d.ts.map +1 -0
  38. package/dist/gitbook/tasks.js +21 -0
  39. package/dist/gitbook/tasks.js.map +1 -0
  40. package/dist/index.d.ts +7 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +4 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/playground/ReactCodePreview.d.ts.map +1 -1
  45. package/dist/playground/ReactCodePreview.js +7 -0
  46. package/dist/playground/ReactCodePreview.js.map +1 -1
  47. package/dist/streamdown.d.ts +1 -1
  48. package/dist/streamdown.d.ts.map +1 -1
  49. package/dist/streamdown.js +2 -2
  50. package/dist/streamdown.js.map +1 -1
  51. package/dist/styles.css +2 -0
  52. package/package.json +15 -1
  53. package/src/demo/DemoViewer.tsx +4 -2
  54. package/src/docs/DocsRenderer.tsx +22 -5
  55. package/src/docs/DocumentProperties.tsx +84 -0
  56. package/src/docs/TaskCheckbox.tsx +111 -0
  57. package/src/docs/properties.css +28 -0
  58. package/src/docs/tasks.css +13 -0
  59. package/src/gitbook/ast.ts +16 -0
  60. package/src/gitbook/index.ts +3 -0
  61. package/src/gitbook/parse.ts +15 -3
  62. package/src/gitbook/properties.ts +49 -0
  63. package/src/gitbook/serialize.ts +5 -2
  64. package/src/gitbook/tasks.ts +25 -0
  65. package/src/index.ts +7 -0
  66. package/src/playground/ReactCodePreview.tsx +8 -0
  67. package/src/streamdown.tsx +4 -0
  68. 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
+ }
@@ -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
- let out = doc.children.map((b, k) => `${blankLines(b, k ? 1 : 0)}${serializeBlock(b)}\n`).join("")
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 ? `[${item.checked ? (item.checkMark ?? "x") : " "}] ` : ""
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
  })
@@ -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
  )
package/src/styles.css CHANGED
@@ -1,3 +1,5 @@
1
+ @import "./docs/properties.css";
2
+ @import "./docs/tasks.css";
1
3
 
2
4
  [data-docstream],
3
5
  .docs-article {