@tooee/view 0.2.4 → 0.3.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.
Files changed (121) hide show
  1. package/README.md +15 -15
  2. package/dist/components/subviews/{CodeSubview.d.ts → code-subview.d.ts} +2 -2
  3. package/dist/components/subviews/code-subview.d.ts.map +1 -0
  4. package/dist/components/subviews/{CodeSubview.js → code-subview.js} +8 -8
  5. package/dist/components/subviews/code-subview.js.map +1 -0
  6. package/dist/components/subviews/custom-subview.d.ts +9 -0
  7. package/dist/components/subviews/custom-subview.d.ts.map +1 -0
  8. package/dist/components/subviews/{CustomSubview.js → custom-subview.js} +7 -7
  9. package/dist/components/subviews/custom-subview.js.map +1 -0
  10. package/dist/components/subviews/index.d.ts +4 -4
  11. package/dist/components/subviews/index.d.ts.map +1 -1
  12. package/dist/components/subviews/index.js +4 -4
  13. package/dist/components/subviews/index.js.map +1 -1
  14. package/dist/components/subviews/{MarkdownSubview.d.ts → markdown-subview.d.ts} +3 -3
  15. package/dist/components/subviews/markdown-subview.d.ts.map +1 -0
  16. package/dist/components/subviews/{MarkdownSubview.js → markdown-subview.js} +27 -25
  17. package/dist/components/subviews/markdown-subview.js.map +1 -0
  18. package/dist/components/subviews/table-subview.d.ts +8 -0
  19. package/dist/components/subviews/table-subview.d.ts.map +1 -0
  20. package/dist/components/subviews/{TableSubview.js → table-subview.js} +40 -22
  21. package/dist/components/subviews/table-subview.js.map +1 -0
  22. package/dist/components/subviews/types.d.ts.map +1 -1
  23. package/dist/components/{ViewScreen.d.ts → view-screen.d.ts} +3 -3
  24. package/dist/components/view-screen.d.ts.map +1 -0
  25. package/dist/components/{ViewScreen.js → view-screen.js} +11 -11
  26. package/dist/components/view-screen.js.map +1 -0
  27. package/dist/content-loader-store.d.ts +11 -7
  28. package/dist/content-loader-store.d.ts.map +1 -1
  29. package/dist/content-loader-store.js +70 -50
  30. package/dist/content-loader-store.js.map +1 -1
  31. package/dist/default-provider.d.ts +4 -4
  32. package/dist/default-provider.d.ts.map +1 -1
  33. package/dist/default-provider.js +48 -39
  34. package/dist/default-provider.js.map +1 -1
  35. package/dist/directory-provider.d.ts +2 -2
  36. package/dist/directory-provider.d.ts.map +1 -1
  37. package/dist/directory-provider.js +13 -10
  38. package/dist/directory-provider.js.map +1 -1
  39. package/dist/directory-view.d.ts +8 -0
  40. package/dist/directory-view.d.ts.map +1 -0
  41. package/dist/{DirectoryView.js → directory-view.js} +18 -12
  42. package/dist/directory-view.js.map +1 -0
  43. package/dist/hooks/index.d.ts +4 -4
  44. package/dist/hooks/index.d.ts.map +1 -1
  45. package/dist/hooks/index.js +3 -3
  46. package/dist/hooks/index.js.map +1 -1
  47. package/dist/hooks/{useContentCommands.d.ts → use-content-commands.d.ts} +2 -2
  48. package/dist/hooks/use-content-commands.d.ts.map +1 -0
  49. package/dist/hooks/{useContentCommands.js → use-content-commands.js} +6 -4
  50. package/dist/hooks/use-content-commands.js.map +1 -0
  51. package/dist/hooks/{useContentLoader.d.ts → use-content-loader.d.ts} +4 -4
  52. package/dist/hooks/use-content-loader.d.ts.map +1 -0
  53. package/dist/hooks/use-content-loader.js +81 -0
  54. package/dist/hooks/use-content-loader.js.map +1 -0
  55. package/dist/hooks/{useViewCommandContext.d.ts → use-view-command-context.d.ts} +3 -3
  56. package/dist/hooks/use-view-command-context.d.ts.map +1 -0
  57. package/dist/hooks/{useViewCommandContext.js → use-view-command-context.js} +15 -13
  58. package/dist/hooks/use-view-command-context.js.map +1 -0
  59. package/dist/index.d.ts +4 -4
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +3 -3
  62. package/dist/index.js.map +1 -1
  63. package/dist/launch.d.ts +2 -2
  64. package/dist/launch.d.ts.map +1 -1
  65. package/dist/launch.js +6 -6
  66. package/dist/launch.js.map +1 -1
  67. package/dist/types.d.ts +4 -4
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js +46 -24
  70. package/dist/types.js.map +1 -1
  71. package/dist/{View.d.ts → view.d.ts} +3 -3
  72. package/dist/view.d.ts.map +1 -0
  73. package/dist/{View.js → view.js} +19 -13
  74. package/dist/view.js.map +1 -0
  75. package/package.json +10 -10
  76. package/src/components/subviews/{CodeSubview.tsx → code-subview.tsx} +25 -19
  77. package/src/components/subviews/{CustomSubview.tsx → custom-subview.tsx} +23 -21
  78. package/src/components/subviews/index.ts +5 -5
  79. package/src/components/subviews/{MarkdownSubview.tsx → markdown-subview.tsx} +53 -54
  80. package/src/components/subviews/table-subview.tsx +95 -0
  81. package/src/components/subviews/types.ts +14 -14
  82. package/src/components/{ViewScreen.tsx → view-screen.tsx} +33 -32
  83. package/src/content-loader-store.ts +131 -99
  84. package/src/default-provider.ts +84 -64
  85. package/src/directory-provider.ts +32 -24
  86. package/src/directory-view.tsx +79 -0
  87. package/src/hooks/index.ts +7 -4
  88. package/src/hooks/{useContentCommands.ts → use-content-commands.ts} +19 -14
  89. package/src/hooks/use-content-loader.ts +92 -0
  90. package/src/hooks/{useViewCommandContext.ts → use-view-command-context.ts} +40 -38
  91. package/src/index.ts +14 -14
  92. package/src/launch.tsx +20 -18
  93. package/src/types.ts +107 -78
  94. package/src/{View.tsx → view.tsx} +51 -44
  95. package/dist/DirectoryView.d.ts +0 -7
  96. package/dist/DirectoryView.d.ts.map +0 -1
  97. package/dist/DirectoryView.js.map +0 -1
  98. package/dist/View.d.ts.map +0 -1
  99. package/dist/View.js.map +0 -1
  100. package/dist/components/ViewScreen.d.ts.map +0 -1
  101. package/dist/components/ViewScreen.js.map +0 -1
  102. package/dist/components/subviews/CodeSubview.d.ts.map +0 -1
  103. package/dist/components/subviews/CodeSubview.js.map +0 -1
  104. package/dist/components/subviews/CustomSubview.d.ts +0 -9
  105. package/dist/components/subviews/CustomSubview.d.ts.map +0 -1
  106. package/dist/components/subviews/CustomSubview.js.map +0 -1
  107. package/dist/components/subviews/MarkdownSubview.d.ts.map +0 -1
  108. package/dist/components/subviews/MarkdownSubview.js.map +0 -1
  109. package/dist/components/subviews/TableSubview.d.ts +0 -8
  110. package/dist/components/subviews/TableSubview.d.ts.map +0 -1
  111. package/dist/components/subviews/TableSubview.js.map +0 -1
  112. package/dist/hooks/useContentCommands.d.ts.map +0 -1
  113. package/dist/hooks/useContentCommands.js.map +0 -1
  114. package/dist/hooks/useContentLoader.d.ts.map +0 -1
  115. package/dist/hooks/useContentLoader.js +0 -55
  116. package/dist/hooks/useContentLoader.js.map +0 -1
  117. package/dist/hooks/useViewCommandContext.d.ts.map +0 -1
  118. package/dist/hooks/useViewCommandContext.js.map +0 -1
  119. package/src/DirectoryView.tsx +0 -68
  120. package/src/components/subviews/TableSubview.tsx +0 -65
  121. package/src/hooks/useContentLoader.ts +0 -72
