@tooee/view 0.1.11 → 0.1.12
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/DirectoryView.js.map +1 -1
- package/dist/View.d.ts.map +1 -1
- package/dist/View.js +41 -353
- package/dist/View.js.map +1 -1
- package/dist/components/SubviewLayout.d.ts +15 -0
- package/dist/components/SubviewLayout.d.ts.map +1 -0
- package/dist/components/SubviewLayout.js +26 -0
- package/dist/components/SubviewLayout.js.map +1 -0
- package/dist/components/subviews/CodeSubview.d.ts +8 -0
- package/dist/components/subviews/CodeSubview.d.ts.map +1 -0
- package/dist/components/subviews/CodeSubview.js +71 -0
- package/dist/components/subviews/CodeSubview.js.map +1 -0
- package/dist/components/subviews/CustomSubview.d.ts +9 -0
- package/dist/components/subviews/CustomSubview.d.ts.map +1 -0
- package/dist/components/subviews/CustomSubview.js +87 -0
- package/dist/components/subviews/CustomSubview.js.map +1 -0
- package/dist/components/subviews/ImageSubview.d.ts +8 -0
- package/dist/components/subviews/ImageSubview.d.ts.map +1 -0
- package/dist/components/subviews/ImageSubview.js +32 -0
- package/dist/components/subviews/ImageSubview.js.map +1 -0
- package/dist/components/subviews/MarkdownSubview.d.ts +8 -0
- package/dist/components/subviews/MarkdownSubview.d.ts.map +1 -0
- package/dist/components/subviews/MarkdownSubview.js +80 -0
- package/dist/components/subviews/MarkdownSubview.js.map +1 -0
- package/dist/components/subviews/TableSubview.d.ts +8 -0
- package/dist/components/subviews/TableSubview.d.ts.map +1 -0
- package/dist/components/subviews/TableSubview.js +113 -0
- package/dist/components/subviews/TableSubview.js.map +1 -0
- package/dist/components/subviews/index.d.ts +8 -0
- package/dist/components/subviews/index.d.ts.map +1 -0
- package/dist/components/subviews/index.js +7 -0
- package/dist/components/subviews/index.js.map +1 -0
- package/dist/components/subviews/types.d.ts +15 -0
- package/dist/components/subviews/types.d.ts.map +1 -0
- package/dist/components/subviews/types.js +2 -0
- package/dist/components/subviews/types.js.map +1 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useContentLoader.d.ts +10 -0
- package/dist/hooks/useContentLoader.d.ts.map +1 -0
- package/dist/hooks/useContentLoader.js +125 -0
- package/dist/hooks/useContentLoader.js.map +1 -0
- package/dist/hooks/useMarkState.d.ts +13 -0
- package/dist/hooks/useMarkState.d.ts.map +1 -0
- package/dist/hooks/useMarkState.js +71 -0
- package/dist/hooks/useMarkState.js.map +1 -0
- package/dist/hooks/useViewCommandContext.d.ts +18 -0
- package/dist/hooks/useViewCommandContext.d.ts.map +1 -0
- package/dist/hooks/useViewCommandContext.js +23 -0
- package/dist/hooks/useViewCommandContext.js.map +1 -0
- package/dist/hooks/useViewCommands.d.ts +13 -0
- package/dist/hooks/useViewCommands.d.ts.map +1 -0
- package/dist/hooks/useViewCommands.js +20 -0
- package/dist/hooks/useViewCommands.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/launch.d.ts.map +1 -1
- package/dist/launch.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +24 -23
- package/src/DirectoryView.tsx +1 -1
- package/src/View.tsx +49 -462
- package/src/components/SubviewLayout.tsx +55 -0
- package/src/components/subviews/CodeSubview.tsx +112 -0
- package/src/components/subviews/CustomSubview.tsx +139 -0
- package/src/components/subviews/ImageSubview.tsx +64 -0
- package/src/components/subviews/MarkdownSubview.tsx +122 -0
- package/src/components/subviews/TableSubview.tsx +153 -0
- package/src/components/subviews/index.ts +7 -0
- package/src/components/subviews/types.ts +15 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useContentLoader.ts +147 -0
- package/src/hooks/useMarkState.ts +99 -0
- package/src/hooks/useViewCommandContext.ts +49 -0
- package/src/hooks/useViewCommands.ts +36 -0
- package/src/index.ts +6 -1
- package/src/launch.tsx +1 -6
- package/src/types.ts +9 -0
package/src/View.tsx
CHANGED
|
@@ -1,31 +1,16 @@
|
|
|
1
|
-
import { useState,
|
|
2
|
-
import { MarkdownView, CodeView, ImageView, Table, type RowDocumentRenderable } from "@tooee/renderers"
|
|
3
|
-
import { AppLayout } from "@tooee/layout"
|
|
1
|
+
import { useState, useCallback } from "react"
|
|
4
2
|
import { useTheme } from "@tooee/themes"
|
|
5
|
-
import { useActions, useProvideCommandContext } from "@tooee/commands"
|
|
6
3
|
import type { ActionDefinition } from "@tooee/commands"
|
|
4
|
+
import type { MarkSet } from "@tooee/marks"
|
|
5
|
+
import { isCustomContent, type ContentProvider, type ContentRenderer } from "./types.js"
|
|
6
|
+
import { useContentLoader } from "./hooks/useContentLoader.js"
|
|
7
7
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "@tooee/shell"
|
|
15
|
-
import { useConfig } from "@tooee/config"
|
|
16
|
-
import { marked } from "marked"
|
|
17
|
-
import {
|
|
18
|
-
getTextContent,
|
|
19
|
-
isBuiltinContent,
|
|
20
|
-
isCustomContent,
|
|
21
|
-
type AnyContent,
|
|
22
|
-
type Content,
|
|
23
|
-
type ContentFormat,
|
|
24
|
-
type ContentProvider,
|
|
25
|
-
type ContentChunk,
|
|
26
|
-
type ContentRenderer,
|
|
27
|
-
type CustomContent,
|
|
28
|
-
} from "./types.js"
|
|
8
|
+
MarkdownSubview,
|
|
9
|
+
CodeSubview,
|
|
10
|
+
TableSubview,
|
|
11
|
+
ImageSubview,
|
|
12
|
+
CustomSubview,
|
|
13
|
+
} from "./components/subviews/index.js"
|
|
29
14
|
|
|
30
15
|
interface ViewProps {
|
|
31
16
|
contentProvider: ContentProvider
|
|
@@ -33,309 +18,32 @@ interface ViewProps {
|
|
|
33
18
|
renderers?: Record<string, ContentRenderer>
|
|
34
19
|
}
|
|
35
20
|
|
|
36
|
-
function isAsyncIterable(value: unknown): value is AsyncIterable<ContentChunk> {
|
|
37
|
-
return value != null && typeof value === "object" && Symbol.asyncIterator in value
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function createEmptyContent(format: string, title?: string): AnyContent {
|
|
41
|
-
switch (format) {
|
|
42
|
-
case "markdown":
|
|
43
|
-
return { format, markdown: "", title }
|
|
44
|
-
case "code":
|
|
45
|
-
return { format, code: "", title }
|
|
46
|
-
case "text":
|
|
47
|
-
return { format, text: "", title }
|
|
48
|
-
case "image":
|
|
49
|
-
return { format, src: "", title }
|
|
50
|
-
case "table":
|
|
51
|
-
return { format, columns: [], rows: [], title }
|
|
52
|
-
default:
|
|
53
|
-
return { format, data: undefined, title } as CustomContent
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function ensureContentFormat<F extends ContentFormat>(
|
|
58
|
-
current: AnyContent | null,
|
|
59
|
-
format: F,
|
|
60
|
-
title?: string,
|
|
61
|
-
): Extract<Content, { format: F }> {
|
|
62
|
-
if (!current || current.format !== format) {
|
|
63
|
-
return createEmptyContent(format, title) as Extract<Content, { format: F }>
|
|
64
|
-
}
|
|
65
|
-
return current as Extract<Content, { format: F }>
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function applyContentChunk(
|
|
69
|
-
current: AnyContent | null,
|
|
70
|
-
chunk: ContentChunk,
|
|
71
|
-
title?: string,
|
|
72
|
-
): AnyContent {
|
|
73
|
-
switch (chunk.type) {
|
|
74
|
-
case "replace":
|
|
75
|
-
return chunk.content
|
|
76
|
-
case "append":
|
|
77
|
-
if (chunk.format === "markdown") {
|
|
78
|
-
const target = ensureContentFormat(current, "markdown", title)
|
|
79
|
-
return { ...target, markdown: target.markdown + chunk.data }
|
|
80
|
-
}
|
|
81
|
-
if (chunk.format === "code") {
|
|
82
|
-
const target = ensureContentFormat(current, "code", title)
|
|
83
|
-
return {
|
|
84
|
-
...target,
|
|
85
|
-
code: target.code + chunk.data,
|
|
86
|
-
language: chunk.language ?? target.language,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
{
|
|
90
|
-
const target = ensureContentFormat(current, "text", title)
|
|
91
|
-
return { ...target, text: target.text + chunk.data }
|
|
92
|
-
}
|
|
93
|
-
case "patch":
|
|
94
|
-
return chunk.apply(current)
|
|
95
|
-
default:
|
|
96
|
-
return current ?? createEmptyContent("markdown", title)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
21
|
export function View({ contentProvider, actions, renderers }: ViewProps) {
|
|
101
22
|
const { theme } = useTheme()
|
|
102
|
-
const config = useConfig()
|
|
103
|
-
const [content, setContent] = useState<AnyContent | null>(null)
|
|
104
|
-
const [streaming, setStreaming] = useState(false)
|
|
105
|
-
const [error, setError] = useState<string | null>(null)
|
|
106
|
-
const docRef = useRef<RowDocumentRenderable>(null)
|
|
107
23
|
const [reloadTrigger, setReloadTrigger] = useState(0)
|
|
108
24
|
const reload = useCallback(() => setReloadTrigger((n) => n + 1), [])
|
|
109
25
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const loaded = contentProvider.load()
|
|
114
|
-
|
|
115
|
-
if (isAsyncIterable(loaded)) {
|
|
116
|
-
const fallbackFormat = contentProvider.format ?? "markdown"
|
|
117
|
-
const title = contentProvider.title
|
|
118
|
-
let cancelled = false
|
|
119
|
-
let current: AnyContent | null = createEmptyContent(fallbackFormat, title)
|
|
120
|
-
setContent(current)
|
|
121
|
-
setStreaming(true)
|
|
122
|
-
|
|
123
|
-
;(async () => {
|
|
124
|
-
try {
|
|
125
|
-
for await (const chunk of loaded) {
|
|
126
|
-
if (cancelled) break
|
|
127
|
-
current = applyContentChunk(current, chunk, title)
|
|
128
|
-
setContent(current)
|
|
129
|
-
}
|
|
130
|
-
} catch (error) {
|
|
131
|
-
if (!cancelled && error instanceof Error) {
|
|
132
|
-
setError(error.message)
|
|
133
|
-
}
|
|
134
|
-
} finally {
|
|
135
|
-
if (!cancelled) {
|
|
136
|
-
setStreaming(false)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
})()
|
|
140
|
-
|
|
141
|
-
return () => {
|
|
142
|
-
cancelled = true
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (loaded instanceof Promise) {
|
|
147
|
-
let active = true
|
|
148
|
-
loaded
|
|
149
|
-
.then((value) => {
|
|
150
|
-
if (active) setContent(value)
|
|
151
|
-
})
|
|
152
|
-
.catch((error: Error) => {
|
|
153
|
-
if (active) setError(error.message)
|
|
154
|
-
})
|
|
155
|
-
return () => {
|
|
156
|
-
active = false
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
setContent(loaded)
|
|
161
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
162
|
-
}, [contentProvider, reloadTrigger])
|
|
163
|
-
|
|
164
|
-
const TABLE_SEARCH_HEADER_OFFSET = 1
|
|
165
|
-
|
|
166
|
-
const textContent = useMemo(() => (content ? getTextContent(content) : ""), [content])
|
|
167
|
-
|
|
168
|
-
const lineCount = useMemo(() => {
|
|
169
|
-
if (!content) return 0
|
|
170
|
-
if (isBuiltinContent(content) && content.format === "table") {
|
|
171
|
-
return content.rows.length
|
|
172
|
-
}
|
|
173
|
-
return textContent.split("\n").length
|
|
174
|
-
}, [content, textContent])
|
|
175
|
-
|
|
176
|
-
const { blockCount, blockLineMap } = useMemo(() => {
|
|
177
|
-
if (!content) return { blockCount: undefined, blockLineMap: undefined }
|
|
178
|
-
if (!isBuiltinContent(content)) return { blockCount: undefined, blockLineMap: undefined }
|
|
179
|
-
|
|
180
|
-
if (content.format === "table") {
|
|
181
|
-
const map = content.rows.map((_: unknown, i: number) => i)
|
|
182
|
-
return { blockCount: content.rows.length, blockLineMap: map }
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (content.format !== "markdown") {
|
|
186
|
-
return { blockCount: undefined, blockLineMap: undefined }
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const tokens = marked.lexer(content.markdown)
|
|
190
|
-
const blocks = tokens.filter((t) => t.type !== "space")
|
|
191
|
-
const lineMap: number[] = []
|
|
192
|
-
let lineOffset = 0
|
|
193
|
-
for (const token of tokens) {
|
|
194
|
-
if (token.type === "space") {
|
|
195
|
-
if ("raw" in token && typeof token.raw === "string") {
|
|
196
|
-
lineOffset += token.raw.split("\n").length - 1
|
|
197
|
-
}
|
|
198
|
-
continue
|
|
199
|
-
}
|
|
200
|
-
lineMap.push(lineOffset)
|
|
201
|
-
if ("raw" in token && typeof token.raw === "string") {
|
|
202
|
-
lineOffset += token.raw.split("\n").length - 1
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
return { blockCount: blocks.length, blockLineMap: lineMap }
|
|
206
|
-
}, [content])
|
|
207
|
-
|
|
208
|
-
const nav = useModalNavigationCommands({
|
|
209
|
-
totalLines: lineCount,
|
|
210
|
-
getText: () => (content ? textContent : undefined),
|
|
211
|
-
blockCount,
|
|
212
|
-
blockLineMap,
|
|
213
|
-
searchLineOffset: content?.format === "table" ? TABLE_SEARCH_HEADER_OFFSET : 0,
|
|
214
|
-
multiSelect: true,
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
if (nav.cursor) {
|
|
219
|
-
docRef.current?.scrollToRow(nav.cursor.line, "nearest")
|
|
220
|
-
}
|
|
221
|
-
}, [nav.cursor])
|
|
222
|
-
|
|
223
|
-
const [showLineNumbers, setShowLineNumbers] = useState(config.view?.gutter ?? true)
|
|
224
|
-
|
|
225
|
-
const { name: themeName } = useThemeCommands()
|
|
226
|
-
useQuitCommand()
|
|
227
|
-
useCopyCommand({
|
|
228
|
-
getText: () => (content ? textContent : undefined),
|
|
229
|
-
})
|
|
230
|
-
useToggleLineNumbersCommand({
|
|
231
|
-
showLineNumbers,
|
|
232
|
-
onToggle: () => setShowLineNumbers((v) => !v),
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
const _palette = useCommandPalette()
|
|
236
|
-
|
|
237
|
-
useActions(actions)
|
|
238
|
-
|
|
239
|
-
function getActiveRow(): Record<string, unknown> | undefined {
|
|
240
|
-
if (!content || !isBuiltinContent(content) || content.format !== "table") return undefined
|
|
241
|
-
if (!nav.cursor) return undefined
|
|
242
|
-
return content.rows[nav.cursor.line]
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function getSelectedRows(): Record<string, unknown>[] {
|
|
246
|
-
if (!content || !isBuiltinContent(content) || content.format !== "table") return []
|
|
247
|
-
if (nav.toggledIndices.size) {
|
|
248
|
-
return Array.from(nav.toggledIndices)
|
|
249
|
-
.map((i) => content.rows[i])
|
|
250
|
-
.filter(Boolean)
|
|
251
|
-
}
|
|
252
|
-
if (nav.selection) {
|
|
253
|
-
const start = nav.selection.start.line
|
|
254
|
-
const end = nav.selection.end.line
|
|
255
|
-
return content.rows.slice(start, end + 1)
|
|
256
|
-
}
|
|
257
|
-
return []
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
useProvideCommandContext(() => ({
|
|
261
|
-
view: {
|
|
262
|
-
content,
|
|
263
|
-
format: content?.format,
|
|
264
|
-
cursor: nav.cursor,
|
|
265
|
-
selection: nav.selection,
|
|
266
|
-
mode: nav.mode,
|
|
267
|
-
activeRow: getActiveRow(),
|
|
268
|
-
selectedRows: getSelectedRows(),
|
|
269
|
-
toggledIndices: nav.toggledIndices,
|
|
270
|
-
reload,
|
|
271
|
-
},
|
|
272
|
-
}))
|
|
273
|
-
|
|
274
|
-
const matchingLinesSet = useMemo(
|
|
275
|
-
() => (nav.matchingLines.length > 0 ? new Set<number>(nav.matchingLines) : undefined),
|
|
276
|
-
[nav.matchingLines],
|
|
26
|
+
const { content, streaming, error, providerMarks } = useContentLoader(
|
|
27
|
+
contentProvider,
|
|
28
|
+
reloadTrigger,
|
|
277
29
|
)
|
|
278
|
-
const currentMatchLine =
|
|
279
|
-
nav.matchingLines.length > 0 ? nav.matchingLines[nav.currentMatchIndex] : undefined
|
|
280
30
|
|
|
281
|
-
|
|
282
|
-
const matchingBlocks = useMemo(() => {
|
|
283
|
-
if (!matchingLinesSet || !blockLineMap || blockLineMap.length === 0) return undefined
|
|
284
|
-
if (content?.format === "table") return undefined // table uses matchingRows instead
|
|
285
|
-
const blocks = new Set<number>()
|
|
286
|
-
for (const line of matchingLinesSet) {
|
|
287
|
-
let blockIdx = 0
|
|
288
|
-
for (let i = blockLineMap.length - 1; i >= 0; i--) {
|
|
289
|
-
if (blockLineMap[i] <= line) {
|
|
290
|
-
blockIdx = i
|
|
291
|
-
break
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
blocks.add(blockIdx)
|
|
295
|
-
}
|
|
296
|
-
return blocks.size > 0 ? blocks : undefined
|
|
297
|
-
}, [matchingLinesSet, blockLineMap, content?.format])
|
|
31
|
+
const [userMarks, setUserMarks] = useState<MarkSet[]>([])
|
|
298
32
|
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return 0
|
|
306
|
-
}, [currentMatchLine, blockLineMap, content?.format])
|
|
33
|
+
const setMarkSet = useCallback((set: MarkSet) => {
|
|
34
|
+
setUserMarks((prev) => {
|
|
35
|
+
const filtered = prev.filter((s) => s.namespace !== set.namespace)
|
|
36
|
+
return [...filtered, set]
|
|
37
|
+
})
|
|
38
|
+
}, [])
|
|
307
39
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
const rows = new Set<number>()
|
|
312
|
-
for (const line of matchingLinesSet) {
|
|
313
|
-
const row = line - TABLE_SEARCH_HEADER_OFFSET
|
|
314
|
-
if (row >= 0) rows.add(row)
|
|
315
|
-
}
|
|
316
|
-
return rows.size > 0 ? rows : undefined
|
|
317
|
-
}, [matchingLinesSet, content?.format])
|
|
40
|
+
const clearMarkNamespace = useCallback((namespace: string) => {
|
|
41
|
+
setUserMarks((prev) => prev.filter((s) => s.namespace !== namespace))
|
|
42
|
+
}, [])
|
|
318
43
|
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
return row >= 0 ? row : undefined
|
|
323
|
-
}, [content?.format, currentMatchLine])
|
|
324
|
-
|
|
325
|
-
const toggledBlocks = useMemo(() => {
|
|
326
|
-
if (content?.format !== "markdown" || nav.toggledIndices.size === 0) return undefined
|
|
327
|
-
return new Set<number>(nav.toggledIndices)
|
|
328
|
-
}, [content?.format, nav.toggledIndices])
|
|
329
|
-
|
|
330
|
-
const toggledRows = useMemo(() => {
|
|
331
|
-
if (content?.format !== "table" || nav.toggledIndices.size === 0) return undefined
|
|
332
|
-
return new Set<number>(nav.toggledIndices)
|
|
333
|
-
}, [content?.format, nav.toggledIndices])
|
|
334
|
-
|
|
335
|
-
const toggledLines = useMemo(() => {
|
|
336
|
-
if (!content || (content.format !== "code" && content.format !== "text")) return undefined
|
|
337
|
-
return nav.toggledIndices.size > 0 ? new Set<number>(nav.toggledIndices) : undefined
|
|
338
|
-
}, [content, nav.toggledIndices])
|
|
44
|
+
const clearAllUserMarks = useCallback(() => {
|
|
45
|
+
setUserMarks([])
|
|
46
|
+
}, [])
|
|
339
47
|
|
|
340
48
|
if (error) {
|
|
341
49
|
return (
|
|
@@ -353,151 +61,30 @@ export function View({ contentProvider, actions, renderers }: ViewProps) {
|
|
|
353
61
|
)
|
|
354
62
|
}
|
|
355
63
|
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
content,
|
|
366
|
-
lineCount,
|
|
367
|
-
cursor: cursorLine,
|
|
368
|
-
selectionStart,
|
|
369
|
-
selectionEnd,
|
|
370
|
-
})
|
|
371
|
-
}
|
|
372
|
-
// No renderer for this custom format — fall back to text
|
|
373
|
-
const text = getTextContent(content)
|
|
374
|
-
return (
|
|
375
|
-
<CodeView
|
|
376
|
-
content={text}
|
|
377
|
-
showLineNumbers={false}
|
|
378
|
-
cursor={cursorLine}
|
|
379
|
-
selectionStart={selectionStart}
|
|
380
|
-
selectionEnd={selectionEnd}
|
|
381
|
-
docRef={docRef}
|
|
382
|
-
/>
|
|
383
|
-
)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Built-in formats
|
|
387
|
-
switch (content.format) {
|
|
388
|
-
case "markdown":
|
|
389
|
-
return (
|
|
390
|
-
<MarkdownView
|
|
391
|
-
content={content.markdown}
|
|
392
|
-
showLineNumbers={showLineNumbers}
|
|
393
|
-
activeBlock={cursorLine}
|
|
394
|
-
selectedBlocks={
|
|
395
|
-
selectionStart != null && selectionEnd != null
|
|
396
|
-
? { start: selectionStart, end: selectionEnd }
|
|
397
|
-
: undefined
|
|
398
|
-
}
|
|
399
|
-
matchingBlocks={matchingBlocks}
|
|
400
|
-
currentMatchBlock={currentMatchBlock}
|
|
401
|
-
toggledBlocks={toggledBlocks}
|
|
402
|
-
docRef={docRef}
|
|
403
|
-
/>
|
|
404
|
-
)
|
|
405
|
-
case "code":
|
|
406
|
-
return (
|
|
407
|
-
<CodeView
|
|
408
|
-
content={content.code}
|
|
409
|
-
language={content.language}
|
|
410
|
-
showLineNumbers={showLineNumbers}
|
|
411
|
-
cursor={cursorLine}
|
|
412
|
-
selectionStart={selectionStart}
|
|
413
|
-
selectionEnd={selectionEnd}
|
|
414
|
-
matchingLines={matchingLinesSet}
|
|
415
|
-
currentMatchLine={currentMatchLine}
|
|
416
|
-
toggledLines={toggledLines}
|
|
417
|
-
docRef={docRef}
|
|
418
|
-
/>
|
|
419
|
-
)
|
|
420
|
-
case "text":
|
|
421
|
-
return (
|
|
422
|
-
<CodeView
|
|
423
|
-
content={content.text}
|
|
424
|
-
showLineNumbers={showLineNumbers}
|
|
425
|
-
cursor={cursorLine}
|
|
426
|
-
selectionStart={selectionStart}
|
|
427
|
-
selectionEnd={selectionEnd}
|
|
428
|
-
matchingLines={matchingLinesSet}
|
|
429
|
-
currentMatchLine={currentMatchLine}
|
|
430
|
-
toggledLines={toggledLines}
|
|
431
|
-
docRef={docRef}
|
|
432
|
-
/>
|
|
433
|
-
)
|
|
434
|
-
case "image":
|
|
435
|
-
return <ImageView src={content.src} />
|
|
436
|
-
case "table":
|
|
437
|
-
return (
|
|
438
|
-
<Table
|
|
439
|
-
columns={content.columns}
|
|
440
|
-
rows={content.rows}
|
|
441
|
-
showLineNumbers={showLineNumbers}
|
|
442
|
-
cursor={cursorLine}
|
|
443
|
-
selectionStart={selectionStart}
|
|
444
|
-
selectionEnd={selectionEnd}
|
|
445
|
-
matchingRows={matchingRowsSet}
|
|
446
|
-
currentMatchRow={currentMatchRow}
|
|
447
|
-
toggledRows={toggledRows}
|
|
448
|
-
docRef={docRef}
|
|
449
|
-
/>
|
|
450
|
-
)
|
|
451
|
-
}
|
|
64
|
+
const shared = {
|
|
65
|
+
providerMarks,
|
|
66
|
+
userMarks,
|
|
67
|
+
setMarkSet,
|
|
68
|
+
clearMarkNamespace,
|
|
69
|
+
clearAllUserMarks,
|
|
70
|
+
reload,
|
|
71
|
+
streaming,
|
|
72
|
+
actions,
|
|
452
73
|
}
|
|
453
74
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
const selectionItems =
|
|
458
|
-
toggledCount > 0
|
|
459
|
-
? [{ label: "Selected:", value: String(toggledCount) }]
|
|
460
|
-
: selectionCount > 0
|
|
461
|
-
? [{ label: "Selected:", value: String(selectionCount) }]
|
|
462
|
-
: []
|
|
463
|
-
|
|
464
|
-
const statusItems = [
|
|
465
|
-
{ label: "Theme:", value: themeName },
|
|
466
|
-
{ label: "Format:", value: content.format },
|
|
467
|
-
...(isBuiltinContent(content) && content.format === "table"
|
|
468
|
-
? [
|
|
469
|
-
{ label: "Rows:", value: String(content.rows.length) },
|
|
470
|
-
{ label: "Cols:", value: String(content.columns.length) },
|
|
471
|
-
]
|
|
472
|
-
: [{ label: "Lines:", value: String(lineCount) }]),
|
|
473
|
-
{ label: "Mode:", value: nav.mode },
|
|
474
|
-
{ label: "Cursor:", value: nav.cursor ? String(nav.cursor.line) : "-" },
|
|
475
|
-
...selectionItems,
|
|
476
|
-
...(streaming ? [{ label: "Status:", value: "streaming" }] : []),
|
|
477
|
-
...(nav.searchActive ? [{ label: "Search:", value: nav.searchQuery }] : []),
|
|
478
|
-
]
|
|
75
|
+
if (isCustomContent(content)) {
|
|
76
|
+
return <CustomSubview content={content} renderers={renderers} {...shared} />
|
|
77
|
+
}
|
|
479
78
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
onQueryChange: nav.setSearchQuery,
|
|
492
|
-
onSubmit: nav.submitSearch,
|
|
493
|
-
onCancel: () => {
|
|
494
|
-
nav.setSearchQuery("")
|
|
495
|
-
},
|
|
496
|
-
matchCount: nav.matchingLines.length,
|
|
497
|
-
currentMatch: nav.currentMatchIndex,
|
|
498
|
-
}}
|
|
499
|
-
>
|
|
500
|
-
{renderContent()}
|
|
501
|
-
</AppLayout>
|
|
502
|
-
)
|
|
79
|
+
switch (content.format) {
|
|
80
|
+
case "markdown":
|
|
81
|
+
return <MarkdownSubview content={content} {...shared} />
|
|
82
|
+
case "code":
|
|
83
|
+
case "text":
|
|
84
|
+
return <CodeSubview content={content} {...shared} />
|
|
85
|
+
case "table":
|
|
86
|
+
return <TableSubview content={content} {...shared} />
|
|
87
|
+
case "image":
|
|
88
|
+
return <ImageSubview content={content} {...shared} />
|
|
89
|
+
}
|
|
503
90
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
import { AppLayout, type StatusBarItem } from "@tooee/layout"
|
|
3
|
+
import type { NavigationState, SearchState } from "@tooee/shell"
|
|
4
|
+
import type { AnyContent } from "../types.js"
|
|
5
|
+
|
|
6
|
+
interface SubviewLayoutProps {
|
|
7
|
+
content: AnyContent
|
|
8
|
+
nav: NavigationState & SearchState
|
|
9
|
+
streaming: boolean
|
|
10
|
+
themeName: string
|
|
11
|
+
extraStatusItems?: StatusBarItem[]
|
|
12
|
+
children: ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SubviewLayout({
|
|
16
|
+
content,
|
|
17
|
+
nav,
|
|
18
|
+
streaming,
|
|
19
|
+
themeName,
|
|
20
|
+
extraStatusItems,
|
|
21
|
+
children,
|
|
22
|
+
}: SubviewLayoutProps) {
|
|
23
|
+
const statusItems = [
|
|
24
|
+
{ label: "Theme:", value: themeName },
|
|
25
|
+
...(extraStatusItems ?? []),
|
|
26
|
+
{ label: "Mode:", value: nav.mode },
|
|
27
|
+
{ label: "Cursor:", value: nav.cursor ? String(nav.cursor.line) : "-" },
|
|
28
|
+
...(streaming ? [{ label: "Status:", value: "streaming" }] : []),
|
|
29
|
+
...(nav.searchActive ? [{ label: "Search:", value: nav.searchQuery }] : []),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<AppLayout
|
|
34
|
+
titleBar={
|
|
35
|
+
content.title
|
|
36
|
+
? { title: content.title, subtitle: content.format }
|
|
37
|
+
: { title: content.format }
|
|
38
|
+
}
|
|
39
|
+
statusBar={{ items: statusItems }}
|
|
40
|
+
searchBar={{
|
|
41
|
+
active: nav.searchActive,
|
|
42
|
+
query: nav.searchQuery,
|
|
43
|
+
onQueryChange: nav.setSearchQuery,
|
|
44
|
+
onSubmit: nav.submitSearch,
|
|
45
|
+
onCancel: () => {
|
|
46
|
+
nav.setSearchQuery("")
|
|
47
|
+
},
|
|
48
|
+
matchCount: nav.matchingLines.length,
|
|
49
|
+
currentMatch: nav.currentMatchIndex,
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{children}
|
|
53
|
+
</AppLayout>
|
|
54
|
+
)
|
|
55
|
+
}
|