@tooee/view 0.2.3 → 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.
- package/README.md +15 -15
- package/dist/components/subviews/{CodeSubview.d.ts → code-subview.d.ts} +2 -2
- package/dist/components/subviews/code-subview.d.ts.map +1 -0
- package/dist/components/subviews/{CodeSubview.js → code-subview.js} +8 -8
- package/dist/components/subviews/code-subview.js.map +1 -0
- package/dist/components/subviews/custom-subview.d.ts +9 -0
- package/dist/components/subviews/custom-subview.d.ts.map +1 -0
- package/dist/components/subviews/{CustomSubview.js → custom-subview.js} +7 -7
- package/dist/components/subviews/custom-subview.js.map +1 -0
- package/dist/components/subviews/index.d.ts +4 -4
- package/dist/components/subviews/index.d.ts.map +1 -1
- package/dist/components/subviews/index.js +4 -4
- package/dist/components/subviews/index.js.map +1 -1
- package/dist/components/subviews/markdown-subview.d.ts +11 -0
- package/dist/components/subviews/markdown-subview.d.ts.map +1 -0
- package/dist/components/subviews/{MarkdownSubview.js → markdown-subview.js} +28 -26
- package/dist/components/subviews/markdown-subview.js.map +1 -0
- package/dist/components/subviews/table-subview.d.ts +8 -0
- package/dist/components/subviews/table-subview.d.ts.map +1 -0
- package/dist/components/subviews/{TableSubview.js → table-subview.js} +40 -22
- package/dist/components/subviews/table-subview.js.map +1 -0
- package/dist/components/subviews/types.d.ts.map +1 -1
- package/dist/components/{ViewScreen.d.ts → view-screen.d.ts} +3 -3
- package/dist/components/view-screen.d.ts.map +1 -0
- package/dist/components/{ViewScreen.js → view-screen.js} +11 -11
- package/dist/components/view-screen.js.map +1 -0
- package/dist/content-loader-store.d.ts +11 -7
- package/dist/content-loader-store.d.ts.map +1 -1
- package/dist/content-loader-store.js +70 -50
- package/dist/content-loader-store.js.map +1 -1
- package/dist/default-provider.d.ts +4 -4
- package/dist/default-provider.d.ts.map +1 -1
- package/dist/default-provider.js +48 -39
- package/dist/default-provider.js.map +1 -1
- package/dist/directory-provider.d.ts +2 -2
- package/dist/directory-provider.d.ts.map +1 -1
- package/dist/directory-provider.js +13 -10
- package/dist/directory-provider.js.map +1 -1
- package/dist/directory-view.d.ts +8 -0
- package/dist/directory-view.d.ts.map +1 -0
- package/dist/{DirectoryView.js → directory-view.js} +18 -12
- package/dist/directory-view.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -4
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -3
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/{useContentCommands.d.ts → use-content-commands.d.ts} +2 -2
- package/dist/hooks/use-content-commands.d.ts.map +1 -0
- package/dist/hooks/{useContentCommands.js → use-content-commands.js} +6 -4
- package/dist/hooks/use-content-commands.js.map +1 -0
- package/dist/hooks/{useContentLoader.d.ts → use-content-loader.d.ts} +4 -4
- package/dist/hooks/use-content-loader.d.ts.map +1 -0
- package/dist/hooks/use-content-loader.js +81 -0
- package/dist/hooks/use-content-loader.js.map +1 -0
- package/dist/hooks/{useViewCommandContext.d.ts → use-view-command-context.d.ts} +3 -3
- package/dist/hooks/use-view-command-context.d.ts.map +1 -0
- package/dist/hooks/{useViewCommandContext.js → use-view-command-context.js} +15 -13
- package/dist/hooks/use-view-command-context.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/launch.d.ts +2 -2
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js +6 -6
- package/dist/launch.js.map +1 -1
- package/dist/types.d.ts +4 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +46 -24
- package/dist/types.js.map +1 -1
- package/dist/view.d.ts +18 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/{View.js → view.js} +20 -14
- package/dist/view.js.map +1 -0
- package/package.json +10 -10
- package/src/components/subviews/{CodeSubview.tsx → code-subview.tsx} +25 -19
- package/src/components/subviews/{CustomSubview.tsx → custom-subview.tsx} +23 -21
- package/src/components/subviews/index.ts +5 -5
- package/src/components/subviews/{MarkdownSubview.tsx → markdown-subview.tsx} +55 -52
- package/src/components/subviews/table-subview.tsx +95 -0
- package/src/components/subviews/types.ts +14 -14
- package/src/components/{ViewScreen.tsx → view-screen.tsx} +33 -32
- package/src/content-loader-store.ts +131 -99
- package/src/default-provider.ts +84 -64
- package/src/directory-provider.ts +32 -24
- package/src/directory-view.tsx +79 -0
- package/src/hooks/index.ts +7 -4
- package/src/hooks/{useContentCommands.ts → use-content-commands.ts} +19 -14
- package/src/hooks/use-content-loader.ts +92 -0
- package/src/hooks/{useViewCommandContext.ts → use-view-command-context.ts} +40 -38
- package/src/index.ts +14 -14
- package/src/launch.tsx +20 -18
- package/src/types.ts +107 -78
- package/src/view.tsx +114 -0
- package/dist/DirectoryView.d.ts +0 -7
- package/dist/DirectoryView.d.ts.map +0 -1
- package/dist/DirectoryView.js.map +0 -1
- package/dist/View.d.ts +0 -16
- package/dist/View.d.ts.map +0 -1
- package/dist/View.js.map +0 -1
- package/dist/components/ViewScreen.d.ts.map +0 -1
- package/dist/components/ViewScreen.js.map +0 -1
- package/dist/components/subviews/CodeSubview.d.ts.map +0 -1
- package/dist/components/subviews/CodeSubview.js.map +0 -1
- package/dist/components/subviews/CustomSubview.d.ts +0 -9
- package/dist/components/subviews/CustomSubview.d.ts.map +0 -1
- package/dist/components/subviews/CustomSubview.js.map +0 -1
- package/dist/components/subviews/MarkdownSubview.d.ts +0 -10
- package/dist/components/subviews/MarkdownSubview.d.ts.map +0 -1
- package/dist/components/subviews/MarkdownSubview.js.map +0 -1
- package/dist/components/subviews/TableSubview.d.ts +0 -8
- package/dist/components/subviews/TableSubview.d.ts.map +0 -1
- package/dist/components/subviews/TableSubview.js.map +0 -1
- package/dist/hooks/useContentCommands.d.ts.map +0 -1
- package/dist/hooks/useContentCommands.js.map +0 -1
- package/dist/hooks/useContentLoader.d.ts.map +0 -1
- package/dist/hooks/useContentLoader.js +0 -55
- package/dist/hooks/useContentLoader.js.map +0 -1
- package/dist/hooks/useViewCommandContext.d.ts.map +0 -1
- package/dist/hooks/useViewCommandContext.js.map +0 -1
- package/src/DirectoryView.tsx +0 -68
- package/src/View.tsx +0 -94
- package/src/components/subviews/TableSubview.tsx +0 -65
- 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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
73
|
+
format: resolvedFormat,
|
|
74
74
|
marks: {
|
|
75
|
-
setMarkSet: marks?.setMarkSet ?? noop,
|
|
76
|
-
clearNamespace: marks?.clearNamespace ?? noop,
|
|
77
75
|
clearAll: marks?.clearAll ?? noop,
|
|
78
|
-
|
|
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 "./
|
|
2
|
-
export { DirectoryView } from "./
|
|
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/
|
|
17
|
+
} from "./hooks/use-view-command-context.js";
|
|
18
18
|
export type {
|
|
19
19
|
CreateViewCommandContextOptions,
|
|
20
20
|
ProvideViewCommandContextOptions,
|
|
21
21
|
ViewCommandContext,
|
|
22
|
-
} from "./hooks/
|
|
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 "./
|
|
5
|
-
import { DirectoryView } from "./
|
|
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(
|
|
37
|
-
|
|
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()
|
|
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
|
|
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
|
|
122
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
};
|