@@ -1,11 +1,11 @@
1
- import { useState } from "react"
2
- import { useCopyCommand, useToggleLineNumbersCommand } from "@tooee/shell"
3
- import { useConfig } from "@tooee/config"
4
- import type { AnyContent } from "../types.js"
1
+ import { useState } from "react";
2
+ import { useCopyCommand, useToggleLineNumbersCommand } from "@tooee/shell";
3
+ import { useConfig } from "@tooee/config";
4
+ import type { AnyContent } from "../types.js";
5
5
 
6
6
  interface UseContentCommandsParams {
7
- content: AnyContent | null
8
- textContent: string
7
+ content: AnyContent | null;
8
+ textContent: string;
9
9
  }
10
10
 
11
11
  /**
@@ -13,17 +13,22 @@ interface UseContentCommandsParams {
13
13
  * whole document and toggling the line-number gutter. Theme, quit, actions and
14
14
  * every row-level command belong to the document controller and its screen.
15
15
  */
16
- export function useContentCommands({ content, textContent }: UseContentCommandsParams) {
17
- const config = useConfig()
18
- const [showLineNumbers, setShowLineNumbers] = useState(config.view?.gutter ?? true)
16
+ export const useContentCommands = function useContentCommands({
17
+ content,
18
+ textContent,
19
+ }: UseContentCommandsParams) {
20
+ const config = useConfig();
21
+ const [showLineNumbers, setShowLineNumbers] = useState(config.view?.gutter ?? true);
19
22
 
20
23
  useCopyCommand({
21
24
  getText: () => (content ? textContent : undefined),
22
- })
25
+ });
23
26
  useToggleLineNumbersCommand({
27
+ onToggle: () => {
28
+ setShowLineNumbers((v) => !v);
29
+ },
24
30
  showLineNumbers,
25
- onToggle: () => setShowLineNumbers((v) => !v),
26
- })
31
+ });
27
32
 
