@tooee/view 0.12.0 → 0.14.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/dist/components/subviews/diff-subview.d.ts.map +1 -1
- package/dist/components/subviews/diff-subview.js +16 -4
- package/dist/components/subviews/diff-subview.js.map +1 -1
- package/dist/components/subviews/image-subview.d.ts.map +1 -1
- package/dist/components/subviews/image-subview.js +2 -1
- package/dist/components/subviews/image-subview.js.map +1 -1
- package/dist/components/subviews/index.d.ts.map +1 -1
- package/dist/components/subviews/index.js.map +1 -1
- package/dist/components/subviews/markdown-subview.d.ts.map +1 -1
- package/dist/components/subviews/markdown-subview.js +3 -2
- package/dist/components/subviews/markdown-subview.js.map +1 -1
- package/dist/components/subviews/table-subview.d.ts.map +1 -1
- package/dist/components/subviews/table-subview.js +4 -3
- package/dist/components/subviews/table-subview.js.map +1 -1
- package/dist/components/view-screen.d.ts.map +1 -1
- package/dist/components/view-screen.js +11 -1
- package/dist/components/view-screen.js.map +1 -1
- package/dist/content-loader-store.d.ts.map +1 -1
- package/dist/content-loader-store.js.map +1 -1
- package/dist/default-provider.d.ts.map +1 -1
- package/dist/default-provider.js.map +1 -1
- package/dist/directory-provider.d.ts.map +1 -1
- package/dist/directory-provider.js.map +1 -1
- package/dist/directory-view.d.ts.map +1 -1
- package/dist/directory-view.js +5 -2
- package/dist/directory-view.js.map +1 -1
- package/dist/folds/fold-decorations.d.ts.map +1 -1
- package/dist/folds/fold-decorations.js.map +1 -1
- package/dist/folds/fold-model.d.ts.map +1 -1
- package/dist/folds/fold-model.js +3 -1
- package/dist/folds/fold-model.js.map +1 -1
- package/dist/folds/markdown-heading-folds.d.ts.map +1 -1
- package/dist/folds/markdown-heading-folds.js +1 -6
- package/dist/folds/markdown-heading-folds.js.map +1 -1
- package/dist/folds/use-folds.d.ts.map +1 -1
- package/dist/folds/use-folds.js.map +1 -1
- package/dist/hooks/use-content-document.d.ts.map +1 -1
- package/dist/hooks/use-content-document.js +3 -2
- package/dist/hooks/use-content-document.js.map +1 -1
- package/dist/hooks/use-content-loader.d.ts.map +1 -1
- package/dist/hooks/use-content-loader.js.map +1 -1
- package/dist/hooks/use-view-command-context.d.ts.map +1 -1
- package/dist/hooks/use-view-command-context.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/launch.d.ts +7 -1
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/link-handlers.d.ts.map +1 -1
- package/dist/link-handlers.js.map +1 -1
- package/dist/markdown-links.d.ts.map +1 -1
- package/dist/markdown-links.js.map +1 -1
- package/dist/outline/markdown-outline.d.ts.map +1 -1
- package/dist/outline/markdown-outline.js.map +1 -1
- package/dist/outline/outline-panel.d.ts.map +1 -1
- package/dist/outline/outline-panel.js.map +1 -1
- package/dist/outline/use-outline.d.ts.map +1 -1
- package/dist/outline/use-outline.js.map +1 -1
- package/dist/standalone-router.d.ts.map +1 -1
- package/dist/standalone-router.js +3 -0
- package/dist/standalone-router.js.map +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/view.d.ts.map +1 -1
- package/dist/view.js.map +1 -1
- package/package.json +14 -14
- package/src/components/subviews/diff-subview.tsx +21 -4
- package/src/components/subviews/image-subview.tsx +2 -1
- package/src/components/subviews/index.ts +6 -0
- package/src/components/subviews/markdown-subview.tsx +12 -2
- package/src/components/subviews/table-subview.tsx +6 -3
- package/src/components/view-screen.tsx +11 -1
- package/src/content-loader-store.ts +23 -0
- package/src/default-provider.ts +18 -0
- package/src/directory-provider.ts +7 -0
- package/src/directory-view.tsx +7 -2
- package/src/folds/fold-decorations.ts +4 -0
- package/src/folds/fold-model.ts +8 -1
- package/src/folds/markdown-heading-folds.ts +7 -6
- package/src/folds/use-folds.ts +18 -0
- package/src/hooks/use-content-document.ts +5 -2
- package/src/hooks/use-content-loader.ts +7 -0
- package/src/hooks/use-view-command-context.ts +1 -0
- package/src/index.ts +17 -0
- package/src/launch.tsx +9 -1
- package/src/link-handlers.ts +10 -0
- package/src/markdown-links.ts +16 -0
- package/src/outline/markdown-outline.ts +3 -0
- package/src/outline/outline-panel.tsx +13 -0
- package/src/outline/use-outline.ts +24 -0
- package/src/standalone-router.tsx +23 -2
- package/src/types.ts +11 -0
- package/src/view.tsx +6 -0
package/src/markdown-links.ts
CHANGED
|
@@ -19,39 +19,53 @@ export const resolveMarkdownLink = function resolveMarkdownLink(
|
|
|
19
19
|
): LinkResolution {
|
|
20
20
|
try {
|
|
21
21
|
const decoded = decodeURIComponent(href);
|
|
22
|
+
|
|
22
23
|
if (/[\p{Cc}\\]/u.test(decoded)) {
|
|
23
24
|
return { status: "unsupported" };
|
|
24
25
|
}
|
|
26
|
+
|
|
25
27
|
const target = href.trim();
|
|
28
|
+
|
|
26
29
|
if (!target || target.startsWith("//")) {
|
|
27
30
|
return { status: "unsupported" };
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
const [rawPath] = target.split("#", 1);
|
|
34
|
+
|
|
30
35
|
if (rawPath.includes("?")) {
|
|
31
36
|
return { status: "unsupported" };
|
|
32
37
|
}
|
|
38
|
+
|
|
33
39
|
let path: string;
|
|
40
|
+
|
|
34
41
|
if (/^[a-zA-Z][a-zA-Z\d+.-]*:/u.test(rawPath)) {
|
|
35
42
|
// Check the authority before URL normalizes localhost into an empty host.
|
|
36
43
|
if (!/^file:\/\/\//iu.test(rawPath)) {
|
|
37
44
|
return { status: "unsupported" };
|
|
38
45
|
}
|
|
46
|
+
|
|
39
47
|
const url = new URL(rawPath);
|
|
48
|
+
|
|
40
49
|
if (url.hostname || url.search) {
|
|
41
50
|
return { status: "unsupported" };
|
|
42
51
|
}
|
|
52
|
+
|
|
43
53
|
path = fileURLToPath(url);
|
|
44
54
|
} else {
|
|
45
55
|
path = rawPath
|
|
46
56
|
? nodePath.resolve(baseDir, decodeURIComponent(rawPath))
|
|
47
57
|
: (currentPath ?? baseDir);
|
|
48
58
|
}
|
|
59
|
+
|
|
49
60
|
const absolute = nodePath.resolve(path);
|
|
61
|
+
|
|
50
62
|
try {
|
|
51
63
|
const stat = statSync(absolute);
|
|
64
|
+
|
|
52
65
|
if (stat.isFile()) {
|
|
53
66
|
return { path: absolute, status: "file" };
|
|
54
67
|
}
|
|
68
|
+
|
|
55
69
|
return stat.isDirectory()
|
|
56
70
|
? { path: absolute, status: "directory" }
|
|
57
71
|
: { status: "unsupported" };
|
|
@@ -82,8 +96,10 @@ const collect = (tokens: Token[], links: MarkdownLink[]): void => {
|
|
|
82
96
|
}
|
|
83
97
|
}
|
|
84
98
|
};
|
|
99
|
+
|
|
85
100
|
export const markdownLinks = function markdownLinks(line: string): MarkdownLink[] {
|
|
86
101
|
const links: MarkdownLink[] = [];
|
|
87
102
|
collect(Lexer.lexInline(line), links);
|
|
103
|
+
|
|
88
104
|
return links;
|
|
89
105
|
};
|
|
@@ -25,11 +25,14 @@ export const markdownOutline = function markdownOutline(
|
|
|
25
25
|
): OutlineEntry[] {
|
|
26
26
|
const headings = blocks.flatMap((block, row) => {
|
|
27
27
|
const heading = headingToken(block);
|
|
28
|
+
|
|
28
29
|
return heading === null
|
|
29
30
|
? []
|
|
30
31
|
: [{ depth: heading.depth, row, text: getPlainText(heading.tokens).trim() }];
|
|
31
32
|
});
|
|
33
|
+
|
|
32
34
|
const minDepth = Math.min(...headings.map((heading) => heading.depth));
|
|
35
|
+
|
|
33
36
|
return headings.map((heading) => ({ ...heading, level: heading.depth - minDepth }));
|
|
34
37
|
};
|
|
35
38
|
|
|
@@ -12,7 +12,9 @@ import type { OutlineEntry } from "./markdown-outline.js";
|
|
|
12
12
|
import type { OutlineState } from "./use-outline.js";
|
|
13
13
|
|
|
14
14
|
const OUTLINE_PANEL_ID = "outline";
|
|
15
|
+
|
|
15
16
|
const INDENT = " ";
|
|
17
|
+
|
|
16
18
|
/** Columns the panel border and the list padding take from the panel width. */
|
|
17
19
|
const OUTLINE_CHROME_WIDTH = 4;
|
|
18
20
|
|
|
@@ -29,6 +31,7 @@ export const outlineLabel = function outlineLabel(
|
|
|
29
31
|
const label = `${INDENT.repeat(entry.level)}${current ? "▸ " : " "}${entry.text}`;
|
|
30
32
|
const room = Math.max(1, width - suffix.length);
|
|
31
33
|
const cut = label.length > room ? `${label.slice(0, Math.max(0, room - 1))}…` : label;
|
|
34
|
+
|
|
32
35
|
return `${cut}${suffix}`;
|
|
33
36
|
};
|
|
34
37
|
|
|
@@ -49,18 +52,23 @@ const OutlineCommands = function OutlineCommands({
|
|
|
49
52
|
const next = (): void => {
|
|
50
53
|
outline.move(1);
|
|
51
54
|
};
|
|
55
|
+
|
|
52
56
|
const previous = (): void => {
|
|
53
57
|
outline.move(-1);
|
|
54
58
|
};
|
|
59
|
+
|
|
55
60
|
const first = (): void => {
|
|
56
61
|
outline.select(0);
|
|
57
62
|
};
|
|
63
|
+
|
|
58
64
|
const last = (): void => {
|
|
59
65
|
outline.select(outline.entries.length - 1);
|
|
60
66
|
};
|
|
67
|
+
|
|
61
68
|
const jump = (): void => {
|
|
62
69
|
outline.jump(outline.selectedIndex);
|
|
63
70
|
};
|
|
71
|
+
|
|
64
72
|
const commands: [hotkey: string, id: string, title: string, run: () => void][] = [
|
|
65
73
|
["j", "outline.down", "Next heading", next],
|
|
66
74
|
["down", "outline.down-arrow", "Next heading", next],
|
|
@@ -72,6 +80,7 @@ const OutlineCommands = function OutlineCommands({
|
|
|
72
80
|
["escape", "outline.focus-content", "Return to document", outline.focusContent],
|
|
73
81
|
["g o", "outline.close", "Close outline", outline.close],
|
|
74
82
|
];
|
|
83
|
+
|
|
75
84
|
const selectedRow = (): number | undefined => outline.entries[outline.selectedIndex]?.row;
|
|
76
85
|
useActions([
|
|
77
86
|
...commands.map(([hotkey, id, title, run]): ActionDefinition => ({
|
|
@@ -84,6 +93,7 @@ const OutlineCommands = function OutlineCommands({
|
|
|
84
93
|
})),
|
|
85
94
|
...foldActionDefinitions(foldActions, selectedRow, "outline.fold", "No fold at heading"),
|
|
86
95
|
]);
|
|
96
|
+
|
|
87
97
|
return null;
|
|
88
98
|
};
|
|
89
99
|
|
|
@@ -113,6 +123,7 @@ const OutlineList = function OutlineList({ outline }: { outline: OutlineState })
|
|
|
113
123
|
{outline.entries.map((entry, index): ReactNode => {
|
|
114
124
|
const current = index === outline.currentIndex;
|
|
115
125
|
const highlighted = outline.focused && index === outline.selectedIndex;
|
|
126
|
+
|
|
116
127
|
return (
|
|
117
128
|
<box
|
|
118
129
|
key={entry.row}
|
|
@@ -152,10 +163,12 @@ export const OutlinePanel = function OutlinePanel({
|
|
|
152
163
|
if (!outline.visible) {
|
|
153
164
|
return null;
|
|
154
165
|
}
|
|
166
|
+
|
|
155
167
|
// The only panel asks for activation on a mouse-down inside it.
|
|
156
168
|
const handleActivePanelChange = (): void => {
|
|
157
169
|
outline.focus();
|
|
158
170
|
};
|
|
171
|
+
|
|
159
172
|
return (
|
|
160
173
|
<PanelGroup
|
|
161
174
|
activePanelId={outline.focused ? OUTLINE_PANEL_ID : null}
|
|
@@ -11,10 +11,13 @@ import type { OutlineEntry } from "./markdown-outline.js";
|
|
|
11
11
|
|
|
12
12
|
/** Outline width in columns, border included. */
|
|
13
13
|
const OUTLINE_WIDTH = 32;
|
|
14
|
+
|
|
14
15
|
/** The outline never takes more than this share of the terminal width. */
|
|
15
16
|
const OUTLINE_MAX_SHARE = 0.4;
|
|
17
|
+
|
|
16
18
|
/** Below this terminal width the outline is not shown. */
|
|
17
19
|
export const OUTLINE_MIN_TERMINAL_WIDTH = 60;
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* Lines of the document kept above a heading after a jump. The heading lands
|
|
20
23
|
* near the top of the view, with a little of the text before it for context.
|
|
@@ -26,6 +29,7 @@ export const outlineWidth = function outlineWidth(terminalWidth: number): number
|
|
|
26
29
|
if (terminalWidth < OUTLINE_MIN_TERMINAL_WIDTH) {
|
|
27
30
|
return 0;
|
|
28
31
|
}
|
|
32
|
+
|
|
29
33
|
return Math.min(OUTLINE_WIDTH, Math.floor(terminalWidth * OUTLINE_MAX_SHARE));
|
|
30
34
|
};
|
|
31
35
|
|
|
@@ -85,6 +89,7 @@ export const useMarkdownOutline = function useMarkdownOutline({
|
|
|
85
89
|
const { closedKeys, ranges, setClosedKeys, view } = folds;
|
|
86
90
|
const { cursor, setCursor } = navigation;
|
|
87
91
|
const visible = open && width > 0;
|
|
92
|
+
|
|
88
93
|
const moveTo = useCallback(
|
|
89
94
|
(index: number) => {
|
|
90
95
|
setCursor(index);
|
|
@@ -92,10 +97,12 @@ export const useMarkdownOutline = function useMarkdownOutline({
|
|
|
92
97
|
},
|
|
93
98
|
[setCursor, revealRow]
|
|
94
99
|
);
|
|
100
|
+
|
|
95
101
|
const focused = visible && focusRequested;
|
|
96
102
|
const sourceRow = cursor === null ? undefined : view.visibleToSource[cursor];
|
|
97
103
|
const currentIndex = sourceRow === undefined ? -1 : outlineIndexAt(entries, sourceRow);
|
|
98
104
|
const selectedIndex = focused ? Math.min(selected, entries.length - 1) : currentIndex;
|
|
105
|
+
|
|
99
106
|
const closedRows = useMemo(
|
|
100
107
|
() => new Set(ranges.filter((range) => closedKeys.has(range.key)).map((range) => range.start)),
|
|
101
108
|
[ranges, closedKeys]
|
|
@@ -108,6 +115,7 @@ export const useMarkdownOutline = function useMarkdownOutline({
|
|
|
108
115
|
useEffect(() => {
|
|
109
116
|
const row = pendingRowRef.current;
|
|
110
117
|
const index = row === null ? -1 : (view.sourceToVisible[row] ?? -1);
|
|
118
|
+
|
|
111
119
|
if (index !== -1) {
|
|
112
120
|
pendingRowRef.current = null;
|
|
113
121
|
moveTo(index);
|
|
@@ -118,34 +126,46 @@ export const useMarkdownOutline = function useMarkdownOutline({
|
|
|
118
126
|
setSelected(Math.max(currentIndex, 0));
|
|
119
127
|
setFocusRequested(true);
|
|
120
128
|
};
|
|
129
|
+
|
|
121
130
|
const focusContent = () => {
|
|
122
131
|
setFocusRequested(false);
|
|
123
132
|
};
|
|
133
|
+
|
|
124
134
|
const close = () => {
|
|
125
135
|
setOpen(false);
|
|
126
136
|
setFocusRequested(false);
|
|
127
137
|
};
|
|
138
|
+
|
|
128
139
|
const select = (index: number) => {
|
|
129
140
|
setSelected(Math.max(0, Math.min(index, entries.length - 1)));
|
|
130
141
|
};
|
|
142
|
+
|
|
131
143
|
const move = (delta: number) => {
|
|
132
144
|
select(selectedIndex + delta);
|
|
133
145
|
};
|
|
146
|
+
|
|
134
147
|
const jump = (index: number) => {
|
|
135
148
|
const entry = entries[index];
|
|
136
149
|
setFocusRequested(false);
|
|
150
|
+
|
|
137
151
|
if (entry === undefined) {
|
|
138
152
|
return;
|
|
139
153
|
}
|
|
154
|
+
|
|
140
155
|
const hiding = closedFoldsHiding(ranges, closedKeys, entry.row);
|
|
156
|
+
|
|
141
157
|
if (hiding.length === 0) {
|
|
142
158
|
moveTo(view.sourceToVisible[entry.row] ?? 0);
|
|
159
|
+
|
|
143
160
|
return;
|
|
144
161
|
}
|
|
162
|
+
|
|
145
163
|
const next = new Set(closedKeys);
|
|
164
|
+
|
|
146
165
|
for (const key of hiding) {
|
|
147
166
|
next.delete(key);
|
|
148
167
|
}
|
|
168
|
+
|
|
149
169
|
pendingRowRef.current = entry.row;
|
|
150
170
|
setClosedKeys(next);
|
|
151
171
|
};
|
|
@@ -155,12 +175,16 @@ export const useMarkdownOutline = function useMarkdownOutline({
|
|
|
155
175
|
handler: (ctx) => {
|
|
156
176
|
if (width === 0) {
|
|
157
177
|
ctx.toast?.toast({ level: "info", message: "The outline needs a wider terminal" });
|
|
178
|
+
|
|
158
179
|
return;
|
|
159
180
|
}
|
|
181
|
+
|
|
160
182
|
if (open && focused) {
|
|
161
183
|
close();
|
|
184
|
+
|
|
162
185
|
return;
|
|
163
186
|
}
|
|
187
|
+
|
|
164
188
|
setOpen(true);
|
|
165
189
|
focus();
|
|
166
190
|
},
|
|
@@ -2,7 +2,6 @@ import path from "node:path";
|
|
|
2
2
|
|
|
3
3
|
import { useChooseDialog } from "@tooee/choose";
|
|
4
4
|
import { useCommand } from "@tooee/commands";
|
|
5
|
-
import type { CommandContext } from "@tooee/commands";
|
|
6
5
|
import {
|
|
7
6
|
createRoute,
|
|
8
7
|
createRouter,
|
|
@@ -22,6 +21,7 @@ import { runLinkHandlers } from "./link-handlers.js";
|
|
|
22
21
|
import { markdownLinks } from "./markdown-links.js";
|
|
23
22
|
import type { MarkdownLink } from "./markdown-links.js";
|
|
24
23
|
import { getTextContent } from "./types.js";
|
|
24
|
+
import type { MarkdownLinkActivateHandler } from "./types.js";
|
|
25
25
|
import { View } from "./view.js";
|
|
26
26
|
|
|
27
27
|
const documentParams: Codec<{ path?: string }> = {
|
|
@@ -30,12 +30,15 @@ const documentParams: Codec<{ path?: string }> = {
|
|
|
30
30
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
31
31
|
throw new TypeError("Expected document params");
|
|
32
32
|
}
|
|
33
|
+
|
|
33
34
|
if (!("path" in value) || value.path === undefined) {
|
|
34
35
|
return {};
|
|
35
36
|
}
|
|
37
|
+
|
|
36
38
|
if (typeof value.path !== "string" || !path.isAbsolute(value.path)) {
|
|
37
39
|
throw new TypeError("Expected an absolute document path");
|
|
38
40
|
}
|
|
41
|
+
|
|
39
42
|
/* oxlint-enable anti-slop/no-runtime-typeof */
|
|
40
43
|
return { path: value.path };
|
|
41
44
|
},
|
|
@@ -55,32 +58,45 @@ export const createStandaloneRouter = function createStandaloneRouter(
|
|
|
55
58
|
const navigate = useNavigate();
|
|
56
59
|
const { stack } = useRouter();
|
|
57
60
|
const chooseLink = useChooseDialog<MarkdownLink>();
|
|
61
|
+
|
|
58
62
|
const provider = useMemo(
|
|
59
63
|
() =>
|
|
60
64
|
documentPath === undefined ? options.contentProvider : createFileProvider(documentPath),
|
|
61
65
|
[documentPath]
|
|
62
66
|
);
|
|
63
|
-
|
|
67
|
+
|
|
68
|
+
const activate: MarkdownLinkActivateHandler = (href, command) => {
|
|
69
|
+
if (options.onMarkdownLinkActivate) {
|
|
70
|
+
return options.onMarkdownLinkActivate(href, command);
|
|
71
|
+
}
|
|
72
|
+
|
|
64
73
|
if (options.filePath === undefined) {
|
|
65
74
|
command.toast?.toast({ level: "info", message: "Links need a source file" });
|
|
75
|
+
|
|
66
76
|
return true;
|
|
67
77
|
}
|
|
78
|
+
|
|
68
79
|
const currentPath = path.resolve(documentPath ?? options.filePath);
|
|
80
|
+
|
|
69
81
|
return runLinkHandlers(
|
|
70
82
|
options.linkHandlers ?? [],
|
|
71
83
|
{ baseDir: path.dirname(currentPath), currentPath, href },
|
|
72
84
|
{ command, documentRoute, navigate }
|
|
73
85
|
);
|
|
74
86
|
};
|
|
87
|
+
|
|
75
88
|
useCommand({
|
|
76
89
|
handler: async (context) => {
|
|
77
90
|
// Source positions are zero-based, unlike the displayed line numbers.
|
|
78
91
|
const line = context.document?.activeAnchor?.source?.primary?.start.line;
|
|
79
92
|
const content = context.view?.content;
|
|
93
|
+
|
|
80
94
|
if (line === undefined || content === undefined) {
|
|
81
95
|
return;
|
|
82
96
|
}
|
|
97
|
+
|
|
83
98
|
const links = markdownLinks(getTextContent(content).split("\n")[line] ?? "");
|
|
99
|
+
|
|
84
100
|
// One link follows directly; several open a chooser so none is silently preferred.
|
|
85
101
|
const link =
|
|
86
102
|
links.length > 1
|
|
@@ -90,6 +106,7 @@ export const createStandaloneRouter = function createStandaloneRouter(
|
|
|
90
106
|
toItem: (item) => ({ description: item.href, text: item.text }),
|
|
91
107
|
})
|
|
92
108
|
: (links[0] ?? null);
|
|
109
|
+
|
|
93
110
|
if (link !== null) {
|
|
94
111
|
activate(link.href, context);
|
|
95
112
|
}
|
|
@@ -100,6 +117,7 @@ export const createStandaloneRouter = function createStandaloneRouter(
|
|
|
100
117
|
title: "Follow Markdown link",
|
|
101
118
|
when: (context) => context.view?.format === "markdown",
|
|
102
119
|
});
|
|
120
|
+
|
|
103
121
|
return (
|
|
104
122
|
<View
|
|
105
123
|
key={`${stack.length}:${documentPath ?? ""}`}
|
|
@@ -112,14 +130,17 @@ export const createStandaloneRouter = function createStandaloneRouter(
|
|
|
112
130
|
/>
|
|
113
131
|
);
|
|
114
132
|
};
|
|
133
|
+
|
|
115
134
|
const documentRoute = createRoute({
|
|
116
135
|
component: DocumentScreen,
|
|
117
136
|
id: "view.document",
|
|
118
137
|
params: documentParams,
|
|
119
138
|
});
|
|
139
|
+
|
|
120
140
|
const router = createRouter({
|
|
121
141
|
initial: { routeId: documentRoute.id },
|
|
122
142
|
routes: [documentRoute],
|
|
123
143
|
});
|
|
144
|
+
|
|
124
145
|
return { documentRoute, router };
|
|
125
146
|
};
|
package/src/types.ts
CHANGED
|
@@ -137,7 +137,9 @@ export interface ContentProvider {
|
|
|
137
137
|
// === Compat aliases ===
|
|
138
138
|
|
|
139
139
|
export type ViewContent = Content;
|
|
140
|
+
|
|
140
141
|
export type ViewContentProvider = ContentProvider;
|
|
142
|
+
|
|
141
143
|
export type { ColumnDef, TableRow } from "@tooee/renderers";
|
|
142
144
|
|
|
143
145
|
// === Utilities ===
|
|
@@ -167,29 +169,38 @@ export const getTextContent = function getTextContent(content: AnyContent): stri
|
|
|
167
169
|
case "markdown": {
|
|
168
170
|
return content.markdown;
|
|
169
171
|
}
|
|
172
|
+
|
|
170
173
|
case "code": {
|
|
171
174
|
return content.code;
|
|
172
175
|
}
|
|
176
|
+
|
|
173
177
|
case "diff": {
|
|
174
178
|
return content.patch;
|
|
175
179
|
}
|
|
180
|
+
|
|
176
181
|
case "text": {
|
|
177
182
|
return content.text;
|
|
178
183
|
}
|
|
184
|
+
|
|
179
185
|
case "image": {
|
|
180
186
|
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- ImageSource is OpenTUI's public string-or-binary union; only its string and URL variants have text content
|
|
181
187
|
if (typeof content.src === "string") {
|
|
182
188
|
return content.src;
|
|
183
189
|
}
|
|
190
|
+
|
|
184
191
|
return content.src instanceof URL ? content.src.href : "";
|
|
185
192
|
}
|
|
193
|
+
|
|
186
194
|
case "table": {
|
|
187
195
|
const headers = content.columns.map((column) => column.header ?? column.key);
|
|
196
|
+
|
|
188
197
|
const rowLines = content.rows.map((row) =>
|
|
189
198
|
content.columns.map((column) => formatTableCell(row[column.key])).join("\t")
|
|
190
199
|
);
|
|
200
|
+
|
|
191
201
|
return [headers.join("\t"), ...rowLines].join("\n");
|
|
192
202
|
}
|
|
203
|
+
|
|
193
204
|
default: {
|
|
194
205
|
return "";
|
|
195
206
|
}
|
package/src/view.tsx
CHANGED
|
@@ -50,6 +50,7 @@ export const View = function View({
|
|
|
50
50
|
const setMarkSet = useCallback((set: MarkSet) => {
|
|
51
51
|
setUserMarks((prev) => {
|
|
52
52
|
const filtered = prev.filter((s) => s.namespace !== set.namespace);
|
|
53
|
+
|
|
53
54
|
return [...filtered, set];
|
|
54
55
|
});
|
|
55
56
|
}, []);
|
|
@@ -109,19 +110,24 @@ export const View = function View({
|
|
|
109
110
|
/>
|
|
110
111
|
);
|
|
111
112
|
}
|
|
113
|
+
|
|
112
114
|
case "code":
|
|
113
115
|
case "text": {
|
|
114
116
|
return <CodeSubview content={content} {...shared} />;
|
|
115
117
|
}
|
|
118
|
+
|
|
116
119
|
case "image": {
|
|
117
120
|
return <ImageSubview content={content} {...shared} />;
|
|
118
121
|
}
|
|
122
|
+
|
|
119
123
|
case "table": {
|
|
120
124
|
return <TableSubview content={content} {...shared} />;
|
|
121
125
|
}
|
|
126
|
+
|
|
122
127
|
case "diff": {
|
|
123
128
|
return <DiffSubview content={content} {...shared} />;
|
|
124
129
|
}
|
|
130
|
+
|
|
125
131
|
default: {
|
|
126
132
|
return null;
|
|
127
133
|
}
|