@tooee/view 0.2.2 → 0.2.4
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/dist/View.d.ts +4 -2
- package/dist/View.d.ts.map +1 -1
- package/dist/View.js +3 -5
- package/dist/View.js.map +1 -1
- package/dist/components/subviews/MarkdownSubview.d.ts +3 -2
- package/dist/components/subviews/MarkdownSubview.d.ts.map +1 -1
- package/dist/components/subviews/MarkdownSubview.js +2 -2
- package/dist/components/subviews/MarkdownSubview.js.map +1 -1
- package/dist/content-loader-store.d.ts +53 -0
- package/dist/content-loader-store.d.ts.map +1 -0
- package/dist/content-loader-store.js +111 -0
- package/dist/content-loader-store.js.map +1 -0
- package/dist/hooks/useContentLoader.d.ts +6 -6
- package/dist/hooks/useContentLoader.d.ts.map +1 -1
- package/dist/hooks/useContentLoader.js +33 -107
- package/dist/hooks/useContentLoader.js.map +1 -1
- package/package.json +12 -10
- package/src/View.tsx +17 -10
- package/src/components/subviews/MarkdownSubview.tsx +4 -0
- package/src/content-loader-store.ts +154 -0
- package/src/hooks/useContentLoader.ts +47 -128
package/dist/View.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ActionDefinition } from "@tooee/commands";
|
|
2
|
-
import type { CodeBlockRenderer } from "@tooee/renderers";
|
|
2
|
+
import type { CodeBlockRenderer, MarkdownLinkHandler } from "@tooee/renderers";
|
|
3
3
|
import { type ContentProvider, type ContentRenderer } from "./types.js";
|
|
4
4
|
interface ViewProps {
|
|
5
5
|
contentProvider: ContentProvider;
|
|
@@ -10,7 +10,9 @@ interface ViewProps {
|
|
|
10
10
|
* fence type (first word of the fence info string, case-insensitive).
|
|
11
11
|
*/
|
|
12
12
|
codeBlockRenderers?: Record<string, CodeBlockRenderer>;
|
|
13
|
+
/** Handles primary-button activation of inline Markdown links. */
|
|
14
|
+
onMarkdownLinkActivate?: MarkdownLinkHandler;
|
|
13
15
|
}
|
|
14
|
-
export declare function View({ contentProvider, actions, renderers, codeBlockRenderers }: ViewProps): import("react").ReactNode;
|
|
16
|
+
export declare function View({ contentProvider, actions, renderers, codeBlockRenderers, onMarkdownLinkActivate, }: ViewProps): import("react").ReactNode;
|
|
15
17
|
export {};
|
|
16
18
|
//# sourceMappingURL=View.d.ts.map
|
package/dist/View.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"View.d.ts","sourceRoot":"","sources":["../src/View.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"View.d.ts","sourceRoot":"","sources":["../src/View.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAC9E,OAAO,EAAmB,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AASxF,UAAU,SAAS;IACjB,eAAe,EAAE,eAAe,CAAA;IAChC,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAA;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC3C;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IACtD,kEAAkE;IAClE,sBAAsB,CAAC,EAAE,mBAAmB,CAAA;CAC7C;AAED,wBAAgB,IAAI,CAAC,EACnB,eAAe,EACf,OAAO,EACP,SAAS,EACT,kBAAkB,EAClB,sBAAsB,GACvB,EAAE,SAAS,6BAyEX"}
|
package/dist/View.js
CHANGED
|
@@ -4,11 +4,9 @@ import { useTheme } from "@tooee/themes";
|
|
|
4
4
|
import { isCustomContent } from "./types.js";
|
|
5
5
|
import { useContentLoader } from "./hooks/useContentLoader.js";
|
|
6
6
|
import { MarkdownSubview, CodeSubview, TableSubview, CustomSubview, } from "./components/subviews/index.js";
|
|
7
|
-
export function View({ contentProvider, actions, renderers, codeBlockRenderers }) {
|
|
7
|
+
export function View({ contentProvider, actions, renderers, codeBlockRenderers, onMarkdownLinkActivate, }) {
|
|
8
8
|
const { theme } = useTheme();
|
|
9
|
-
const
|
|
10
|
-
const reload = useCallback(() => setReloadTrigger((n) => n + 1), []);
|
|
11
|
-
const { content, streaming, error, providerMarks } = useContentLoader(contentProvider, reloadTrigger);
|
|
9
|
+
const { content, streaming, error, providerMarks, reload } = useContentLoader(contentProvider);
|
|
12
10
|
const [userMarks, setUserMarks] = useState([]);
|
|
13
11
|
const setMarkSet = useCallback((set) => {
|
|
14
12
|
setUserMarks((prev) => {
|
|
@@ -47,7 +45,7 @@ export function View({ contentProvider, actions, renderers, codeBlockRenderers }
|
|
|
47
45
|
}
|
|
48
46
|
switch (content.format) {
|
|
49
47
|
case "markdown":
|
|
50
|
-
return (_jsx(MarkdownSubview, { content: content, codeBlockRenderers: codeBlockRenderers, ...shared }));
|
|
48
|
+
return (_jsx(MarkdownSubview, { content: content, codeBlockRenderers: codeBlockRenderers, onLinkActivate: onMarkdownLinkActivate, ...shared }));
|
|
51
49
|
case "code":
|
|
52
50
|
case "text":
|
|
53
51
|
return _jsx(CodeSubview, { content: content, ...shared });
|
package/dist/View.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"View.js","sourceRoot":"","sources":["../src/View.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAIxC,OAAO,EAAE,eAAe,EAA8C,MAAM,YAAY,CAAA;AACxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAC9D,OAAO,EACL,eAAe,EACf,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"View.js","sourceRoot":"","sources":["../src/View.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type CodeBlockRenderer } from "@tooee/renderers";
|
|
1
|
+
import { type CodeBlockRenderer, type MarkdownLinkHandler } from "@tooee/renderers";
|
|
2
2
|
import type { MarkdownContent } from "../../types.js";
|
|
3
3
|
import type { SubviewProps } from "./types.js";
|
|
4
4
|
interface MarkdownSubviewProps extends SubviewProps {
|
|
5
5
|
content: MarkdownContent;
|
|
6
6
|
codeBlockRenderers?: Record<string, CodeBlockRenderer>;
|
|
7
|
+
onLinkActivate?: MarkdownLinkHandler;
|
|
7
8
|
}
|
|
8
|
-
export declare function MarkdownSubview({ content, codeBlockRenderers, decorations, actions, ...screen }: MarkdownSubviewProps): import("react").ReactNode;
|
|
9
|
+
export declare function MarkdownSubview({ content, codeBlockRenderers, onLinkActivate, decorations, actions, ...screen }: MarkdownSubviewProps): import("react").ReactNode;
|
|
9
10
|
export {};
|
|
10
11
|
//# sourceMappingURL=MarkdownSubview.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownSubview.d.ts","sourceRoot":"","sources":["../../../src/components/subviews/MarkdownSubview.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"MarkdownSubview.d.ts","sourceRoot":"","sources":["../../../src/components/subviews/MarkdownSubview.tsx"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EAEzB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,UAAU,oBAAqB,SAAQ,YAAY;IACjD,OAAO,EAAE,eAAe,CAAA;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IACtD,cAAc,CAAC,EAAE,mBAAmB,CAAA;CACrC;AAeD,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,kBAAkB,EAClB,cAAc,EACd,WAAW,EACX,OAAO,EACP,GAAG,MAAM,EACV,EAAE,oBAAoB,6BAuEtB"}
|
|
@@ -16,7 +16,7 @@ const MARKDOWN_BLOCK_ADAPTER = {
|
|
|
16
16
|
getText: (block) => getFlatBlockText(block),
|
|
17
17
|
getSource: (block) => block.source,
|
|
18
18
|
};
|
|
19
|
-
export function MarkdownSubview({ content, codeBlockRenderers, decorations, actions, ...screen }) {
|
|
19
|
+
export function MarkdownSubview({ content, codeBlockRenderers, onLinkActivate, decorations, actions, ...screen }) {
|
|
20
20
|
const textContent = content.markdown;
|
|
21
21
|
const lineCount = useMemo(() => textContent.split("\n").length, [textContent]);
|
|
22
22
|
const blocks = useMemo(() => flattenMarkdown(content.markdown), [content.markdown]);
|
|
@@ -61,6 +61,6 @@ export function MarkdownSubview({ content, codeBlockRenderers, decorations, acti
|
|
|
61
61
|
{ label: "Format:", value: content.format },
|
|
62
62
|
{ label: "Lines:", value: String(lineCount) },
|
|
63
63
|
], [content.format, lineCount]);
|
|
64
|
-
return (_jsx(ViewScreen, { content: content, controller: document, actions: actions, statusItems: statusItems, ...screen, children: _jsx(MarkdownView, { content: content.markdown, blocks: blocks, showLineNumbers: showLineNumbers, document: document, hScrollableBlocksRef: hScrollableBlocksRef, codeBlockRenderers: codeBlockRenderers }) }));
|
|
64
|
+
return (_jsx(ViewScreen, { content: content, controller: document, actions: actions, statusItems: statusItems, ...screen, children: _jsx(MarkdownView, { content: content.markdown, blocks: blocks, showLineNumbers: showLineNumbers, document: document, hScrollableBlocksRef: hScrollableBlocksRef, codeBlockRenderers: codeBlockRenderers, onLinkActivate: onLinkActivate }) }));
|
|
65
65
|
}
|
|
66
66
|
//# sourceMappingURL=MarkdownSubview.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MarkdownSubview.js","sourceRoot":"","sources":["../../../src/components/subviews/MarkdownSubview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAA;AAEvC,OAAO,EACL,YAAY,EACZ,eAAe,EACf,gBAAgB,
|
|
1
|
+
{"version":3,"file":"MarkdownSubview.js","sourceRoot":"","sources":["../../../src/components/subviews/MarkdownSubview.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { MarkSet } from "@tooee/marks";
|
|
2
|
+
import type { AnyContent, ContentChunk } from "./types.js";
|
|
3
|
+
export type ContentLoaderStatus = "idle" | "loading" | "streaming" | "ready" | "error";
|
|
4
|
+
export interface ContentLoaderContext {
|
|
5
|
+
status: ContentLoaderStatus;
|
|
6
|
+
requestId: number;
|
|
7
|
+
loadSeq: number;
|
|
8
|
+
content: AnyContent | null;
|
|
9
|
+
error: string | null;
|
|
10
|
+
providerMarks: MarkSet[];
|
|
11
|
+
title: string | undefined;
|
|
12
|
+
}
|
|
13
|
+
export type ContentLoaderEvents = {
|
|
14
|
+
loadStarted: {
|
|
15
|
+
marks: MarkSet[];
|
|
16
|
+
title?: string;
|
|
17
|
+
};
|
|
18
|
+
streamStarted: {
|
|
19
|
+
requestId: number;
|
|
20
|
+
format: string;
|
|
21
|
+
};
|
|
22
|
+
chunkReceived: {
|
|
23
|
+
requestId: number;
|
|
24
|
+
chunk: ContentChunk;
|
|
25
|
+
};
|
|
26
|
+
loaded: {
|
|
27
|
+
requestId: number;
|
|
28
|
+
content: AnyContent;
|
|
29
|
+
};
|
|
30
|
+
streamEnded: {
|
|
31
|
+
requestId: number;
|
|
32
|
+
};
|
|
33
|
+
loadFailed: {
|
|
34
|
+
requestId: number;
|
|
35
|
+
error: string;
|
|
36
|
+
};
|
|
37
|
+
loadCancelled: {
|
|
38
|
+
requestId: number;
|
|
39
|
+
};
|
|
40
|
+
reloadRequested: {};
|
|
41
|
+
};
|
|
42
|
+
export declare function isAsyncIterable(value: unknown): value is AsyncIterable<ContentChunk>;
|
|
43
|
+
export declare function createEmptyContent(format: string, title?: string): AnyContent;
|
|
44
|
+
export declare function applyContentChunk(current: AnyContent | null, chunk: ContentChunk, title?: string): AnyContent;
|
|
45
|
+
export declare function normalizeError(error: unknown): string;
|
|
46
|
+
export declare function createContentLoaderStore(): import("@xstate/store").Store<ContentLoaderContext, ContentLoaderEvents, never>;
|
|
47
|
+
export declare const selectContent: (ctx: ContentLoaderContext) => AnyContent | null;
|
|
48
|
+
export declare const selectStatus: (ctx: ContentLoaderContext) => ContentLoaderStatus;
|
|
49
|
+
export declare const selectStreaming: (ctx: ContentLoaderContext) => boolean;
|
|
50
|
+
export declare const selectError: (ctx: ContentLoaderContext) => string | null;
|
|
51
|
+
export declare const selectProviderMarks: (ctx: ContentLoaderContext) => MarkSet[];
|
|
52
|
+
export declare const selectLoadSeq: (ctx: ContentLoaderContext) => number;
|
|
53
|
+
//# sourceMappingURL=content-loader-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-loader-store.d.ts","sourceRoot":"","sources":["../src/content-loader-store.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAW,YAAY,EAAgC,MAAM,YAAY,CAAA;AAEjG,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,OAAO,CAAA;AAEtF,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,mBAAmB,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,UAAU,GAAG,IAAI,CAAA;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,aAAa,EAAE,OAAO,EAAE,CAAA;IACxB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CAC1B;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE;QAAE,KAAK,EAAE,OAAO,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,aAAa,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAA;IACpD,aAAa,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,YAAY,CAAA;KAAE,CAAA;IACzD,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,UAAU,CAAA;KAAE,CAAA;IAClD,WAAW,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IAClC,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,aAAa,EAAE;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,eAAe,EAAE,EAAE,CAAA;CACpB,CAAA;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,aAAa,CAAC,YAAY,CAAC,CAEpF;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,UAAU,CAa7E;AAaD,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,GAAG,IAAI,EAC1B,KAAK,EAAE,YAAY,EACnB,KAAK,CAAC,EAAE,MAAM,GACb,UAAU,CA0BZ;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAErD;AAED,wBAAgB,wBAAwB,oFAqDvC;AAED,eAAO,MAAM,aAAa,GAAI,KAAK,oBAAoB,sBAAgB,CAAA;AACvE,eAAO,MAAM,YAAY,GAAI,KAAK,oBAAoB,wBAAe,CAAA;AACrE,eAAO,MAAM,eAAe,GAAI,KAAK,oBAAoB,YAA+B,CAAA;AACxF,eAAO,MAAM,WAAW,GAAI,KAAK,oBAAoB,kBAAc,CAAA;AACnE,eAAO,MAAM,mBAAmB,GAAI,KAAK,oBAAoB,cAAsB,CAAA;AACnF,eAAO,MAAM,aAAa,GAAI,KAAK,oBAAoB,WAAgB,CAAA"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store";
|
|
2
|
+
export function isAsyncIterable(value) {
|
|
3
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
4
|
+
}
|
|
5
|
+
export function createEmptyContent(format, title) {
|
|
6
|
+
switch (format) {
|
|
7
|
+
case "markdown":
|
|
8
|
+
return { format, markdown: "", title };
|
|
9
|
+
case "code":
|
|
10
|
+
return { format, code: "", title };
|
|
11
|
+
case "text":
|
|
12
|
+
return { format, text: "", title };
|
|
13
|
+
case "table":
|
|
14
|
+
return { format, columns: [], rows: [], title };
|
|
15
|
+
default:
|
|
16
|
+
return { format, data: undefined, title };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function ensureContentFormat(current, format, title) {
|
|
20
|
+
if (!current || current.format !== format) {
|
|
21
|
+
return createEmptyContent(format, title);
|
|
22
|
+
}
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
export function applyContentChunk(current, chunk, title) {
|
|
26
|
+
switch (chunk.type) {
|
|
27
|
+
case "replace":
|
|
28
|
+
return chunk.content;
|
|
29
|
+
case "append":
|
|
30
|
+
if (chunk.format === "markdown") {
|
|
31
|
+
const target = ensureContentFormat(current, "markdown", title);
|
|
32
|
+
return { ...target, markdown: target.markdown + chunk.data };
|
|
33
|
+
}
|
|
34
|
+
if (chunk.format === "code") {
|
|
35
|
+
const target = ensureContentFormat(current, "code", title);
|
|
36
|
+
return {
|
|
37
|
+
...target,
|
|
38
|
+
code: target.code + chunk.data,
|
|
39
|
+
language: chunk.language ?? target.language,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
{
|
|
43
|
+
const target = ensureContentFormat(current, "text", title);
|
|
44
|
+
return { ...target, text: target.text + chunk.data };
|
|
45
|
+
}
|
|
46
|
+
case "patch":
|
|
47
|
+
return chunk.apply(current);
|
|
48
|
+
default:
|
|
49
|
+
return current ?? createEmptyContent("markdown", title);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function normalizeError(error) {
|
|
53
|
+
return error instanceof Error ? error.message : String(error);
|
|
54
|
+
}
|
|
55
|
+
export function createContentLoaderStore() {
|
|
56
|
+
return createStore({
|
|
57
|
+
context: {
|
|
58
|
+
status: "idle",
|
|
59
|
+
requestId: 0,
|
|
60
|
+
loadSeq: 0,
|
|
61
|
+
content: null,
|
|
62
|
+
error: null,
|
|
63
|
+
providerMarks: [],
|
|
64
|
+
title: undefined,
|
|
65
|
+
},
|
|
66
|
+
on: {
|
|
67
|
+
loadStarted: (ctx, event) => ({
|
|
68
|
+
...ctx,
|
|
69
|
+
status: "loading",
|
|
70
|
+
requestId: ctx.requestId + 1,
|
|
71
|
+
error: null,
|
|
72
|
+
providerMarks: event.marks,
|
|
73
|
+
title: event.title,
|
|
74
|
+
}),
|
|
75
|
+
streamStarted: (ctx, event) => event.requestId !== ctx.requestId
|
|
76
|
+
? ctx
|
|
77
|
+
: {
|
|
78
|
+
...ctx,
|
|
79
|
+
status: "streaming",
|
|
80
|
+
content: createEmptyContent(event.format, ctx.title),
|
|
81
|
+
},
|
|
82
|
+
chunkReceived: (ctx, event) => {
|
|
83
|
+
if (event.requestId !== ctx.requestId)
|
|
84
|
+
return ctx;
|
|
85
|
+
if (event.chunk.type === "marks") {
|
|
86
|
+
const markSet = event.chunk.set;
|
|
87
|
+
const providerMarks = [
|
|
88
|
+
...ctx.providerMarks.filter((set) => set.namespace !== markSet.namespace),
|
|
89
|
+
markSet,
|
|
90
|
+
];
|
|
91
|
+
return { ...ctx, providerMarks };
|
|
92
|
+
}
|
|
93
|
+
return { ...ctx, content: applyContentChunk(ctx.content, event.chunk, ctx.title) };
|
|
94
|
+
},
|
|
95
|
+
loaded: (ctx, event) => event.requestId !== ctx.requestId
|
|
96
|
+
? ctx
|
|
97
|
+
: { ...ctx, status: "ready", content: event.content },
|
|
98
|
+
streamEnded: (ctx, event) => event.requestId !== ctx.requestId ? ctx : { ...ctx, status: "ready" },
|
|
99
|
+
loadFailed: (ctx, event) => event.requestId !== ctx.requestId ? ctx : { ...ctx, status: "error", error: event.error },
|
|
100
|
+
loadCancelled: (ctx, event) => event.requestId !== ctx.requestId ? ctx : { ...ctx, requestId: ctx.requestId + 1 },
|
|
101
|
+
reloadRequested: (ctx) => ({ ...ctx, loadSeq: ctx.loadSeq + 1 }),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export const selectContent = (ctx) => ctx.content;
|
|
106
|
+
export const selectStatus = (ctx) => ctx.status;
|
|
107
|
+
export const selectStreaming = (ctx) => ctx.status === "streaming";
|
|
108
|
+
export const selectError = (ctx) => ctx.error;
|
|
109
|
+
export const selectProviderMarks = (ctx) => ctx.providerMarks;
|
|
110
|
+
export const selectLoadSeq = (ctx) => ctx.loadSeq;
|
|
111
|
+
//# sourceMappingURL=content-loader-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content-loader-store.js","sourceRoot":"","sources":["../src/content-loader-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
content: AnyContent | null;
|
|
1
|
+
import type { ContentProvider } from "../types.js";
|
|
2
|
+
export declare function useContentLoader(contentProvider: ContentProvider): {
|
|
3
|
+
content: import("../types.js").AnyContent | null;
|
|
5
4
|
streaming: boolean;
|
|
6
5
|
error: string | null;
|
|
7
|
-
providerMarks: MarkSet[];
|
|
8
|
-
|
|
6
|
+
providerMarks: import("@tooee/marks").MarkSet[];
|
|
7
|
+
status: import("../content-loader-store.js").ContentLoaderStatus;
|
|
8
|
+
reload: () => void;
|
|
9
9
|
};
|
|
10
10
|
//# sourceMappingURL=useContentLoader.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContentLoader.d.ts","sourceRoot":"","sources":["../../src/hooks/useContentLoader.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useContentLoader.d.ts","sourceRoot":"","sources":["../../src/hooks/useContentLoader.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAalD,wBAAgB,gBAAgB,CAAC,eAAe,EAAE,eAAe;;;;;;;EAwDhE"}
|
|
@@ -1,129 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
case "markdown":
|
|
8
|
-
return { format, markdown: "", title };
|
|
9
|
-
case "code":
|
|
10
|
-
return { format, code: "", title };
|
|
11
|
-
case "text":
|
|
12
|
-
return { format, text: "", title };
|
|
13
|
-
case "table":
|
|
14
|
-
return { format, columns: [], rows: [], title };
|
|
15
|
-
default:
|
|
16
|
-
return { format, data: undefined, title };
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function ensureContentFormat(current, format, title) {
|
|
20
|
-
if (!current || current.format !== format) {
|
|
21
|
-
return createEmptyContent(format, title);
|
|
22
|
-
}
|
|
23
|
-
return current;
|
|
24
|
-
}
|
|
25
|
-
function applyContentChunk(current, chunk, title) {
|
|
26
|
-
switch (chunk.type) {
|
|
27
|
-
case "replace":
|
|
28
|
-
return chunk.content;
|
|
29
|
-
case "append":
|
|
30
|
-
if (chunk.format === "markdown") {
|
|
31
|
-
const target = ensureContentFormat(current, "markdown", title);
|
|
32
|
-
return { ...target, markdown: target.markdown + chunk.data };
|
|
33
|
-
}
|
|
34
|
-
if (chunk.format === "code") {
|
|
35
|
-
const target = ensureContentFormat(current, "code", title);
|
|
36
|
-
return {
|
|
37
|
-
...target,
|
|
38
|
-
code: target.code + chunk.data,
|
|
39
|
-
language: chunk.language ?? target.language,
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
{
|
|
43
|
-
const target = ensureContentFormat(current, "text", title);
|
|
44
|
-
return { ...target, text: target.text + chunk.data };
|
|
45
|
-
}
|
|
46
|
-
case "patch":
|
|
47
|
-
return chunk.apply(current);
|
|
48
|
-
default:
|
|
49
|
-
return current ?? createEmptyContent("markdown", title);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
export function useContentLoader(contentProvider, reloadTrigger) {
|
|
53
|
-
const [content, setContent] = useState(null);
|
|
54
|
-
const [streaming, setStreaming] = useState(false);
|
|
55
|
-
const [error, setError] = useState(null);
|
|
56
|
-
const [providerMarks, setProviderMarks] = useState([]);
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useSelector } from "@xstate/store-react";
|
|
3
|
+
import { createContentLoaderStore, isAsyncIterable, normalizeError, selectContent, selectError, selectLoadSeq, selectProviderMarks, selectStatus, selectStreaming, } from "../content-loader-store.js";
|
|
4
|
+
export function useContentLoader(contentProvider) {
|
|
5
|
+
const [store] = useState(createContentLoaderStore);
|
|
6
|
+
const loadSeq = useSelector(store, (snapshot) => selectLoadSeq(snapshot.context));
|
|
57
7
|
useEffect(() => {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
8
|
+
store.trigger.loadStarted({
|
|
9
|
+
marks: contentProvider.marks ?? [],
|
|
10
|
+
title: contentProvider.title,
|
|
11
|
+
});
|
|
12
|
+
const requestId = store.getSnapshot().context.requestId;
|
|
62
13
|
const loaded = contentProvider.load();
|
|
63
14
|
if (isAsyncIterable(loaded)) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
setContent(current);
|
|
69
|
-
setStreaming(true);
|
|
70
|
-
// Hold the iterator explicitly so cleanup can close it: a cleanup that
|
|
71
|
-
// only flips `cancelled` leaves a pending next() (and the provider's
|
|
72
|
-
// subprocess/file handle/network stream) alive until the next chunk.
|
|
15
|
+
store.trigger.streamStarted({
|
|
16
|
+
requestId,
|
|
17
|
+
format: contentProvider.format ?? "markdown",
|
|
18
|
+
});
|
|
73
19
|
const iterator = loaded[Symbol.asyncIterator]();
|
|
74
20
|
(async () => {
|
|
75
21
|
try {
|
|
76
22
|
while (true) {
|
|
77
23
|
const result = await iterator.next();
|
|
78
|
-
if (
|
|
24
|
+
if (result.done)
|
|
79
25
|
break;
|
|
80
|
-
|
|
81
|
-
if (chunk.type === "marks") {
|
|
82
|
-
// Streamed mark set: merge by replacing any existing set with same namespace
|
|
83
|
-
setProviderMarks((prev) => {
|
|
84
|
-
const filtered = prev.filter((s) => s.namespace !== chunk.set.namespace);
|
|
85
|
-
return [...filtered, chunk.set];
|
|
86
|
-
});
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
current = applyContentChunk(current, chunk, title);
|
|
90
|
-
setContent(current);
|
|
26
|
+
store.trigger.chunkReceived({ requestId, chunk: result.value });
|
|
91
27
|
}
|
|
28
|
+
store.trigger.streamEnded({ requestId });
|
|
92
29
|
}
|
|
93
|
-
catch (
|
|
94
|
-
|
|
95
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
finally {
|
|
99
|
-
if (!cancelled) {
|
|
100
|
-
setStreaming(false);
|
|
101
|
-
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
store.trigger.loadFailed({ requestId, error: normalizeError(error) });
|
|
102
32
|
}
|
|
103
33
|
})();
|
|
104
34
|
return () => {
|
|
105
|
-
|
|
106
|
-
// Close the iterator to release provider resources mid-stream.
|
|
35
|
+
store.trigger.loadCancelled({ requestId });
|
|
107
36
|
Promise.resolve(iterator.return?.()).catch(() => { });
|
|
108
37
|
};
|
|
109
38
|
}
|
|
110
39
|
if (loaded instanceof Promise) {
|
|
111
|
-
let active = true;
|
|
112
40
|
loaded
|
|
113
|
-
.then((
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
})
|
|
117
|
-
.catch((err) => {
|
|
118
|
-
if (active)
|
|
119
|
-
setError(err instanceof Error ? err.message : String(err));
|
|
120
|
-
});
|
|
121
|
-
return () => {
|
|
122
|
-
active = false;
|
|
123
|
-
};
|
|
41
|
+
.then((content) => store.trigger.loaded({ requestId, content }))
|
|
42
|
+
.catch((error) => store.trigger.loadFailed({ requestId, error: normalizeError(error) }));
|
|
43
|
+
return () => store.trigger.loadCancelled({ requestId });
|
|
124
44
|
}
|
|
125
|
-
|
|
126
|
-
}, [contentProvider,
|
|
127
|
-
|
|
45
|
+
store.trigger.loaded({ requestId, content: loaded });
|
|
46
|
+
}, [contentProvider, loadSeq, store]);
|
|
47
|
+
const content = useSelector(store, (snapshot) => selectContent(snapshot.context));
|
|
48
|
+
const streaming = useSelector(store, (snapshot) => selectStreaming(snapshot.context));
|
|
49
|
+
const error = useSelector(store, (snapshot) => selectError(snapshot.context));
|
|
50
|
+
const providerMarks = useSelector(store, (snapshot) => selectProviderMarks(snapshot.context));
|
|
51
|
+
const status = useSelector(store, (snapshot) => selectStatus(snapshot.context));
|
|
52
|
+
const reload = useCallback(() => store.trigger.reloadRequested({}), [store]);
|
|
53
|
+
return { content, streaming, error, providerMarks, status, reload };
|
|
128
54
|
}
|
|
129
55
|
//# sourceMappingURL=useContentLoader.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useContentLoader.js","sourceRoot":"","sources":["../../src/hooks/useContentLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"useContentLoader.js","sourceRoot":"","sources":["../../src/hooks/useContentLoader.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tooee/view",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Terminal content viewer for markdown, code, text, and tables",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -37,15 +37,17 @@
|
|
|
37
37
|
"typecheck": "tsc --noEmit"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tooee/commands": "0.2.
|
|
41
|
-
"@tooee/config": "0.2.
|
|
42
|
-
"@tooee/layout": "0.2.
|
|
43
|
-
"@tooee/marks": "0.2.
|
|
44
|
-
"@tooee/overlays": "0.2.
|
|
45
|
-
"@tooee/renderers": "0.2.
|
|
46
|
-
"@tooee/search": "0.2.
|
|
47
|
-
"@tooee/shell": "0.2.
|
|
48
|
-
"@tooee/themes": "0.2.
|
|
40
|
+
"@tooee/commands": "0.2.4",
|
|
41
|
+
"@tooee/config": "0.2.4",
|
|
42
|
+
"@tooee/layout": "0.2.4",
|
|
43
|
+
"@tooee/marks": "0.2.4",
|
|
44
|
+
"@tooee/overlays": "0.2.4",
|
|
45
|
+
"@tooee/renderers": "0.2.4",
|
|
46
|
+
"@tooee/search": "0.2.4",
|
|
47
|
+
"@tooee/shell": "0.2.4",
|
|
48
|
+
"@tooee/themes": "0.2.4",
|
|
49
|
+
"@xstate/store": "^4",
|
|
50
|
+
"@xstate/store-react": "^2",
|
|
49
51
|
"marked": "^17.0.4"
|
|
50
52
|
},
|
|
51
53
|
"devDependencies": {
|
package/src/View.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import { useCallback, useMemo, useState } from "react"
|
|
|
2
2
|
import { useTheme } from "@tooee/themes"
|
|
3
3
|
import type { ActionDefinition } from "@tooee/commands"
|
|
4
4
|
import type { MarkSet } from "@tooee/marks"
|
|
5
|
-
import type { CodeBlockRenderer } from "@tooee/renderers"
|
|
5
|
+
import type { CodeBlockRenderer, MarkdownLinkHandler } from "@tooee/renderers"
|
|
6
6
|
import { isCustomContent, type ContentProvider, type ContentRenderer } from "./types.js"
|
|
7
7
|
import { useContentLoader } from "./hooks/useContentLoader.js"
|
|
8
8
|
import {
|
|
@@ -21,17 +21,19 @@ interface ViewProps {
|
|
|
21
21
|
* fence type (first word of the fence info string, case-insensitive).
|
|
22
22
|
*/
|
|
23
23
|
codeBlockRenderers?: Record<string, CodeBlockRenderer>
|
|
24
|
+
/** Handles primary-button activation of inline Markdown links. */
|
|
25
|
+
onMarkdownLinkActivate?: MarkdownLinkHandler
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
export function View({
|
|
28
|
+
export function View({
|
|
29
|
+
contentProvider,
|
|
30
|
+
actions,
|
|
31
|
+
renderers,
|
|
32
|
+
codeBlockRenderers,
|
|
33
|
+
onMarkdownLinkActivate,
|
|
34
|
+
}: ViewProps) {
|
|
27
35
|
const { theme } = useTheme()
|
|
28
|
-
const
|
|
29
|
-
const reload = useCallback(() => setReloadTrigger((n) => n + 1), [])
|
|
30
|
-
|
|
31
|
-
const { content, streaming, error, providerMarks } = useContentLoader(
|
|
32
|
-
contentProvider,
|
|
33
|
-
reloadTrigger,
|
|
34
|
-
)
|
|
36
|
+
const { content, streaming, error, providerMarks, reload } = useContentLoader(contentProvider)
|
|
35
37
|
|
|
36
38
|
const [userMarks, setUserMarks] = useState<MarkSet[]>([])
|
|
37
39
|
|
|
@@ -89,7 +91,12 @@ export function View({ contentProvider, actions, renderers, codeBlockRenderers }
|
|
|
89
91
|
switch (content.format) {
|
|
90
92
|
case "markdown":
|
|
91
93
|
return (
|
|
92
|
-
<MarkdownSubview
|
|
94
|
+
<MarkdownSubview
|
|
95
|
+
content={content}
|
|
96
|
+
codeBlockRenderers={codeBlockRenderers}
|
|
97
|
+
onLinkActivate={onMarkdownLinkActivate}
|
|
98
|
+
{...shared}
|
|
99
|
+
/>
|
|
93
100
|
)
|
|
94
101
|
case "code":
|
|
95
102
|
case "text":
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
flattenMarkdown,
|
|
6
6
|
getFlatBlockText,
|
|
7
7
|
type CodeBlockRenderer,
|
|
8
|
+
type MarkdownLinkHandler,
|
|
8
9
|
type FlatBlock,
|
|
9
10
|
} from "@tooee/renderers"
|
|
10
11
|
import { useCommand } from "@tooee/commands"
|
|
@@ -17,6 +18,7 @@ import type { SubviewProps } from "./types.js"
|
|
|
17
18
|
interface MarkdownSubviewProps extends SubviewProps {
|
|
18
19
|
content: MarkdownContent
|
|
19
20
|
codeBlockRenderers?: Record<string, CodeBlockRenderer>
|
|
21
|
+
onLinkActivate?: MarkdownLinkHandler
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/** Columns moved per h/l press when scrolling a wide block horizontally. */
|
|
@@ -35,6 +37,7 @@ const MARKDOWN_BLOCK_ADAPTER: DocumentRowAdapter<FlatBlock> = {
|
|
|
35
37
|
export function MarkdownSubview({
|
|
36
38
|
content,
|
|
37
39
|
codeBlockRenderers,
|
|
40
|
+
onLinkActivate,
|
|
38
41
|
decorations,
|
|
39
42
|
actions,
|
|
40
43
|
...screen
|
|
@@ -105,6 +108,7 @@ export function MarkdownSubview({
|
|
|
105
108
|
document={document}
|
|
106
109
|
hScrollableBlocksRef={hScrollableBlocksRef}
|
|
107
110
|
codeBlockRenderers={codeBlockRenderers}
|
|
111
|
+
onLinkActivate={onLinkActivate}
|
|
108
112
|
/>
|
|
109
113
|
</ViewScreen>
|
|
110
114
|
)
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createStore } from "@xstate/store"
|
|
2
|
+
import type { MarkSet } from "@tooee/marks"
|
|
3
|
+
import type { AnyContent, Content, ContentChunk, ContentFormat, CustomContent } from "./types.js"
|
|
4
|
+
|
|
5
|
+
export type ContentLoaderStatus = "idle" | "loading" | "streaming" | "ready" | "error"
|
|
6
|
+
|
|
7
|
+
export interface ContentLoaderContext {
|
|
8
|
+
status: ContentLoaderStatus
|
|
9
|
+
requestId: number
|
|
10
|
+
loadSeq: number
|
|
11
|
+
content: AnyContent | null
|
|
12
|
+
error: string | null
|
|
13
|
+
providerMarks: MarkSet[]
|
|
14
|
+
title: string | undefined
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type ContentLoaderEvents = {
|
|
18
|
+
loadStarted: { marks: MarkSet[]; title?: string }
|
|
19
|
+
streamStarted: { requestId: number; format: string }
|
|
20
|
+
chunkReceived: { requestId: number; chunk: ContentChunk }
|
|
21
|
+
loaded: { requestId: number; content: AnyContent }
|
|
22
|
+
streamEnded: { requestId: number }
|
|
23
|
+
loadFailed: { requestId: number; error: string }
|
|
24
|
+
loadCancelled: { requestId: number }
|
|
25
|
+
reloadRequested: {}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isAsyncIterable(value: unknown): value is AsyncIterable<ContentChunk> {
|
|
29
|
+
return value != null && typeof value === "object" && Symbol.asyncIterator in value
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function createEmptyContent(format: string, title?: string): AnyContent {
|
|
33
|
+
switch (format) {
|
|
34
|
+
case "markdown":
|
|
35
|
+
return { format, markdown: "", title }
|
|
36
|
+
case "code":
|
|
37
|
+
return { format, code: "", title }
|
|
38
|
+
case "text":
|
|
39
|
+
return { format, text: "", title }
|
|
40
|
+
case "table":
|
|
41
|
+
return { format, columns: [], rows: [], title }
|
|
42
|
+
default:
|
|
43
|
+
return { format, data: undefined, title } as CustomContent
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureContentFormat<F extends ContentFormat>(
|
|
48
|
+
current: AnyContent | null,
|
|
49
|
+
format: F,
|
|
50
|
+
title?: string,
|
|
51
|
+
): Extract<Content, { format: F }> {
|
|
52
|
+
if (!current || current.format !== format) {
|
|
53
|
+
return createEmptyContent(format, title) as Extract<Content, { format: F }>
|
|
54
|
+
}
|
|
55
|
+
return current as Extract<Content, { format: F }>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function applyContentChunk(
|
|
59
|
+
current: AnyContent | null,
|
|
60
|
+
chunk: ContentChunk,
|
|
61
|
+
title?: string,
|
|
62
|
+
): AnyContent {
|
|
63
|
+
switch (chunk.type) {
|
|
64
|
+
case "replace":
|
|
65
|
+
return chunk.content
|
|
66
|
+
case "append":
|
|
67
|
+
if (chunk.format === "markdown") {
|
|
68
|
+
const target = ensureContentFormat(current, "markdown", title)
|
|
69
|
+
return { ...target, markdown: target.markdown + chunk.data }
|
|
70
|
+
}
|
|
71
|
+
if (chunk.format === "code") {
|
|
72
|
+
const target = ensureContentFormat(current, "code", title)
|
|
73
|
+
return {
|
|
74
|
+
...target,
|
|
75
|
+
code: target.code + chunk.data,
|
|
76
|
+
language: chunk.language ?? target.language,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
{
|
|
80
|
+
const target = ensureContentFormat(current, "text", title)
|
|
81
|
+
return { ...target, text: target.text + chunk.data }
|
|
82
|
+
}
|
|
83
|
+
case "patch":
|
|
84
|
+
return chunk.apply(current)
|
|
85
|
+
default:
|
|
86
|
+
return current ?? createEmptyContent("markdown", title)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function normalizeError(error: unknown): string {
|
|
91
|
+
return error instanceof Error ? error.message : String(error)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createContentLoaderStore() {
|
|
95
|
+
return createStore<ContentLoaderContext, ContentLoaderEvents>({
|
|
96
|
+
context: {
|
|
97
|
+
status: "idle",
|
|
98
|
+
requestId: 0,
|
|
99
|
+
loadSeq: 0,
|
|
100
|
+
content: null,
|
|
101
|
+
error: null,
|
|
102
|
+
providerMarks: [],
|
|
103
|
+
title: undefined,
|
|
104
|
+
},
|
|
105
|
+
on: {
|
|
106
|
+
loadStarted: (ctx, event) => ({
|
|
107
|
+
...ctx,
|
|
108
|
+
status: "loading",
|
|
109
|
+
requestId: ctx.requestId + 1,
|
|
110
|
+
error: null,
|
|
111
|
+
providerMarks: event.marks,
|
|
112
|
+
title: event.title,
|
|
113
|
+
}),
|
|
114
|
+
streamStarted: (ctx, event) =>
|
|
115
|
+
event.requestId !== ctx.requestId
|
|
116
|
+
? ctx
|
|
117
|
+
: {
|
|
118
|
+
...ctx,
|
|
119
|
+
status: "streaming",
|
|
120
|
+
content: createEmptyContent(event.format, ctx.title),
|
|
121
|
+
},
|
|
122
|
+
chunkReceived: (ctx, event) => {
|
|
123
|
+
if (event.requestId !== ctx.requestId) return ctx
|
|
124
|
+
if (event.chunk.type === "marks") {
|
|
125
|
+
const markSet = event.chunk.set
|
|
126
|
+
const providerMarks = [
|
|
127
|
+
...ctx.providerMarks.filter((set) => set.namespace !== markSet.namespace),
|
|
128
|
+
markSet,
|
|
129
|
+
]
|
|
130
|
+
return { ...ctx, providerMarks }
|
|
131
|
+
}
|
|
132
|
+
return { ...ctx, content: applyContentChunk(ctx.content, event.chunk, ctx.title) }
|
|
133
|
+
},
|
|
134
|
+
loaded: (ctx, event) =>
|
|
135
|
+
event.requestId !== ctx.requestId
|
|
136
|
+
? ctx
|
|
137
|
+
: { ...ctx, status: "ready", content: event.content },
|
|
138
|
+
streamEnded: (ctx, event) =>
|
|
139
|
+
event.requestId !== ctx.requestId ? ctx : { ...ctx, status: "ready" },
|
|
140
|
+
loadFailed: (ctx, event) =>
|
|
141
|
+
event.requestId !== ctx.requestId ? ctx : { ...ctx, status: "error", error: event.error },
|
|
142
|
+
loadCancelled: (ctx, event) =>
|
|
143
|
+
event.requestId !== ctx.requestId ? ctx : { ...ctx, requestId: ctx.requestId + 1 },
|
|
144
|
+
reloadRequested: (ctx) => ({ ...ctx, loadSeq: ctx.loadSeq + 1 }),
|
|
145
|
+
},
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const selectContent = (ctx: ContentLoaderContext) => ctx.content
|
|
150
|
+
export const selectStatus = (ctx: ContentLoaderContext) => ctx.status
|
|
151
|
+
export const selectStreaming = (ctx: ContentLoaderContext) => ctx.status === "streaming"
|
|
152
|
+
export const selectError = (ctx: ContentLoaderContext) => ctx.error
|
|
153
|
+
export const selectProviderMarks = (ctx: ContentLoaderContext) => ctx.providerMarks
|
|
154
|
+
export const selectLoadSeq = (ctx: ContentLoaderContext) => ctx.loadSeq
|
|
@@ -1,153 +1,72 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
import { useCallback, useEffect, useState } 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"
|
|
11
15
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
function createEmptyContent(format: string, title?: string): AnyContent {
|
|
17
|
-
switch (format) {
|
|
18
|
-
case "markdown":
|
|
19
|
-
return { format, markdown: "", title }
|
|
20
|
-
case "code":
|
|
21
|
-
return { format, code: "", title }
|
|
22
|
-
case "text":
|
|
23
|
-
return { format, text: "", title }
|
|
24
|
-
case "table":
|
|
25
|
-
return { format, columns: [], rows: [], title }
|
|
26
|
-
default:
|
|
27
|
-
return { format, data: undefined, title } as CustomContent
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function ensureContentFormat<F extends ContentFormat>(
|
|
32
|
-
current: AnyContent | null,
|
|
33
|
-
format: F,
|
|
34
|
-
title?: string,
|
|
35
|
-
): Extract<Content, { format: F }> {
|
|
36
|
-
if (!current || current.format !== format) {
|
|
37
|
-
return createEmptyContent(format, title) as Extract<Content, { format: F }>
|
|
38
|
-
}
|
|
39
|
-
return current as Extract<Content, { format: F }>
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function applyContentChunk(
|
|
43
|
-
current: AnyContent | null,
|
|
44
|
-
chunk: ContentChunk,
|
|
45
|
-
title?: string,
|
|
46
|
-
): AnyContent {
|
|
47
|
-
switch (chunk.type) {
|
|
48
|
-
case "replace":
|
|
49
|
-
return chunk.content
|
|
50
|
-
case "append":
|
|
51
|
-
if (chunk.format === "markdown") {
|
|
52
|
-
const target = ensureContentFormat(current, "markdown", title)
|
|
53
|
-
return { ...target, markdown: target.markdown + chunk.data }
|
|
54
|
-
}
|
|
55
|
-
if (chunk.format === "code") {
|
|
56
|
-
const target = ensureContentFormat(current, "code", title)
|
|
57
|
-
return {
|
|
58
|
-
...target,
|
|
59
|
-
code: target.code + chunk.data,
|
|
60
|
-
language: chunk.language ?? target.language,
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
{
|
|
64
|
-
const target = ensureContentFormat(current, "text", title)
|
|
65
|
-
return { ...target, text: target.text + chunk.data }
|
|
66
|
-
}
|
|
67
|
-
case "patch":
|
|
68
|
-
return chunk.apply(current)
|
|
69
|
-
default:
|
|
70
|
-
return current ?? createEmptyContent("markdown", title)
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export function useContentLoader(contentProvider: ContentProvider, reloadTrigger: number) {
|
|
75
|
-
const [content, setContent] = useState<AnyContent | null>(null)
|
|
76
|
-
const [streaming, setStreaming] = useState(false)
|
|
77
|
-
const [error, setError] = useState<string | null>(null)
|
|
78
|
-
const [providerMarks, setProviderMarks] = useState<MarkSet[]>([])
|
|
16
|
+
export function useContentLoader(contentProvider: ContentProvider) {
|
|
17
|
+
const [store] = useState(createContentLoaderStore)
|
|
18
|
+
const loadSeq = useSelector(store, (snapshot) => selectLoadSeq(snapshot.context))
|
|
79
19
|
|
|
80
20
|
useEffect(() => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
21
|
+
store.trigger.loadStarted({
|
|
22
|
+
marks: contentProvider.marks ?? [],
|
|
23
|
+
title: contentProvider.title,
|
|
24
|
+
})
|
|
25
|
+
const requestId = store.getSnapshot().context.requestId
|
|
85
26
|
const loaded = contentProvider.load()
|
|
86
27
|
|
|
87
28
|
if (isAsyncIterable(loaded)) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
setContent(current)
|
|
93
|
-
setStreaming(true)
|
|
94
|
-
|
|
95
|
-
// Hold the iterator explicitly so cleanup can close it: a cleanup that
|
|
96
|
-
// only flips `cancelled` leaves a pending next() (and the provider's
|
|
97
|
-
// subprocess/file handle/network stream) alive until the next chunk.
|
|
29
|
+
store.trigger.streamStarted({
|
|
30
|
+
requestId,
|
|
31
|
+
format: contentProvider.format ?? "markdown",
|
|
32
|
+
})
|
|
98
33
|
const iterator = loaded[Symbol.asyncIterator]()
|
|
99
|
-
|
|
100
34
|
;(async () => {
|
|
101
35
|
try {
|
|
102
36
|
while (true) {
|
|
103
37
|
const result = await iterator.next()
|
|
104
|
-
if (
|
|
105
|
-
|
|
106
|
-
if (chunk.type === "marks") {
|
|
107
|
-
// Streamed mark set: merge by replacing any existing set with same namespace
|
|
108
|
-
setProviderMarks((prev) => {
|
|
109
|
-
const filtered = prev.filter((s) => s.namespace !== chunk.set.namespace)
|
|
110
|
-
return [...filtered, chunk.set]
|
|
111
|
-
})
|
|
112
|
-
continue
|
|
113
|
-
}
|
|
114
|
-
current = applyContentChunk(current, chunk, title)
|
|
115
|
-
setContent(current)
|
|
116
|
-
}
|
|
117
|
-
} catch (err) {
|
|
118
|
-
if (!cancelled) {
|
|
119
|
-
setError(err instanceof Error ? err.message : String(err))
|
|
120
|
-
}
|
|
121
|
-
} finally {
|
|
122
|
-
if (!cancelled) {
|
|
123
|
-
setStreaming(false)
|
|
38
|
+
if (result.done) break
|
|
39
|
+
store.trigger.chunkReceived({ requestId, chunk: result.value })
|
|
124
40
|
}
|
|
41
|
+
store.trigger.streamEnded({ requestId })
|
|
42
|
+
} catch (error) {
|
|
43
|
+
store.trigger.loadFailed({ requestId, error: normalizeError(error) })
|
|
125
44
|
}
|
|
126
45
|
})()
|
|
127
|
-
|
|
128
46
|
return () => {
|
|
129
|
-
|
|
130
|
-
// Close the iterator to release provider resources mid-stream.
|
|
47
|
+
store.trigger.loadCancelled({ requestId })
|
|
131
48
|
Promise.resolve(iterator.return?.()).catch(() => {})
|
|
132
49
|
}
|
|
133
50
|
}
|
|
134
51
|
|
|
135
52
|
if (loaded instanceof Promise) {
|
|
136
|
-
let active = true
|
|
137
53
|
loaded
|
|
138
|
-
.then((
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
})
|
|
144
|
-
return () => {
|
|
145
|
-
active = false
|
|
146
|
-
}
|
|
54
|
+
.then((content) => store.trigger.loaded({ requestId, content }))
|
|
55
|
+
.catch((error: unknown) =>
|
|
56
|
+
store.trigger.loadFailed({ requestId, error: normalizeError(error) }),
|
|
57
|
+
)
|
|
58
|
+
return () => store.trigger.loadCancelled({ requestId })
|
|
147
59
|
}
|
|
148
60
|
|
|
149
|
-
|
|
150
|
-
}, [contentProvider,
|
|
61
|
+
store.trigger.loaded({ requestId, content: loaded })
|
|
62
|
+
}, [contentProvider, loadSeq, store])
|
|
63
|
+
|
|
64
|
+
const content = useSelector(store, (snapshot) => selectContent(snapshot.context))
|
|
65
|
+
const streaming = useSelector(store, (snapshot) => selectStreaming(snapshot.context))
|
|
66
|
+
const error = useSelector(store, (snapshot) => selectError(snapshot.context))
|
|
67
|
+
const providerMarks = useSelector(store, (snapshot) => selectProviderMarks(snapshot.context))
|
|
68
|
+
const status = useSelector(store, (snapshot) => selectStatus(snapshot.context))
|
|
69
|
+
const reload = useCallback(() => store.trigger.reloadRequested({}), [store])
|
|
151
70
|
|
|
152
|
-
return { content, streaming, error, providerMarks,
|
|
71
|
+
return { content, streaming, error, providerMarks, status, reload }
|
|
153
72
|
}
|