28
- return { showLineNumbers }
29
- }
33
+ return { showLineNumbers };
34
+ };
@@ -0,0 +1,92 @@
1
+ import { useCallback, useEffect, useMemo } from "react";
2
+ import { useSelector } from "@xstate/store-react";
3
+ import type { ContentProvider } from "../types.js";
4
+ import {
5
+ createContentLoaderStore,
6
+ isAsyncIterable,
7
+ normalizeError,
8
+ selectContent,
9
+ selectError,
10
+ selectLoadSeq,
11
+ selectProviderMarks,
12
+ selectStatus,
13
+ selectStreaming,
14
+ } from "../content-loader-store.js";
15
+
16
+ export const useContentLoader = function useContentLoader(contentProvider: ContentProvider) {
17
+ const store = useMemo(createContentLoaderStore, []);
18
+ const loadSeq = useSelector(store, (snapshot) => selectLoadSeq(snapshot.context));
19
+
20
+ useEffect(() => {
21
+ store.trigger.loadStarted({
22
+ marks: contentProvider.marks ?? [],
23
+ title: contentProvider.title,
24
+ });
25
+ const { requestId } = store.getSnapshot().context;
26
+ const loaded = contentProvider.load();
27
+ let cleanup: () => void = () => {
28
+ // Synchronous providers have no pending work to cancel.
29
+ };
30
+
31
+ if (isAsyncIterable(loaded)) {
32
+ store.trigger.streamStarted({
33
+ format: contentProvider.format ?? "markdown",
34
+ requestId,
35
+ });
36
+ const iterator = loaded[Symbol.asyncIterator]();
37
+ void (async () => {
38
+ try {
39
+ while (true) {
40
+ // Deferred(lint-sweep): preserve sequential stream consumption for ordered backpressure.
41
+ // oxlint-disable-next-line no-await-in-loop -- each chunk must be consumed in iterator order
42
+ const result = await iterator.next();
43
+ if (result.done === true) {
44
+ break;
45
+ }
46
+ store.trigger.chunkReceived({ chunk: result.value, requestId });
47
+ }
48
+ store.trigger.streamEnded({ requestId });
49
+ } catch (error) {
50
+ store.trigger.loadFailed({ error: normalizeError(error), requestId });
51
+ }
52
+ })();
53
+ cleanup = () => {
54
+ store.trigger.loadCancelled({ requestId });
55
+ void (async () => {
56
+ try {
57
+ await iterator.return?.();
58
+ } catch {
59
+ // Iterator cleanup is best-effort during effect disposal.
60
+ }
61
+ })();
62
+ };
63
+ } else if (loaded instanceof Promise) {
64
+ void (async () => {
65
+ try {
66
+ const content = await loaded;
67
+ store.trigger.loaded({ content, requestId });
68
+ } catch (error) {
69
+ store.trigger.loadFailed({ error: normalizeError(error), requestId });
70
+ }
71
+ })();
72
+ cleanup = () => {
73
+ store.trigger.loadCancelled({ requestId });
74
+ };
75
+ } else {
76
+ store.trigger.loaded({ content: loaded, requestId });
77
+ }
78
+
79
+ return cleanup;
80
+ }, [contentProvider, loadSeq, store]);
81
+
82
+ const content = useSelector(store, (snapshot) => selectContent(snapshot.context));
83
+ const streaming = useSelector(store, (snapshot) => selectStreaming(snapshot.context));
84
+ const error = useSelector(store, (snapshot) => selectError(snapshot.context));
85
+ const providerMarks = useSelector(store, (snapshot) => selectProviderMarks(snapshot.context));
86
+ const status = useSelector(store, (snapshot) => selectStatus(snapshot.context));
87
+ const reload = useCallback(() => {
88
+ store.trigger.reloadRequested({});
89
+ }, [store]);
90
+
91
+ return { content, error, providerMarks, reload, status, streaming };
92
+ };
@@ -1,6 +1,6 @@
1
- import { useProvideCommandContextKey } from "@tooee/commands"
2
- import type { MarkSet } from "@tooee/marks"
3
- import type { AnyContent } from "../types.js"
1
+ import { useProvideCommandContextKey } from "@tooee/commands";
2
+ import type { MarkSet } from "@tooee/marks";
3
+ import type { AnyContent } from "../types.js";
4
4
 
5
5
  /**
6
6
  * The `view` field contributed to the command context (see augmentation below).
@@ -10,45 +10,47 @@ import type { AnyContent } from "../types.js"
10
10
  * which `DocumentScreen` provides alongside this.
11
11
  */
12
12
  export interface ViewCommandContext {
13
- content: AnyContent
14
- format: string
15
- title?: string
16
- data?: unknown
13
+ content: AnyContent;
14
+ format: string;
15
+ title?: string;
16
+ data?: unknown;
17
17
  /** Reload the content from its provider. */
18
- reload: () => void
18
+ reload: () => void;
19
19
  marks: {
20
- setMarkSet: (set: MarkSet) => void
21
- clearNamespace: (namespace: string) => void
22
- clearAll: () => void
23
- userMarks: MarkSet[]
24
- providerMarks: MarkSet[]
25
- }
20
+ setMarkSet: (set: MarkSet) => void;
21
+ clearNamespace: (namespace: string) => void;
22
+ clearAll: () => void;
23
+ userMarks: MarkSet[];
24
+ providerMarks: MarkSet[];
25
+ };
26
26
  }
27
27
 
28
28
  declare module "@tooee/commands" {
29
29
  interface CommandContext {
30
- view: ViewCommandContext
30
+ view: ViewCommandContext;
31
31
  }
32
32
  }
33
33
 
34
- const noop = () => {}
34
+ const noop = () => {
35
+ // Optional command-context operations default to intentional no-ops.
36
+ };
35
37
 
36
38
  export interface CreateViewCommandContextOptions {
37
39
  /**
38
40
  * Content represented by this command context. Custom/headless surfaces may
39
41
  * omit it; the builder will synthesize a minimal custom content object.
40
42
  */
41
- content?: AnyContent
42
- format?: string
43
- title?: string
44
- data?: unknown
45
- reload?: () => void
46
- marks?: Partial<ViewCommandContext["marks"]>
43
+ content?: AnyContent;
44
+ format?: string;
45
+ title?: string;
46
+ data?: unknown;
47
+ reload?: () => void;
48
+ marks?: Partial<ViewCommandContext["marks"]>;
47
49
  }
48
50
 
49
- export type ProvideViewCommandContextOptions = CreateViewCommandContextOptions
51
+ export type ProvideViewCommandContextOptions = CreateViewCommandContextOptions;
50
52
 
51
- export function createViewCommandContext({
53
+ export const createViewCommandContext = function createViewCommandContext({
52
54
  content,
53
55
  format,
54
56
  title,
@@ -56,35 +58,35 @@ export function createViewCommandContext({
56
58
  reload,
57
59
  marks,
58
60
  }: CreateViewCommandContextOptions): ViewCommandContext {
59
- const resolvedFormat = format ?? content?.format ?? "custom"
61
+ const resolvedFormat = format ?? content?.format ?? "custom";
60
62
  const resolvedContent: AnyContent =
61
63
  content ??
62
64
  ({
63
- format: resolvedFormat,
64
65
  data,
66
+ format: resolvedFormat,
65
67
  title,
66
- } satisfies AnyContent)
68
+ } satisfies AnyContent);
67
69
 
68
70
  return {
69
71
  content: resolvedContent,
70
- format: resolvedFormat,
71
- title: title ?? content?.title,
72
72
  data: data ?? ("data" in resolvedContent ? resolvedContent.data : undefined),
73
- reload: reload ?? noop,
73
+ format: resolvedFormat,
74
74
  marks: {
75
- setMarkSet: marks?.setMarkSet ?? noop,
76
- clearNamespace: marks?.clearNamespace ?? noop,
77
75
  clearAll: marks?.clearAll ?? noop,
78
- userMarks: marks?.userMarks ?? [],
76
+ clearNamespace: marks?.clearNamespace ?? noop,
79
77
  providerMarks: marks?.providerMarks ?? [],
78
+ setMarkSet: marks?.setMarkSet ?? noop,
79
+ userMarks: marks?.userMarks ?? [],
80
80
  },
81
- }
82
- }
81
+ reload: reload ?? noop,
82
+ title: title ?? content?.title,
83
+ };
84
+ };
83
85
 
84
- export function useProvideViewCommandContext(
86
+ export const useProvideViewCommandContext = function useProvideViewCommandContext(
85
87
  options: ProvideViewCommandContextOptions | (() => ProvideViewCommandContextOptions),
86
88
  ) {
87
89
  useProvideCommandContextKey("view", () =>
88
90
  createViewCommandContext(typeof options === "function" ? options() : options),
89
- )
90
- }
91
+ );
92
+ };
package/src/index.ts CHANGED
@@ -1,25 +1,25 @@
1
- export { View } from "./View.js"
2
- export { DirectoryView } from "./DirectoryView.js"
3
- export { launch, launchDirectory } from "./launch.js"
4
- export type { ViewLaunchOptions } from "./launch.js"
1
+ export { View } from "./view.js";
2
+ export { DirectoryView } from "./directory-view.js";
3
+ export { launch, launchDirectory } from "./launch.js";
4
+ export type { ViewLaunchOptions } from "./launch.js";
5
5
  export {
6
6
  createFileProvider,
7
7
  createStdinProvider,
8
8
  createTableFileProvider,
9
9
  createTableStdinProvider,
10
- } from "./default-provider.js"
11
- export type { CreateProviderOptions } from "./default-provider.js"
12
- export { listDirectoryFiles } from "./directory-provider.js"
13
- export type { DirectoryEntry } from "./directory-provider.js"
10
+ } from "./default-provider.js";
11
+ export type { CreateProviderOptions } from "./default-provider.js";
12
+ export { listDirectoryFiles } from "./directory-provider.js";
13
+ export type { DirectoryEntry } from "./directory-provider.js";
14
14
  export {
15
15
  createViewCommandContext,
16
16
  useProvideViewCommandContext,
17
- } from "./hooks/useViewCommandContext.js"
17
+ } from "./hooks/use-view-command-context.js";
18
18
  export type {
19
19
  CreateViewCommandContextOptions,
20
20
  ProvideViewCommandContextOptions,
21
21
  ViewCommandContext,
22
- } from "./hooks/useViewCommandContext.js"
22
+ } from "./hooks/use-view-command-context.js";
23
23
  export type {
24
24
  AnyContent,
25
25
  Content,
@@ -33,7 +33,7 @@ export type {
33
33
  ViewContentProvider,
34
34
  ColumnDef,
35
35
  TableRow,
36
- } from "./types.js"
37
- export { getTextContent, isBuiltinContent, isCustomContent } from "./types.js"
38
- export { CodeBlockChrome } from "@tooee/renderers"
39
- export type { CodeBlockRenderer, CodeBlockRendererProps, CodeBlockHScroll } from "@tooee/renderers"
36
+ } from "./types.js";
37
+ export { getTextContent, isBuiltinContent, isCustomContent } from "./types.js";
38
+ export { CodeBlockChrome } from "@tooee/renderers";
39
+ export type { CodeBlockRenderer, CodeBlockRendererProps, CodeBlockHScroll } from "@tooee/renderers";
package/src/launch.tsx CHANGED
@@ -1,28 +1,28 @@
1
- import { launchCli } from "@tooee/shell"
2
- import type { ActionDefinition } from "@tooee/commands"
3
- import type { CodeBlockRenderer } from "@tooee/renderers"
4
- import { View } from "./View.js"
5
- import { DirectoryView } from "./DirectoryView.js"
6
- import type { ContentProvider, ContentRenderer } from "./types.js"
1
+ import { launchCli } from "@tooee/shell";
2
+ import type { ActionDefinition } from "@tooee/commands";
3
+ import type { CodeBlockRenderer } from "@tooee/renderers";
4
+ import { View } from "./view.js";
5
+ import { DirectoryView } from "./directory-view.js";
6
+ import type { ContentProvider, ContentRenderer } from "./types.js";
7
7
 
8
8
  export interface ViewLaunchOptions {
9
- contentProvider: ContentProvider
10
- actions?: ActionDefinition[]
11
- renderers?: Record<string, ContentRenderer>
9
+ contentProvider: ContentProvider;
10
+ actions?: ActionDefinition[];
11
+ renderers?: Record<string, ContentRenderer>;
12
12
  /**
13
13
  * Custom renderers for fenced code blocks in markdown content, keyed by
14
14
  * fence type (first word of the fence info string, case-insensitive).
15
15
  * Unmatched types fall back to the default syntax-highlighted code block.
16
16
  */
17
- codeBlockRenderers?: Record<string, CodeBlockRenderer>
17
+ codeBlockRenderers?: Record<string, CodeBlockRenderer>;
18
18
  }
19
19
 
20
20
  export interface DirectoryLaunchOptions {
21
- dirPath: string
22
- actions?: ActionDefinition[]
21
+ dirPath: string;
22
+ actions?: ActionDefinition[];
23
23
  }
24
24
 
25
- export async function launch(options: ViewLaunchOptions): Promise<void> {
25
+ export const launch = async function launch(options: ViewLaunchOptions): Promise<void> {
26
26
  await launchCli(
27
27
  <View
28
28
  contentProvider={options.contentProvider}
@@ -30,9 +30,11 @@ export async function launch(options: ViewLaunchOptions): Promise<void> {
30
30
  renderers={options.renderers}
31
31
  codeBlockRenderers={options.codeBlockRenderers}
32
32
  />,
33
- )
34
- }
33
+ );
34
+ };
35
35
 
36
- export async function launchDirectory(options: DirectoryLaunchOptions): Promise<void> {
37
- await launchCli(<DirectoryView dirPath={options.dirPath} actions={options.actions} />)
38
- }
36
+ export const launchDirectory = async function launchDirectory(
37
+ options: DirectoryLaunchOptions,
38
+ ): Promise<void> {
39
+ await launchCli(<DirectoryView dirPath={options.dirPath} actions={options.actions} />);
40
+ };
package/src/types.ts CHANGED
@@ -1,57 +1,57 @@
1
- import type { ReactNode } from "react"
2
- import type { ColumnDef, SourceLineRow, TableRow } from "@tooee/renderers"
3
- import type { DocumentController } from "@tooee/shell"
4
- import type { MarkSet } from "@tooee/marks"
1
+ import type { ReactNode } from "react";
2
+ import type { ColumnDef, SourceLineRow, TableRow } from "@tooee/renderers";
3
+ import type { DocumentController } from "@tooee/shell";
4
+ import type { MarkSet } from "@tooee/marks";
5
5
 
6
6
  // === Built-in content types ===
7
7
 
8
- export type Content = MarkdownContent | CodeContent | TextContent | TableContent
8
+ export type Content = MarkdownContent | CodeContent | TextContent | TableContent;
9
9
 
10
- export type ContentFormat = Content["format"]
10
+ export type ContentFormat = Content["format"];
11
11
 
12
12
  interface BaseContent {
13
- title?: string
14
- getTextContent?: () => string
13
+ title?: string;
14
+ getTextContent?: () => string;
15
15
  }
16
16
 
17
17
  export interface MarkdownContent extends BaseContent {
18
- format: "markdown"
19
- markdown: string
18
+ format: "markdown";
19
+ markdown: string;
20
20
  }
21
21
 
22
22
  export interface CodeContent extends BaseContent {
23
- format: "code"
24
- code: string
25
- language?: string
23
+ format: "code";
24
+ code: string;
25
+ language?: string;
26
26
  }
27
27
 
28
28
  export interface TextContent extends BaseContent {
29
- format: "text"
30
- text: string
29
+ format: "text";
30
+ text: string;
31
31
  }
32
32
 
33
33
  export interface TableContent extends BaseContent {
34
- format: "table"
35
- columns: ColumnDef[]
36
- rows: TableRow[]
34
+ format: "table";
35
+ columns: ColumnDef[];
36
+ rows: TableRow[];
37
37
  }
38
38
 
39
39
  // === Custom content ===
40
40
 
41
41
  export interface CustomContent<T = unknown> {
42
- format: string
43
- data: T
44
- title?: string
45
- getTextContent?: () => string
42
+ format: string;
43
+ data: T;
44
+ title?: string;
45
+ getTextContent?: () => string;
46
46
  }
47
47
 
48
48
  /** Content or custom content — used where custom formats are accepted */
49
- export type AnyContent = Content | CustomContent
49
+ export type AnyContent = Content | CustomContent;
50
50
 
51
51
  // === Custom renderers ===
52
52
 
53
53
  export interface ContentRendererProps {
54
- content: CustomContent
54
+ content: CustomContent;
55
55
  /**
56
56
  * The host's document controller. Its rows are the content's source lines
57
57
  * (`SourceLineRow`), which is also the unit navigation, search and copy work
@@ -65,98 +65,127 @@ export interface ContentRendererProps {
65
65
  * `selectRow` stands down while a modal overlay is open, so it can be wired
66
66
  * unconditionally.
67
67
  */
68
- document: DocumentController<SourceLineRow>
68
+ document: DocumentController<SourceLineRow>;
69
69
  }
70
70
 
71
- export type ContentRenderer = (props: ContentRendererProps) => ReactNode
71
+ export type ContentRenderer = (props: ContentRendererProps) => ReactNode;
72
72
 
73
73
  // === Streaming ===
74
74
 
75
- export type AppendableFormat = Extract<ContentFormat, "markdown" | "code" | "text">
75
+ export type AppendableFormat = Extract<ContentFormat, "markdown" | "code" | "text">;
76
76
 
77
77
  export type ContentChunk =
78
78
  | {
79
- type: "append"
80
- format: AppendableFormat
81
- data: string
82
- language?: string
79
+ type: "append";
80
+ format: AppendableFormat;
81
+ data: string;
82
+ language?: string;
83
83
  }
84
84
  | {
85
- type: "replace"
86
- content: AnyContent
85
+ type: "replace";
86
+ content: AnyContent;
87
87
  }
88
88
  | {
89
- type: "patch"
90
- apply: (current: AnyContent | null) => AnyContent
89
+ type: "patch";
90
+ apply: (current: AnyContent | null) => AnyContent;
91
91
  }
92
92
  | {
93
- type: "marks"
94
- set: MarkSet
95
- }
93
+ type: "marks";
94
+ set: MarkSet;
95
+ };
96
96
 
97
97
  // === Provider ===
98
98
 
99
99
  export interface ContentProvider {
100
- load(): AnyContent | Promise<AnyContent> | AsyncIterable<ContentChunk>
101
- format?: string
102
- title?: string
100
+ load: () => AnyContent | Promise<AnyContent> | AsyncIterable<ContentChunk>;
101
+ format?: string;
102
+ title?: string;
103
103
  /** Static marks to apply to the content. Merged with nav marks in View. */
104
- marks?: MarkSet[]
104
+ marks?: MarkSet[];
105
105
  }
106
106
 
107
107
  // === Compat aliases ===
108
108
 
109
- export type ViewContent = Content
110
- export type ViewContentProvider = ContentProvider
111
- export type { ColumnDef, TableRow } from "@tooee/renderers"
109
+ export type ViewContent = Content;
110
+ export type ViewContentProvider = ContentProvider;
111
+ export type { ColumnDef, TableRow } from "@tooee/renderers";
112
112
 
113
113
  // === Utilities ===
114
114
 
115
- const BUILTIN_FORMATS: Set<string> = new Set(["markdown", "code", "text", "table"])
115
+ const BUILTIN_FORMATS = new Set<string>(["markdown", "code", "text", "table"]);
116
116
 
117
- export function isBuiltinContent(content: AnyContent): content is Content {
118
- return BUILTIN_FORMATS.has(content.format)
119
- }
117
+ export const isBuiltinContent = function isBuiltinContent(content: AnyContent): content is Content {
118
+ return BUILTIN_FORMATS.has(content.format);
119
+ };
120
120
 
121
- export function isCustomContent(content: AnyContent): content is CustomContent {
122
- return !BUILTIN_FORMATS.has(content.format)
123
- }
121
+ export const isCustomContent = function isCustomContent(
122
+ content: AnyContent,
123
+ ): content is CustomContent {
124
+ return !BUILTIN_FORMATS.has(content.format);
125
+ };
126
+
127
+ const stringifyCell = function stringifyCell(value: unknown): string {
128
+ if (value === null || value === undefined) {
129
+ return "";
130
+ }
131
+ if (typeof value === "string") {
132
+ return value;
133
+ }
134
+ if (typeof value === "number" || typeof value === "boolean") {
135
+ return String(value);
136
+ }
137
+ if (typeof value === "string") {
138
+ return value;
139
+ }
140
+ if (
141
+ typeof value === "number" ||
142
+ typeof value === "bigint" ||
143
+ typeof value === "boolean" ||
144
+ typeof value === "symbol"
145
+ ) {
146
+ return value.toString();
147
+ }
148
+ if (value === null || value === undefined) {
149
+ return String(value);
150
+ }
151
+ if (typeof value === "function") {
152
+ return Function.prototype.toString.call(value);
153
+ }
154
+ try {
155
+ return JSON.stringify(value);
156
+ } catch {
157
+ return Array.isArray(value) ? value.join(",") : Object.prototype.toString.call(value);
158
+ }
159
+ };
124
160
 
125
- export function getTextContent(content: AnyContent): string {
161
+ export const getTextContent = function getTextContent(content: AnyContent): string {
126
162
  if (content.getTextContent) {
127
- return content.getTextContent()
163
+ return content.getTextContent();
128
164
  }
129
165
 
130
166
  if (isCustomContent(content)) {
131
- return ""
167
+ return "";
132
168
  }
133
169
 
134
170
  switch (content.format) {
135
- case "markdown":
136
- return content.markdown
137
- case "code":
138
- return content.code
139
- case "text":
140
- return content.text
171
+ case "markdown": {
172
+ return content.markdown;
173
+ }
174
+ case "code": {
175
+ return content.code;
176
+ }
177
+ case "text": {
178
+ return content.text;
179
+ }
141
180
  case "table": {
142
- const headers = content.columns.map((column) => column.header ?? column.key)
181
+ const headers = content.columns.map((column) => column.header ?? column.key);
143
182
  const rowLines = content.rows.map((row) =>
144
183
  content.columns.map((column) => stringifyCell(row[column.key])).join("\t"),
145
- )
146
- return [headers.join("\t"), ...rowLines].join("\n")
184
+ );
185
+ return [headers.join("\t"), ...rowLines].join("\n");
186
+ }
187
+ default: {
188
+ return "";
147
189
  }
148
- default:
149
- return ""
150
- }
151
- }
152
-
153
- function stringifyCell(value: unknown): string {
154
- if (value == null) return ""
155
- if (typeof value === "string") return value
156
- if (typeof value === "number" || typeof value === "boolean") return String(value)
157
- try {
158
- return JSON.stringify(value)
159
- } catch {
160
- return String(value)
161
190
  }
162
- }
191
+ };