@deepseek-ai/dsh-client-ui-sidebar-documentpreview 0.1.5-rc.2 → 0.1.6-alpha.1
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.i18n.yaml +2 -2
- package/README.md +5 -5
- package/README.zh.md +9 -9
- package/lib/client.js +262 -64
- package/lib/types/client/LoadingIndicator.d.ts +5 -1
- package/lib/types/client/document/registry.d.ts +17 -2
- package/lib/types/client/document/suffix.d.ts +24 -0
- package/lib/types/client/document/unviewable.d.ts +7 -0
- package/lib/types/client/image/ImageBody.d.ts +5 -3
- package/lib/types/client/image/index.d.ts +2 -0
- package/lib/types/client/locales.d.ts +2 -0
- package/package.json +16 -16
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/client/ui-sidebar-documentpreview/README.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
5
|
+
README.md: 3da629129f36d18a20eef74be8f96a1455d532f3
|
|
6
|
+
README.zh.md: 6f1eb7eef1f690ebdfdf38cfdd1f62b1314d7d66
|
package/README.md
CHANGED
|
@@ -27,10 +27,10 @@ Preview readable files in the right Sidebar and choose among registered renderer
|
|
|
27
27
|
## What it registers
|
|
28
28
|
|
|
29
29
|
- **The type** — `ctx.sidebarRightTabs.register(...)` with id `@deepseek-ai/dsh-client-ui-sidebar-documentpreview` (this implementation's identity in the tab system, and the key its body registers under), kind `text`, pattern `dsh-resource://file/**`, band `fallback`. `canOpen` accepts only Session addresses, whose paths may be relative or absolute; bare `absolute` addresses are not claimed. A type registered at the `extension` or `builtin` band for a narrower pattern (say `*.png`) takes those addresses; other supported files land here. The whole address is the content identity, so two files with one name in different directories, or one path under two sessions, are two tabs; the decoded basename is the tab title, and the keyed `sidebar.right.pane.tab.title` seat places its extension-specific `FileTypeIcon` before that title.
|
|
30
|
-
- **The body** — the keyed `sidebar.right.pane.tab` seat under the type's id. Its fixed header shows the Host's absolute path when available, otherwise the requested path; directories use tertiary label colour, the name uses primary label colour, and a clipped path retains and fades toward its final segment while its tooltip exposes the full value. A dropdown selects among matching renderers and plain text. A wrap toggle appears only when the selected renderer declares `wrap: true`; its glyph describes the mode the click selects, and the per-tab preference starts on. Reload stays in this header, not the Sidebar's tab strip. The body reaches every pane edge; each renderer owns its content inset and may own an inner scrollport. This intentionally differs from the Files tab's 2px right-side scrollbar offset: previews keep the full pane width so edge-to-edge HTML and code scrollports end at the pane edge.
|
|
30
|
+
- **The body** — the keyed `sidebar.right.pane.tab` seat under the type's id. Its fixed header shows the Host's absolute path when available, otherwise the requested path; directories use tertiary label colour, the name uses primary label colour, and a clipped path retains and fades toward its final segment while its tooltip exposes the full value. A dropdown selects among matching renderers and plain text; a suffix declared binary (bitmap images, PDF) drops the plain-text fallback, and a single remaining candidate renders no viewer control at all. A known binary container suffix with no registered renderer (audio, video, archives, office documents) shows an unsupported empty state under the path header — the file-type icon and one explanatory line — and never issues a read. A wrap toggle appears only when the selected renderer declares `wrap: true`; its glyph describes the mode the click selects, and the per-tab preference starts on. Reload stays in this header, not the Sidebar's tab strip. The body reaches every pane edge; each renderer owns its content inset and may own an inner scrollport. This intentionally differs from the Files tab's 2px right-side scrollbar offset: previews keep the full pane width so edge-to-edge HTML and code scrollports end at the pane edge.
|
|
31
31
|
- **Shared loading and view state**, session-scoped and bucketed by tab id. The store holds accumulated pages or complete bytes, read and observed versions, loading/failure state, renderer choice, scroll offset, wrap, and the answered navigation revision. The ordinary inject face calls Remote readers and writes through declared store actions. Reloads and loading-mode changes retire older requests; the tab's abort signal forgets its state.
|
|
32
32
|
|
|
33
|
-
Document implementations register metadata with `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` and a body under the same `id` in the keyed, Session-scoped `sidebar.right.tab.document` child slot. Own both registrations with effects and wait for the child slot through `ctx.slots.inject`. Bodies receive `resourceAddress`, prepared `content`, `wrap`, `scrollportRef`, and the standard `useTabInfo`/`useResource` hooks; they do not receive a custom resource loader. A renderer that owns an inner scrolling element attaches `scrollportRef` to it, and the owner returns to the shared body when that element unmounts. Metadata declares `loading: 'text-pages'` or `'bytes-complete'`. The registry retains all matching alternatives: `extension` (the default) ranks above `builtin`, then longer suffixes rank first, then registration order. The dropdown preserves a selected implementation while it remains available; removing it selects the next candidate. Builtin bodies use these same registrations.
|
|
33
|
+
Document implementations register metadata with `ctx.documentPreviews.register({ id, extensions, binaryExtensions?, priority, title, loading, wrap? })` and a body under the same `id` in the keyed, Session-scoped `sidebar.right.tab.document` child slot. `binaryExtensions` names the suffixes among `extensions` whose bytes are not readable text; a file matching one loses the plain-text fallback among its viewer choices. Own both registrations with effects and wait for the child slot through `ctx.slots.inject`. Bodies receive `resourceAddress`, prepared `content`, `wrap`, `scrollportRef`, and the standard `useTabInfo`/`useResource` hooks; they do not receive a custom resource loader. A renderer that owns an inner scrolling element attaches `scrollportRef` to it, and the owner returns to the shared body when that element unmounts. Metadata declares `loading: 'text-pages'` or `'bytes-complete'`. The registry retains all matching alternatives: `extension` (the default) ranks above `builtin`, then longer suffixes rank first, then registration order. The dropdown preserves a selected implementation while it remains available; removing it selects the next candidate. Builtin bodies use these same registrations.
|
|
34
34
|
|
|
35
35
|
<a id="addresses"></a>
|
|
36
36
|
## Addresses
|
|
@@ -49,11 +49,11 @@ The body reads its record, navigation and lifetime through `useTabInfo().tab`. `
|
|
|
49
49
|
|
|
50
50
|
HTML fills the body edge to edge in a Blob iframe with exactly `sandbox="allow-scripts"`, without `allow-same-origin`; scripts cannot access the parent application's origin or file reader. The renderer loads directly declared relative `.js` classic scripts and `.css` stylesheets through its ordinary inject callback to `remote.workspaceFiles.readRelated`, with fixed safety limits of 4 MiB per asset, 32 MiB total, and 64 distinct assets. Host code resolves the related path; `rpc.ts` decodes the returned bytes. Inside the renderer, base64 is used only to embed the iframe bootstrap payload in script text. A `<base href>` leaves dependency resolution to the browser, as do HTTPS resources. Local module imports, CSS `url()`/`@import`, and dynamic `fetch` do not use Host file access. Read failures, invalid UTF-8, or exceeded limits fail the preview rather than publishing a partial asset package. Replacing or unmounting the document releases its Blob URL.
|
|
51
51
|
|
|
52
|
-
PNG, JPEG, GIF, WebP, BMP, ICO, and SVG render through Blob URLs in an `<img>` static-image context.
|
|
52
|
+
PNG, JPEG, GIF, WebP, BMP, ICO, and SVG render through Blob URLs in an `<img>` static-image context, rounded inside a 12px inset. A wider image scales down to the pane's width at its intrinsic aspect ratio, a smaller image centres at its intrinsic CSS-pixel dimensions, and a taller image scrolls vertically. The renderer provides neither zoom nor drag-to-pan. SVG markup never enters the application DOM or an iframe, so its scripts cannot execute or reach the parent page. Replacing or unmounting the image revokes its Blob URL.
|
|
53
53
|
|
|
54
54
|
Shared copy comes from `sidebarDocumentPreview`; each builtin renderer owns its localized labels.
|
|
55
55
|
|
|
56
|
-
Initial reads, additional pages, and HTML/PDF/image preparation share
|
|
56
|
+
Initial reads, additional pages, and HTML/PDF/image preparation share an icon-only loading spinner that exposes its label to assistive technology and respects reduced-motion preferences; every wait before content exists centres the spinner in the pane, so opening a file shows one spinner in one position until the body appears. Loaded pages stay visible while another page loads. PDF pages fill the pane's width edge to edge as one vertical sequence and render lazily near the viewport; an unrendered page holds its place as a quiet 3:4 placeholder block. Code previews show source line numbers by default without including them in copied text; plain text uses the same font size and line height as code. Code sits on the pane's own background rather than the chat card's fill; its banner is adjacent to a full-height inner scrollport, so both scrollbars begin below the copy control.
|
|
57
57
|
|
|
58
58
|
<a id="navigation"></a>
|
|
59
59
|
## Navigation
|
|
@@ -74,7 +74,7 @@ No direct effect; what the user reads here never enters a model request.
|
|
|
74
74
|
<a id="known-limitations-and-deferred-work"></a>
|
|
75
75
|
- **Preview, not editing.** The viewers provide no file editing or shared search interface; a directory address fails with `not-regular-file`. Unknown extensions use the plain-text reader and remain subject to its UTF-8/NUL checks.
|
|
76
76
|
- **Sequential text and bounded complete files.** Deep source lines require the preceding pages; PDF, HTML, and images require a complete result within the Host's `maxFileBytes` cap.
|
|
77
|
-
- **Byte-view scroll state is not restored.** PDF, HTML, and images can return to the top when their renderer remounts or reloads;
|
|
77
|
+
- **Byte-view scroll state is not restored.** PDF, HTML, and images can return to the top when their renderer remounts or reloads; images fit the pane's width and never scroll horizontally, and HTML iframe scrolling belongs to its opaque browsing context.
|
|
78
78
|
- **Finite local HTML dependencies.** Only direct classic `.js` and stylesheet `.css` references are packed. Browser-resolved resources retain browser origin and network restrictions; no runtime file-read bridge is exposed to the iframe.
|
|
79
79
|
- **Package-local wrap glyphs.** `IconWrapFill16` and `IconNowrapFill16` live in `src/client/icons.tsx` until the shared icon set carries them; their props already match the shared icon contract.
|
|
80
80
|
- **Scroll writes are unthrottled.** Every scroll event records its offset in the store; the line blocks are memoized so the resulting re-render hands React the same elements back.
|
package/README.zh.md
CHANGED
|
@@ -26,16 +26,16 @@ kind: "package-reference"
|
|
|
26
26
|
<a id="what-it-registers"></a>
|
|
27
27
|
## 注册了什么
|
|
28
28
|
|
|
29
|
-
- **类型** —— `ctx.sidebarRightTabs.register(...)`,id 为 `@deepseek-ai/dsh-client-ui-sidebar-documentpreview`(这个实现在 tab
|
|
30
|
-
- **正文** —— keyed slot `sidebar.right.pane.tab`,键为类型的 id。固定头部在可用时显示 Host
|
|
29
|
+
- **类型** —— `ctx.sidebarRightTabs.register(...)`,id 为 `@deepseek-ai/dsh-client-ui-sidebar-documentpreview`(这个实现在 tab 系统中的身份,也是其正文注册所用的键),kind `text`,pattern `dsh-resource://file/**`,档位 `fallback`。`canOpen` 只接受 Session 地址,其中路径可为相对或绝对路径;不认领裸 `absolute` 地址。在 `extension` 或 `builtin` 档以更窄 pattern(比如 `*.png`)注册的类型接走那些地址;其他受支持文件落到这里。整个地址就是内容身份,所以不同目录下同名的两个文件、或同一路径在两个会话之下,是两个 tab;解码后的 basename 是 tab 标题,keyed slot `sidebar.right.pane.tab.title` 会在标题前放置按扩展名选择的 `FileTypeIcon`。
|
|
30
|
+
- **正文** —— keyed slot `sidebar.right.pane.tab`,键为类型的 id。固定头部在可用时显示 Host 的绝对路径,否则显示请求路径;目录使用三级标签色,文件名使用一级标签色,路径过长时保留末段并向开头淡出,提示中仍提供完整值。下拉菜单可在匹配的渲染器与纯文本间切换;声明为二进制的后缀(位图图片、PDF)不提供纯文本兜底,只剩一个候选时完全不渲染查看器控件。已知的二进制容器后缀(音频、视频、压缩包、office 文档)没有注册渲染器时,在路径头部下方显示不支持预览的空态——文件类型图标加一行说明——并且不会发起读取。仅当所选渲染器声明 `wrap: true` 时显示换行开关;图标表示点击后切换到的模式,该偏好按 tab 保存,初始开启。重新载入仍在此头部,不放入 Sidebar 的 tab 条。正文贴合格的每条边,各渲染器自行提供内容留白,并可拥有内部滚动区。这与 Files tab 右侧预留 2px 滚动条间距的布局有意不同:Preview 使用格的完整宽度,使贴边 HTML 与代码滚动区终止于格的边缘。
|
|
31
31
|
- **共享加载与视图状态**,会话作用域、按 tab id 分桶。store 持有累计页或完整字节、读取与观察版本、加载/失败状态、渲染器选择、滚动位置、换行和已响应的导航 revision。普通 inject face 调用 Remote 读取,并经声明的 store action 写入。重新载入和加载模式变化会淘汰旧请求;tab 的中止信号清理其状态。
|
|
32
32
|
|
|
33
|
-
文档实现在 `ctx.documentPreviews.register({ id, extensions, priority, title, loading, wrap? })` 注册元数据,并以相同 `id` 向 keyed、Session 作用域的子 slot `sidebar.right.tab.document`
|
|
33
|
+
文档实现在 `ctx.documentPreviews.register({ id, extensions, binaryExtensions?, priority, title, loading, wrap? })` 注册元数据,并以相同 `id` 向 keyed、Session 作用域的子 slot `sidebar.right.tab.document` 注册正文。`binaryExtensions` 列出 `extensions` 中字节不可按文本阅读的后缀;匹配这类后缀的文件在其查看器选项中不再提供纯文本兜底。两处注册都由 effect 持有,通过 `ctx.slots.inject` 等待子 slot。正文接收 `resourceAddress`、准备好的 `content`、`wrap`、`scrollportRef` 和标准 `useTabInfo`/`useResource` 钩子,不接收自定义资源加载器。拥有内部滚动元素的渲染器把 `scrollportRef` 挂到该元素上;该元素卸载后,owner 恢复使用共享正文。元数据声明 `loading: 'text-pages'` 或 `'bytes-complete'`。注册表保留所有匹配备选:`extension`(默认)优先于 `builtin`,随后按更长的后缀、再按注册顺序排列。所选实现仍可用时,下拉选择保持不变;移除后选择下一个候选。内置正文也使用相同注册方式。
|
|
34
34
|
|
|
35
35
|
<a id="addresses"></a>
|
|
36
36
|
## 地址
|
|
37
37
|
|
|
38
|
-
tab 使用 `fileAddressFor` 构造的 Session 地址,携带相对或绝对路径。`hostFileOf(address)` 仅从地址取得 Session,不接收外部 Session 参数,也不借用当前或 Tab Session。Host 通过 Session 文件系统解析文件及关联路径,由该后端控制读取权限。任何 UI(包括 Global 组件)都共享同一完整地址的元数据。[Workspace Files README](../../api/workspace-files/README.zh.md)定义这些规则;渲染器选择不改变导航地址。
|
|
38
|
+
tab 使用 `fileAddressFor` 构造的 Session 地址,携带相对或绝对路径。`hostFileOf(address)` 仅从地址取得 Session,不接收外部 Session 参数,也不借用当前或 Tab Session。Host 通过 Session 文件系统解析文件及关联路径,由该后端控制读取权限。任何 UI(包括 Global 组件)都共享同一完整地址的元数据。[Workspace Files README](../../api/workspace-files/README.zh.md) 定义这些规则;渲染器选择不改变导航地址。
|
|
39
39
|
|
|
40
40
|
<a id="how-it-reads"></a>
|
|
41
41
|
## 怎么读
|
|
@@ -43,17 +43,17 @@ tab 使用 `fileAddressFor` 构造的 Session 地址,携带相对或绝对路
|
|
|
43
43
|
正文通过 `useTabInfo().tab` 读取记录、导航和生命周期。`useResource<'file'>(tab.contentId)` 提供元数据,普通 inject 回调提供内容读取:
|
|
44
44
|
|
|
45
45
|
- 资源快照仅包含 `status`、`value` 和 `failure`;`value` 是 `WorkspaceFileStat` 元数据。提供方可用后,内容读取无需等待首个元数据帧。观察失败优先于 Preview 的变更提示显示;两者都不会自动替换已加载内容。
|
|
46
|
-
- **文本页** —— 纯文本、Markdown 和代码通过 inject 回调调用 `remote.workspaceFiles.read(sessionId, path, { offset }, signal)`。首次挂载读取第一页;滚动到正文末尾或点击 **加载更多** 会读取下一页,直到 `eof`。owner 以 `{ kind: 'text', text, pages, eof }`
|
|
46
|
+
- **文本页** —— 纯文本、Markdown 和代码通过 inject 回调调用 `remote.workspaceFiles.read(sessionId, path, { offset }, signal)`。首次挂载读取第一页;滚动到正文末尾或点击 **加载更多** 会读取下一页,直到 `eof`。owner 以 `{ kind: 'text', text, pages, eof }` 提供累计前缀,包含源码偏移和行数。Markdown 和代码增量渲染此前缀,不把每页当成独立文档。第一页之后到达的更新版本页会使读取从头开始,避免混合版本。尚无内容时,失败会以文件类型图标、说明与重试按钮填满正文;较晚的失败保留已有内容并在其下提供重试。
|
|
47
47
|
- **完整字节** —— PDF、HTML 和常见图片通过 inject 回调调用 `remote.workspaceFiles.readAll(sessionId, path, signal)`。`rpc.ts` 将线路上的 base64 解码为 `data: Uint8Array<ArrayBuffer>`,供 `{ kind: 'bytes', data }` 使用。Host 的 `maxFileBytes` 上限拒绝超大文件,不截断。PDF 在传给 worker 前复制保留的字节,使 Preview 缓冲区仍可使用。字节仅保存在临时视图状态中,绝不进入持久布局或 Session JSONL。加载模式变化会淘汰先前结果。
|
|
48
48
|
- **重新载入** —— 仅当前 Preview tab 通过自己的 Remote 回调重读,保留滚动偏好并淘汰旧请求。变更提示将读取版本及起读时的观察版本与后续 `resource.value.version` 比较;刷新前已观察到的版本不会被当成新变化。读取既不刷新共享元数据,也不清除其它 tab 的提示。
|
|
49
49
|
|
|
50
50
|
HTML 以贴合正文四边的 Blob iframe 运行,沙箱属性严格为 `sandbox="allow-scripts"`,不含 `allow-same-origin`;脚本无法访问父应用的源或文件读取接口。渲染器通过普通 inject 回调调用 `remote.workspaceFiles.readRelated`,加载直接声明的相对 `.js` 经典脚本和 `.css` 样式表;固定安全上限为单个资源 4 MiB、总计 32 MiB、64 个不同资源。Host 代码解析关联路径,`rpc.ts` 解码返回的字节。在渲染器内部,base64 仅用于把 iframe 引导载荷嵌入脚本文本。`<base href>` 将依赖解析交给浏览器,HTTPS 资源也由浏览器处理。本地模块 import、CSS `url()`/`@import` 和动态 `fetch` 不使用 Host 文件访问。读取失败、无效 UTF-8 或超出上限都使预览失败,不发布部分资源包。替换或卸载文档会释放其 Blob URL。
|
|
51
51
|
|
|
52
|
-
PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 通过 Blob URL 在 `<img>`
|
|
52
|
+
PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 通过 Blob URL 在 `<img>` 静态图片上下文中渲染,带 12px 内边距和圆角。宽图按固有纵横比缩小到面板宽度,小图按固有 CSS 像素尺寸居中,超高图纵向滚动。渲染器既不提供缩放,也不提供拖拽平移。SVG 标记绝不进入应用 DOM 或 iframe,因此其中的脚本无法执行,也无法访问父页面。替换或卸载图片会撤销其 Blob URL。
|
|
53
53
|
|
|
54
54
|
共享文案来自 `sidebarDocumentPreview`;各内置渲染器拥有自己的本地化标签。
|
|
55
55
|
|
|
56
|
-
首次读取、追加页及 HTML/PDF
|
|
56
|
+
首次读取、追加页及 HTML/PDF/图片准备共用仅图标的加载 spinner,其标签暴露给辅助技术,并遵循减少动态效果偏好;内容出现前的每个等待都把 spinner 居中在面板中,打开文件到正文出现始终是同一位置的一个 spinner。下一页加载期间保留已显示的内容。PDF 页面贴边占满面板宽度,组成一个纵向连续序列并在接近视口时惰性渲染;未渲染的页以安静的 3:4 占位块保持位置。代码预览默认显示源码行号,但复制文本不包含行号;纯文本与代码使用相同字号和行高。代码直接坐在分栏自身的背景上,而不是会话卡片的填充色;复制条与占满剩余高度的内部滚动区相邻,因此横纵滚动条都从复制控件下方开始。
|
|
57
57
|
|
|
58
58
|
<a id="navigation"></a>
|
|
59
59
|
## 导航
|
|
@@ -73,8 +73,8 @@ PNG、JPEG、GIF、WebP、BMP、ICO 和 SVG 通过 Blob URL 在 `<img>` 静态
|
|
|
73
73
|
|
|
74
74
|
<a id="known-limitations-and-deferred-work"></a>
|
|
75
75
|
- **预览而非编辑。** 查看器不提供文件编辑或共享搜索接口;目录地址以 `not-regular-file` 失败。未知扩展名使用纯文本读取,仍受其 UTF-8/NUL 检查限制。
|
|
76
|
-
- **文本顺序分页,完整文件受限。**
|
|
77
|
-
- **字节视图不恢复滚动位置。** PDF、HTML
|
|
76
|
+
- **文本顺序分页,完整文件受限。** 定位到较深处的源码行需要先加载此前各页;PDF、HTML 和图片必须取得 Host `maxFileBytes` 上限内的完整结果。
|
|
77
|
+
- **字节视图不恢复滚动位置。** PDF、HTML 与图片的渲染器重新挂载或重新载入时可能回到顶部;图片适配面板宽度、不产生横向滚动,HTML iframe 的滚动属于其不透明浏览上下文。
|
|
78
78
|
- **本地 HTML 依赖集合有限。** 只打包直接引用的经典 `.js` 脚本和 `.css` 样式表。浏览器解析的资源仍受浏览器源与网络规则限制;iframe 不获得运行时文件读取桥接。
|
|
79
79
|
- **换行图标为包内自绘。** `IconWrapFill16` 与 `IconNowrapFill16` 住在 `src/client/icons.tsx`,直到共享图标集提供为止;它们的 props 已与共享图标契约一致。
|
|
80
80
|
- **滚动写入未节流。** 每次滚动事件都把偏移记进 store;行块已 memo 化,于是由此引发的重渲染交还给 React 的是同一批元素。
|
package/lib/client.js
CHANGED
|
@@ -1103,17 +1103,22 @@ window.__ModuleLoader__.load({
|
|
|
1103
1103
|
};
|
|
1104
1104
|
//#endregion
|
|
1105
1105
|
//#region lib/types/client/LoadingIndicator.js
|
|
1106
|
-
/**
|
|
1106
|
+
/**
|
|
1107
|
+
* @param props - localized status label, carried as the accessible name with
|
|
1108
|
+
* no visible text, and optional placement style.
|
|
1109
|
+
* @returns an animated, accessible loading status.
|
|
1110
|
+
*/
|
|
1107
1111
|
function LoadingIndicator({ label, className }) {
|
|
1108
|
-
return (0, react_jsx_runtime.
|
|
1112
|
+
return (0, react_jsx_runtime.jsx)("span", {
|
|
1109
1113
|
className: clsx(LoadingIndicator_module_css_default.loading, className),
|
|
1110
1114
|
role: "status",
|
|
1115
|
+
"aria-label": label,
|
|
1111
1116
|
"data-document-loading": true,
|
|
1112
|
-
children:
|
|
1117
|
+
children: (0, react_jsx_runtime.jsx)("span", {
|
|
1113
1118
|
className: LoadingIndicator_module_css_default.icon,
|
|
1114
1119
|
"aria-hidden": "true",
|
|
1115
1120
|
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconLoadingOutline16, {})
|
|
1116
|
-
})
|
|
1121
|
+
})
|
|
1117
1122
|
});
|
|
1118
1123
|
}
|
|
1119
1124
|
//#endregion
|
|
@@ -1158,6 +1163,38 @@ window.__ModuleLoader__.load({
|
|
|
1158
1163
|
};
|
|
1159
1164
|
}
|
|
1160
1165
|
//#endregion
|
|
1166
|
+
//#region lib/types/client/document/suffix.js
|
|
1167
|
+
/**
|
|
1168
|
+
* Filename suffix matching shared by the preview registry and the owner's
|
|
1169
|
+
* unviewable list, so every consumer normalizes paths and suffixes alike.
|
|
1170
|
+
*/
|
|
1171
|
+
/**
|
|
1172
|
+
* Normalize one declared suffix for comparison.
|
|
1173
|
+
* @param extension - declared file suffix, with or without a leading dot.
|
|
1174
|
+
* @returns the suffix lowercased with any leading dot dropped.
|
|
1175
|
+
*/
|
|
1176
|
+
function normalizeSuffix(extension) {
|
|
1177
|
+
return extension.toLowerCase().replace(/^\./u, "");
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* The filename a path's suffixes are matched against.
|
|
1181
|
+
* @param path - decoded filename or file path; `\` is accepted as a separator.
|
|
1182
|
+
* @returns the lowercased final path segment.
|
|
1183
|
+
*/
|
|
1184
|
+
function documentFileName(path) {
|
|
1185
|
+
const normalized = path.replaceAll("\\", "/").toLowerCase();
|
|
1186
|
+
return normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
1187
|
+
}
|
|
1188
|
+
/**
|
|
1189
|
+
* The longest declared suffix ending the filename.
|
|
1190
|
+
* @param name - lowercased filename from {@link documentFileName}.
|
|
1191
|
+
* @param extensions - declared suffixes; compound suffixes such as `tar.gz` are accepted.
|
|
1192
|
+
* @returns the matched suffix's normalized length, or 0 when none matches.
|
|
1193
|
+
*/
|
|
1194
|
+
function matchedSuffixLength(name, extensions) {
|
|
1195
|
+
return Math.max(0, ...extensions.map(normalizeSuffix).filter((extension) => name.endsWith(`.${extension}`)).map((extension) => extension.length));
|
|
1196
|
+
}
|
|
1197
|
+
//#endregion
|
|
1161
1198
|
//#region lib/types/client/document/registry.js
|
|
1162
1199
|
/** File-extension preview registrations; component dispatch belongs to the keyed document slot. */
|
|
1163
1200
|
/**
|
|
@@ -1167,15 +1204,24 @@ window.__ModuleLoader__.load({
|
|
|
1167
1204
|
* @returns matching implementations, external band first, then longest suffix.
|
|
1168
1205
|
*/
|
|
1169
1206
|
function matchingDocumentPreviews(definitions, path) {
|
|
1170
|
-
const
|
|
1171
|
-
const name = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
1207
|
+
const name = documentFileName(path);
|
|
1172
1208
|
return definitions.map((definition, order) => ({
|
|
1173
1209
|
definition,
|
|
1174
1210
|
order,
|
|
1175
1211
|
rank: definition.priority === "builtin" ? 0 : 1,
|
|
1176
|
-
length:
|
|
1212
|
+
length: matchedSuffixLength(name, definition.extensions)
|
|
1177
1213
|
})).filter((candidate) => candidate.length > 0).sort((left, right) => right.rank - left.rank || right.length - left.length || left.order - right.order).map((candidate) => candidate.definition);
|
|
1178
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Whether any registered implementation declares the filename's suffix binary.
|
|
1217
|
+
* @param definitions - registered implementations.
|
|
1218
|
+
* @param path - decoded filename or file path.
|
|
1219
|
+
* @returns true when a declared binary suffix matches the filename.
|
|
1220
|
+
*/
|
|
1221
|
+
function binaryDocumentPath(definitions, path) {
|
|
1222
|
+
const name = documentFileName(path);
|
|
1223
|
+
return definitions.some((definition) => matchedSuffixLength(name, definition.binaryExtensions ?? []) > 0);
|
|
1224
|
+
}
|
|
1179
1225
|
/** Observable registry of all live implementations, including lower-priority alternatives. */
|
|
1180
1226
|
var DocumentPreviewRegistry = class {
|
|
1181
1227
|
registered = /* @__PURE__ */ new Map();
|
|
@@ -1199,11 +1245,15 @@ window.__ModuleLoader__.load({
|
|
|
1199
1245
|
};
|
|
1200
1246
|
/**
|
|
1201
1247
|
* Register metadata separately from the matching keyed slot component.
|
|
1202
|
-
* @param definition - unique implementation and recognized suffixes
|
|
1203
|
-
*
|
|
1248
|
+
* @param definition - unique implementation and recognized suffixes; every
|
|
1249
|
+
* `binaryExtensions` entry must appear in `extensions`.
|
|
1250
|
+
* @returns an idempotent disposer; duplicate live implementation names and
|
|
1251
|
+
* binary suffixes outside `extensions` throw.
|
|
1204
1252
|
*/
|
|
1205
1253
|
register(definition) {
|
|
1206
1254
|
if (this.registered.has(definition.id)) throw new Error(`documentPreviews: duplicate implementation "${definition.id}"`);
|
|
1255
|
+
const declared = new Set(definition.extensions.map(normalizeSuffix));
|
|
1256
|
+
for (const extension of definition.binaryExtensions ?? []) if (!declared.has(normalizeSuffix(extension))) throw new Error(`documentPreviews: "${definition.id}" declares binary suffix "${extension}" outside its extensions`);
|
|
1207
1257
|
this.registered.set(definition.id, definition);
|
|
1208
1258
|
this.publish();
|
|
1209
1259
|
let active = true;
|
|
@@ -1228,6 +1278,90 @@ window.__ModuleLoader__.load({
|
|
|
1228
1278
|
}
|
|
1229
1279
|
};
|
|
1230
1280
|
//#endregion
|
|
1281
|
+
//#region lib/types/client/document/unviewable.js
|
|
1282
|
+
/**
|
|
1283
|
+
* Known binary container suffixes with no registered renderer. The preview
|
|
1284
|
+
* owner shows these the unsupported empty state instead of the plain-text
|
|
1285
|
+
* fallback; any renderer registration for a suffix takes precedence because
|
|
1286
|
+
* the owner consults this list only when no implementation matches. A suffix
|
|
1287
|
+
* belongs here only when its bytes are never readable text — an uncertain
|
|
1288
|
+
* suffix stays out and keeps the plain-text fallback.
|
|
1289
|
+
*/
|
|
1290
|
+
const UNVIEWABLE_BINARY_EXTENSIONS = [
|
|
1291
|
+
"mp4",
|
|
1292
|
+
"mov",
|
|
1293
|
+
"avi",
|
|
1294
|
+
"mkv",
|
|
1295
|
+
"webm",
|
|
1296
|
+
"flv",
|
|
1297
|
+
"wmv",
|
|
1298
|
+
"m4v",
|
|
1299
|
+
"mp3",
|
|
1300
|
+
"wav",
|
|
1301
|
+
"flac",
|
|
1302
|
+
"ogg",
|
|
1303
|
+
"m4a",
|
|
1304
|
+
"aac",
|
|
1305
|
+
"wma",
|
|
1306
|
+
"opus",
|
|
1307
|
+
"zip",
|
|
1308
|
+
"gz",
|
|
1309
|
+
"tgz",
|
|
1310
|
+
"bz2",
|
|
1311
|
+
"xz",
|
|
1312
|
+
"zst",
|
|
1313
|
+
"7z",
|
|
1314
|
+
"rar",
|
|
1315
|
+
"tar",
|
|
1316
|
+
"jar",
|
|
1317
|
+
"doc",
|
|
1318
|
+
"docx",
|
|
1319
|
+
"xls",
|
|
1320
|
+
"xlsx",
|
|
1321
|
+
"ppt",
|
|
1322
|
+
"pptx",
|
|
1323
|
+
"odt",
|
|
1324
|
+
"ods",
|
|
1325
|
+
"odp",
|
|
1326
|
+
"pages",
|
|
1327
|
+
"numbers",
|
|
1328
|
+
"exe",
|
|
1329
|
+
"dll",
|
|
1330
|
+
"so",
|
|
1331
|
+
"dylib",
|
|
1332
|
+
"bin",
|
|
1333
|
+
"o",
|
|
1334
|
+
"class",
|
|
1335
|
+
"pyc",
|
|
1336
|
+
"wasm",
|
|
1337
|
+
"ttf",
|
|
1338
|
+
"otf",
|
|
1339
|
+
"woff",
|
|
1340
|
+
"woff2",
|
|
1341
|
+
"eot",
|
|
1342
|
+
"dmg",
|
|
1343
|
+
"iso",
|
|
1344
|
+
"img",
|
|
1345
|
+
"sqlite",
|
|
1346
|
+
"db",
|
|
1347
|
+
"psd",
|
|
1348
|
+
"ai",
|
|
1349
|
+
"sketch",
|
|
1350
|
+
"tiff",
|
|
1351
|
+
"tif",
|
|
1352
|
+
"heic",
|
|
1353
|
+
"heif",
|
|
1354
|
+
"avif"
|
|
1355
|
+
];
|
|
1356
|
+
/**
|
|
1357
|
+
* Whether a filename's suffix is a known binary container that no renderer claims.
|
|
1358
|
+
* @param path - decoded filename or file path.
|
|
1359
|
+
* @returns true when the suffix belongs to the unviewable binary list.
|
|
1360
|
+
*/
|
|
1361
|
+
function unviewableBinaryPath(path) {
|
|
1362
|
+
return matchedSuffixLength(documentFileName(path), UNVIEWABLE_BINARY_EXTENSIONS) > 0;
|
|
1363
|
+
}
|
|
1364
|
+
//#endregion
|
|
1231
1365
|
//#region lib/types/client/text/lines.js
|
|
1232
1366
|
/**
|
|
1233
1367
|
* Split a loaded page into its source lines.
|
|
@@ -1274,7 +1408,7 @@ window.__ModuleLoader__.load({
|
|
|
1274
1408
|
}
|
|
1275
1409
|
//#endregion
|
|
1276
1410
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/TextPreview.module.css.mjs
|
|
1277
|
-
const css$5 = ".dhJKeW_preview{flex-direction:column;flex:auto;height:100%;min-height:0;display:flex}.dhJKeW_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}.dhJKeW_path{white-space:nowrap;flex:auto;justify-content:flex-end;min-width:0;margin-right:12px;font-size:12px;display:flex;overflow:hidden}.dhJKeW_path[data-textpreview-path-clipped]{mask-image:linear-gradient(90deg,#0000,#000 28px)}.dhJKeW_pathText{flex:none;margin-right:auto}.dhJKeW_pathDirectory{color:var(--dsw-alias-label-tertiary)}.dhJKeW_pathName{color:var(--dsw-alias-label-primary)}.dhJKeW_changed{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-bottom:.5px solid var(--dsw-alias-border-l1);flex:none;align-items:center;gap:10px;margin:0;padding:6px 10px;font-size:12px;display:flex}.dhJKeW_body{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-mono,ui-monospace, monospace);white-space:pre;flex:auto;margin:0;padding:0;line-height:1.6;position:relative;overflow:auto}.dhJKeW_body::-webkit-scrollbar-track{margin:2px}.dhJKeW_body:has([data-code-preview]){flex-direction:column;display:flex;overflow:hidden}.dhJKeW_wrap{white-space:pre-wrap;word-break:break-word}.dhJKeW_textDocument{box-sizing:border-box;min-width:100%;min-height:100%;padding:8px}.dhJKeW_page{font:var(--dsw-font-markdown-code-block);white-space:inherit;margin:0}.dhJKeW_line{padding:0 10px}.dhJKeW_lineTarget{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_empty{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-family);text-align:center;white-space:normal;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:0 24px;line-height:1.6;display:flex}.dhJKeW_empty:after{content:\"\";flex:0 12%}.dhJKeW_emptyIcon{opacity:.6;filter:grayscale();flex:none}.dhJKeW_emptyLine{margin:0}.dhJKeW_retry{height:32px;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:16px;flex:none;align-items:center;gap:6px;padding:0 14px 0 12px;font-family:inherit;display:inline-flex}.dhJKeW_retry:hover{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_titleIcon{flex:none}.dhJKeW_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);white-space:normal;align-items:center;gap:10px;margin:0;padding:6px 10px;line-height:1.6;display:flex}.dhJKeW_status{flex-direction:column;align-items:
|
|
1411
|
+
const css$5 = ".dhJKeW_preview{flex-direction:column;flex:auto;height:100%;min-height:0;display:flex}.dhJKeW_header{box-sizing:border-box;border-bottom:.5px solid var(--dsw-alias-border-l3);flex:none;align-items:center;gap:4px;height:38px;padding:0 6px 0 16px;display:flex}.dhJKeW_path{white-space:nowrap;flex:auto;justify-content:flex-end;min-width:0;margin-right:12px;font-size:12px;display:flex;overflow:hidden}.dhJKeW_path[data-textpreview-path-clipped]{mask-image:linear-gradient(90deg,#0000,#000 28px)}.dhJKeW_pathText{flex:none;margin-right:auto}.dhJKeW_pathDirectory{color:var(--dsw-alias-label-tertiary)}.dhJKeW_pathName{color:var(--dsw-alias-label-primary)}.dhJKeW_changed{color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-layer-2);border-bottom:.5px solid var(--dsw-alias-border-l1);flex:none;align-items:center;gap:10px;margin:0;padding:6px 10px;font-size:12px;display:flex}.dhJKeW_body{--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);min-height:0;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-mono,ui-monospace, monospace);white-space:pre;flex:auto;margin:0;padding:0;line-height:1.6;position:relative;overflow:auto}.dhJKeW_body::-webkit-scrollbar-track{margin:2px}.dhJKeW_body:has([data-code-preview]){flex-direction:column;display:flex;overflow:hidden}.dhJKeW_wrap{white-space:pre-wrap;word-break:break-word}.dhJKeW_textDocument{box-sizing:border-box;min-width:100%;min-height:100%;padding:8px}.dhJKeW_page{font:var(--dsw-font-markdown-code-block);white-space:inherit;margin:0}.dhJKeW_line{padding:0 10px}.dhJKeW_lineTarget{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_empty{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font-family);text-align:center;white-space:normal;flex-direction:column;justify-content:center;align-items:center;gap:16px;padding:0 24px;line-height:1.6;display:flex}.dhJKeW_empty:after{content:\"\";flex:0 12%}.dhJKeW_emptyIcon{opacity:.6;filter:grayscale();flex:none}.dhJKeW_emptyLine{margin:0}.dhJKeW_retry{height:32px;color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;background:0 0;border-radius:16px;flex:none;align-items:center;gap:6px;padding:0 14px 0 12px;font-family:inherit;display:inline-flex}.dhJKeW_retry:hover{background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_titleIcon{flex:none}.dhJKeW_statusLine{color:var(--dsw-alias-label-secondary);font-size:var(--dsh-content-font-size-secondary,13px);white-space:normal;align-items:center;gap:10px;margin:0;padding:6px 10px;line-height:1.6;display:flex}.dhJKeW_status{box-sizing:border-box;flex-direction:column;justify-content:center;align-items:center;gap:8px;height:100%;padding:12px 10px;display:flex}.dhJKeW_bodyLoading{box-sizing:border-box;justify-content:center;width:100%;height:100%}.dhJKeW_action{color:var(--dsw-alias-label-primary);font-size:var(--dsh-content-font-size-secondary,13px);font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;padding:4px 10px}.dhJKeW_action:hover{background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more{color:var(--dsw-alias-label-secondary);font-size:12px;font-family:var(--dsw-font,inherit);white-space:normal;background:var(--dsw-alias-bg-layer-2);border:.5px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:6px;margin:8px 10px;padding:4px 10px;display:block}.dhJKeW_more:hover:not(:disabled){color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-layer-3)}.dhJKeW_more:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}.dhJKeW_tool{width:28px;height:28px;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;border-radius:28px;flex:none;justify-content:center;align-items:center;padding:6px;line-height:1;display:inline-flex}.dhJKeW_tool svg{width:15px;height:15px}.dhJKeW_tool:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}.dhJKeW_viewerTool{white-space:nowrap;text-overflow:ellipsis;width:auto;max-width:160px;padding:0 6px;font-size:12px;overflow:hidden}";
|
|
1278
1412
|
const tagId$5 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/TextPreview.module.css";
|
|
1279
1413
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
|
|
1280
1414
|
const tag = document.createElement("style");
|
|
@@ -1286,6 +1420,7 @@ window.__ModuleLoader__.load({
|
|
|
1286
1420
|
var TextPreview_module_css_default = {
|
|
1287
1421
|
"action": "dhJKeW_action",
|
|
1288
1422
|
"body": "dhJKeW_body",
|
|
1423
|
+
"bodyLoading": "dhJKeW_bodyLoading",
|
|
1289
1424
|
"changed": "dhJKeW_changed",
|
|
1290
1425
|
"empty": "dhJKeW_empty",
|
|
1291
1426
|
"emptyIcon": "dhJKeW_emptyIcon",
|
|
@@ -1401,6 +1536,27 @@ window.__ModuleLoader__.load({
|
|
|
1401
1536
|
shown
|
|
1402
1537
|
]);
|
|
1403
1538
|
}
|
|
1539
|
+
/** The header's path: directories greyed, the final segment in full ink, faded when clipped. */
|
|
1540
|
+
function HeaderPath({ pathRef, pathTextRef, path }) {
|
|
1541
|
+
const { directory, name } = pathPartsOf(path);
|
|
1542
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
1543
|
+
ref: pathRef,
|
|
1544
|
+
className: TextPreview_module_css_default.path,
|
|
1545
|
+
title: path,
|
|
1546
|
+
"data-textpreview-path": true,
|
|
1547
|
+
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
1548
|
+
ref: pathTextRef,
|
|
1549
|
+
className: TextPreview_module_css_default.pathText,
|
|
1550
|
+
children: [directory !== "" && (0, react_jsx_runtime.jsx)("span", {
|
|
1551
|
+
className: TextPreview_module_css_default.pathDirectory,
|
|
1552
|
+
children: directory
|
|
1553
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1554
|
+
className: TextPreview_module_css_default.pathName,
|
|
1555
|
+
children: name
|
|
1556
|
+
})]
|
|
1557
|
+
})
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1404
1560
|
/**
|
|
1405
1561
|
* The text type's body, registered under `sidebar.right.pane.tab` as `text`.
|
|
1406
1562
|
* @param props - composed slot props.
|
|
@@ -1414,11 +1570,18 @@ window.__ModuleLoader__.load({
|
|
|
1414
1570
|
const file = (0, react.useMemo)(() => hostFileOf(tab.contentId), [tab.contentId]);
|
|
1415
1571
|
const state = useStore((s) => s.byTab[tab.id]);
|
|
1416
1572
|
const definitions = useDocumentPreviews((value) => value);
|
|
1573
|
+
const unviewable = (0, react.useMemo)(() => unviewableBinaryPath(file.path), [file.path]);
|
|
1417
1574
|
const candidates = (0, react.useMemo)(() => {
|
|
1418
1575
|
const matched = matchingDocumentPreviews(definitions, file.path);
|
|
1576
|
+
if (matched.length > 0 && binaryDocumentPath(definitions, file.path)) return matched;
|
|
1577
|
+
if (matched.length === 0 && unviewable) return matched;
|
|
1419
1578
|
const fallback = definitions.find((definition) => definition.id === PLAIN_BODY_ID);
|
|
1420
1579
|
return fallback === void 0 ? matched : [...matched, fallback];
|
|
1421
|
-
}, [
|
|
1580
|
+
}, [
|
|
1581
|
+
definitions,
|
|
1582
|
+
file.path,
|
|
1583
|
+
unviewable
|
|
1584
|
+
]);
|
|
1422
1585
|
const selected = candidates.find((candidate) => candidate.id === state?.rendererId) ?? candidates[0];
|
|
1423
1586
|
const mode = selected?.loading;
|
|
1424
1587
|
const current = (state?.mode ?? "text-pages") === mode ? state : void 0;
|
|
@@ -1512,6 +1675,37 @@ window.__ModuleLoader__.load({
|
|
|
1512
1675
|
current?.complete,
|
|
1513
1676
|
current?.eof
|
|
1514
1677
|
]);
|
|
1678
|
+
if (selected === void 0 && unviewable) {
|
|
1679
|
+
const { name: unsupportedName } = pathPartsOf(displayPath);
|
|
1680
|
+
return (0, react_jsx_runtime.jsxs)("div", {
|
|
1681
|
+
className: TextPreview_module_css_default.preview,
|
|
1682
|
+
"data-textpreview-state": "unsupported",
|
|
1683
|
+
"data-textpreview-url": tab.contentId,
|
|
1684
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
1685
|
+
className: TextPreview_module_css_default.header,
|
|
1686
|
+
children: (0, react_jsx_runtime.jsx)(HeaderPath, {
|
|
1687
|
+
pathRef,
|
|
1688
|
+
pathTextRef,
|
|
1689
|
+
path: displayPath
|
|
1690
|
+
})
|
|
1691
|
+
}), (0, react_jsx_runtime.jsx)("div", {
|
|
1692
|
+
className: TextPreview_module_css_default.body,
|
|
1693
|
+
"data-textpreview-body": true,
|
|
1694
|
+
children: (0, react_jsx_runtime.jsxs)("div", {
|
|
1695
|
+
className: TextPreview_module_css_default.empty,
|
|
1696
|
+
"data-textpreview-unsupported": true,
|
|
1697
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.FileTypeIcon, {
|
|
1698
|
+
kind: (0, _deepseek_ai_dsh_client_ui_primitives.classifyFileType)(unsupportedName),
|
|
1699
|
+
size: 36,
|
|
1700
|
+
className: TextPreview_module_css_default.emptyIcon
|
|
1701
|
+
}), (0, react_jsx_runtime.jsx)("p", {
|
|
1702
|
+
className: TextPreview_module_css_default.emptyLine,
|
|
1703
|
+
children: t("unsupportedFile")
|
|
1704
|
+
})]
|
|
1705
|
+
})
|
|
1706
|
+
})]
|
|
1707
|
+
});
|
|
1708
|
+
}
|
|
1515
1709
|
if (state === void 0 || selected === void 0) return (0, react_jsx_runtime.jsx)("div", {
|
|
1516
1710
|
className: TextPreview_module_css_default.status,
|
|
1517
1711
|
"data-textpreview-state": "loading",
|
|
@@ -1524,7 +1718,7 @@ window.__ModuleLoader__.load({
|
|
|
1524
1718
|
})
|
|
1525
1719
|
});
|
|
1526
1720
|
const next = loadedThrough + 1;
|
|
1527
|
-
const {
|
|
1721
|
+
const { name } = pathPartsOf(displayPath);
|
|
1528
1722
|
const observedVersion = meta.value?.version;
|
|
1529
1723
|
const changed = current?.version !== void 0 && observedVersion !== void 0 && observedVersion !== current.version && observedVersion !== current.observedVersion;
|
|
1530
1724
|
const loadNext = () => {
|
|
@@ -1566,24 +1760,12 @@ window.__ModuleLoader__.load({
|
|
|
1566
1760
|
(0, react_jsx_runtime.jsxs)("div", {
|
|
1567
1761
|
className: TextPreview_module_css_default.header,
|
|
1568
1762
|
children: [
|
|
1569
|
-
(0, react_jsx_runtime.jsx)(
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
"data-textpreview-path": true,
|
|
1574
|
-
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
1575
|
-
ref: pathTextRef,
|
|
1576
|
-
className: TextPreview_module_css_default.pathText,
|
|
1577
|
-
children: [directory !== "" && (0, react_jsx_runtime.jsx)("span", {
|
|
1578
|
-
className: TextPreview_module_css_default.pathDirectory,
|
|
1579
|
-
children: directory
|
|
1580
|
-
}), (0, react_jsx_runtime.jsx)("span", {
|
|
1581
|
-
className: TextPreview_module_css_default.pathName,
|
|
1582
|
-
children: name
|
|
1583
|
-
})]
|
|
1584
|
-
})
|
|
1763
|
+
(0, react_jsx_runtime.jsx)(HeaderPath, {
|
|
1764
|
+
pathRef,
|
|
1765
|
+
pathTextRef,
|
|
1766
|
+
path: displayPath
|
|
1585
1767
|
}),
|
|
1586
|
-
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1768
|
+
candidates.length > 1 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
1587
1769
|
open: menuOpen,
|
|
1588
1770
|
anchor: (0, react_jsx_runtime.jsx)("button", {
|
|
1589
1771
|
type: "button",
|
|
@@ -1658,7 +1840,7 @@ window.__ModuleLoader__.load({
|
|
|
1658
1840
|
},
|
|
1659
1841
|
children: [
|
|
1660
1842
|
!hasContent && current?.failure === void 0 && (0, react_jsx_runtime.jsx)(LoadingIndicator, {
|
|
1661
|
-
className: TextPreview_module_css_default.statusLine,
|
|
1843
|
+
className: clsx(TextPreview_module_css_default.statusLine, TextPreview_module_css_default.bodyLoading),
|
|
1662
1844
|
label: t("loading")
|
|
1663
1845
|
}),
|
|
1664
1846
|
content !== void 0 && renderSlot("sidebar.right.tab.document", {
|
|
@@ -2035,7 +2217,7 @@ window.__ModuleLoader__.load({
|
|
|
2035
2217
|
const zh$5 = {
|
|
2036
2218
|
loading: "正在读取…",
|
|
2037
2219
|
loadMore: "加载更多",
|
|
2038
|
-
changed: "
|
|
2220
|
+
changed: "文件已更新,当前显示为旧内容",
|
|
2039
2221
|
reloadNow: "重新载入",
|
|
2040
2222
|
reload: "重新读取文件",
|
|
2041
2223
|
"wrap.enable": "自动换行",
|
|
@@ -2043,12 +2225,13 @@ window.__ModuleLoader__.load({
|
|
|
2043
2225
|
"wrap.aria": "自动换行",
|
|
2044
2226
|
openWith: "打开方式",
|
|
2045
2227
|
"viewer.text": "纯文本",
|
|
2046
|
-
resourceUnavailable: "
|
|
2047
|
-
rendererUnavailable: "预览器 {name}
|
|
2048
|
-
|
|
2049
|
-
"error.
|
|
2050
|
-
"error.
|
|
2051
|
-
"error.
|
|
2228
|
+
resourceUnavailable: "文件资源服务不可用",
|
|
2229
|
+
rendererUnavailable: "预览器 {name} 不可用",
|
|
2230
|
+
unsupportedFile: "该格式文件暂时无法预览",
|
|
2231
|
+
"error.notFound": "文件不存在,可能已被移动或删除",
|
|
2232
|
+
"error.tooLarge": "单页内容超过 {limit} 上限,无法读取",
|
|
2233
|
+
"error.notText": "该格式文件暂时无法预览",
|
|
2234
|
+
"error.notRegularFile": "该路径不是普通文件,没有可显示的内容",
|
|
2052
2235
|
"error.unavailable": "读取失败:{message}",
|
|
2053
2236
|
retry: "重试"
|
|
2054
2237
|
};
|
|
@@ -2066,9 +2249,10 @@ window.__ModuleLoader__.load({
|
|
|
2066
2249
|
"viewer.text": "Plain text",
|
|
2067
2250
|
resourceUnavailable: "The file resource service is unavailable.",
|
|
2068
2251
|
rendererUnavailable: "The {name} preview is unavailable.",
|
|
2252
|
+
unsupportedFile: "Preview is not available for this file type yet.",
|
|
2069
2253
|
"error.notFound": "File not found. It may have been moved or deleted.",
|
|
2070
2254
|
"error.tooLarge": "This page exceeds the {limit} limit and cannot be read.",
|
|
2071
|
-
"error.notText": "
|
|
2255
|
+
"error.notText": "Preview is not available for this file type yet.",
|
|
2072
2256
|
"error.notRegularFile": "Not a regular file, nothing to display.",
|
|
2073
2257
|
"error.unavailable": "Read failed: {message}",
|
|
2074
2258
|
retry: "Retry"
|
|
@@ -2331,7 +2515,7 @@ document.open();document.write(html);document.close();
|
|
|
2331
2515
|
}
|
|
2332
2516
|
//#endregion
|
|
2333
2517
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/html/HtmlBody.module.css.mjs
|
|
2334
|
-
const css$3 = ".HyIruq_frame{background:var(--dsw-alias-bg-base);border:none;width:100%;height:100%;min-height:240px;display:block}.HyIruq_status{color:var(--dsw-alias-label-secondary);white-space:normal;margin:0;padding:10px}";
|
|
2518
|
+
const css$3 = ".HyIruq_frame{background:var(--dsw-alias-bg-base);border:none;width:100%;height:100%;min-height:240px;display:block}.HyIruq_status{color:var(--dsw-alias-label-secondary);white-space:normal;margin:0;padding:10px}.HyIruq_opening{box-sizing:border-box;justify-content:center;width:100%;min-height:100%}";
|
|
2335
2519
|
const tagId$3 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/HtmlBody.module.css";
|
|
2336
2520
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
|
|
2337
2521
|
const tag = document.createElement("style");
|
|
@@ -2342,6 +2526,7 @@ document.open();document.write(html);document.close();
|
|
|
2342
2526
|
}
|
|
2343
2527
|
var HtmlBody_module_css_default = {
|
|
2344
2528
|
"frame": "HyIruq_frame",
|
|
2529
|
+
"opening": "HyIruq_opening",
|
|
2345
2530
|
"status": "HyIruq_status"
|
|
2346
2531
|
};
|
|
2347
2532
|
//#endregion
|
|
@@ -2378,7 +2563,7 @@ document.open();document.write(html);document.close();
|
|
|
2378
2563
|
};
|
|
2379
2564
|
}, [data, readRelative]);
|
|
2380
2565
|
if (frame?.data !== data || frame.readRelative !== readRelative) return (0, react_jsx_runtime.jsx)(LoadingIndicator, {
|
|
2381
|
-
className: HtmlBody_module_css_default.status,
|
|
2566
|
+
className: clsx(HtmlBody_module_css_default.status, HtmlBody_module_css_default.opening),
|
|
2382
2567
|
label: t("loading")
|
|
2383
2568
|
});
|
|
2384
2569
|
if (frame.url === void 0) return (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -2419,14 +2604,14 @@ document.open();document.write(html);document.close();
|
|
|
2419
2604
|
const zh$3 = {
|
|
2420
2605
|
title: "HTML",
|
|
2421
2606
|
frame: "HTML 文档预览",
|
|
2422
|
-
loading: "
|
|
2423
|
-
failed: "无法预览这份 HTML
|
|
2607
|
+
loading: "正在读取…",
|
|
2608
|
+
failed: "无法预览这份 HTML 文档"
|
|
2424
2609
|
};
|
|
2425
2610
|
/** English dictionary with the same keys as the Chinese dictionary. */
|
|
2426
2611
|
const en$3 = {
|
|
2427
2612
|
title: "HTML",
|
|
2428
2613
|
frame: "HTML document preview",
|
|
2429
|
-
loading: "
|
|
2614
|
+
loading: "Reading…",
|
|
2430
2615
|
failed: "This HTML document could not be previewed."
|
|
2431
2616
|
};
|
|
2432
2617
|
//#endregion
|
|
@@ -2471,7 +2656,7 @@ document.open();document.write(html);document.close();
|
|
|
2471
2656
|
}
|
|
2472
2657
|
//#endregion
|
|
2473
2658
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/image/ImageBody.module.css.mjs
|
|
2474
|
-
const css$2 = ".JFRUHG_frame{box-sizing:border-box;width:
|
|
2659
|
+
const css$2 = ".JFRUHG_frame{box-sizing:border-box;width:100%;height:max-content;min-height:100%;font-family:var(--dsw-font,sans-serif);white-space:normal;padding:12px;display:flex}.JFRUHG_image{user-select:none;border-radius:8px;flex:none;width:auto;max-width:100%;height:auto;max-height:none;margin:auto;display:block}.JFRUHG_image[hidden]{display:none}.JFRUHG_status{box-sizing:border-box;width:100%;min-height:100%;color:var(--dsw-alias-label-secondary);white-space:normal;justify-content:center;align-items:center;margin:0;padding:10px;font-size:13px;line-height:1.5;display:flex}";
|
|
2475
2660
|
const tagId$2 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/ImageBody.module.css";
|
|
2476
2661
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
|
|
2477
2662
|
const tag = document.createElement("style");
|
|
@@ -2487,7 +2672,7 @@ document.open();document.write(html);document.close();
|
|
|
2487
2672
|
};
|
|
2488
2673
|
//#endregion
|
|
2489
2674
|
//#region lib/types/client/image/ImageBody.js
|
|
2490
|
-
/** Complete image bytes rendered
|
|
2675
|
+
/** Complete image bytes rendered rounded within an inset frame, scaled down to the pane's width. */
|
|
2491
2676
|
const IMAGE_MEDIA_TYPES = {
|
|
2492
2677
|
png: "image/png",
|
|
2493
2678
|
jpg: "image/jpeg",
|
|
@@ -2509,9 +2694,11 @@ document.open();document.write(html);document.close();
|
|
|
2509
2694
|
return IMAGE_MEDIA_TYPES[name.slice(name.lastIndexOf(".") + 1)];
|
|
2510
2695
|
}
|
|
2511
2696
|
/**
|
|
2512
|
-
* Present complete image bytes
|
|
2697
|
+
* Present complete image bytes fitted to the pane's width.
|
|
2513
2698
|
* @param props - document bytes, resource identity, and locale.
|
|
2514
|
-
* @returns
|
|
2699
|
+
* @returns a rounded image scaled down to the pane's width at its aspect
|
|
2700
|
+
* ratio — a smaller image centres at its intrinsic size — whose containing
|
|
2701
|
+
* document body provides vertical scrolling.
|
|
2515
2702
|
*/
|
|
2516
2703
|
function ImageBody({ content, resourceAddress, t }) {
|
|
2517
2704
|
const path = (0, react.useMemo)(() => hostFileOf(resourceAddress).path, [resourceAddress]);
|
|
@@ -2601,15 +2788,15 @@ document.open();document.write(html);document.close();
|
|
|
2601
2788
|
const zh$2 = {
|
|
2602
2789
|
title: "图片",
|
|
2603
2790
|
preview: "图片预览:{name}",
|
|
2604
|
-
loading: "
|
|
2605
|
-
failed: "
|
|
2606
|
-
unsupported: "
|
|
2791
|
+
loading: "正在读取…",
|
|
2792
|
+
failed: "无法显示这张图片",
|
|
2793
|
+
unsupported: "图片预览需要完整文件内容"
|
|
2607
2794
|
};
|
|
2608
2795
|
/** English dictionary with the same keys as the Chinese dictionary. */
|
|
2609
2796
|
const en$2 = {
|
|
2610
2797
|
title: "Image",
|
|
2611
2798
|
preview: "Image preview: {name}",
|
|
2612
|
-
loading: "
|
|
2799
|
+
loading: "Reading…",
|
|
2613
2800
|
failed: "This image could not be displayed.",
|
|
2614
2801
|
unsupported: "Image preview requires the complete file contents."
|
|
2615
2802
|
};
|
|
@@ -2628,6 +2815,16 @@ document.open();document.write(html);document.close();
|
|
|
2628
2815
|
"ico",
|
|
2629
2816
|
"svg"
|
|
2630
2817
|
];
|
|
2818
|
+
/** Bitmap suffixes whose bytes are unreadable as text; SVG stays out because its XML source is worth reading. */
|
|
2819
|
+
const BINARY_IMAGE_EXTENSIONS = [
|
|
2820
|
+
"png",
|
|
2821
|
+
"jpg",
|
|
2822
|
+
"jpeg",
|
|
2823
|
+
"gif",
|
|
2824
|
+
"webp",
|
|
2825
|
+
"bmp",
|
|
2826
|
+
"ico"
|
|
2827
|
+
];
|
|
2631
2828
|
/**
|
|
2632
2829
|
* Describe the builtin image renderer independently from its keyed body slot.
|
|
2633
2830
|
* @param title - locale-owned implementation name.
|
|
@@ -2637,6 +2834,7 @@ document.open();document.write(html);document.close();
|
|
|
2637
2834
|
return {
|
|
2638
2835
|
id: IMAGE_BODY_ID,
|
|
2639
2836
|
extensions: IMAGE_EXTENSIONS,
|
|
2837
|
+
binaryExtensions: BINARY_IMAGE_EXTENSIONS,
|
|
2640
2838
|
priority: "builtin",
|
|
2641
2839
|
title,
|
|
2642
2840
|
loading: "bytes-complete",
|
|
@@ -26473,7 +26671,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26473
26671
|
}
|
|
26474
26672
|
//#endregion
|
|
26475
26673
|
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-sidebar-documentpreview/src/client/pdf/PdfBody.module.css.mjs
|
|
26476
|
-
const css$1 = ".E0GbtG_body{box-sizing:border-box;width:100%;min-height:0;font-family:var(--dsw-font,sans-serif);white-space:normal;flex-direction:column;
|
|
26674
|
+
const css$1 = ".E0GbtG_body{box-sizing:border-box;width:100%;min-height:0;font-family:var(--dsw-font,sans-serif);white-space:normal;flex-direction:column;display:flex;overflow-x:hidden}.E0GbtG_page{box-sizing:border-box;width:100%;min-width:0;overflow-x:hidden}.E0GbtG_placeholder{aspect-ratio:3/4;background:var(--dsw-alias-bg-skeleton);width:100%}.E0GbtG_canvas{width:min(var(--pdf-page-width), 100%);max-width:100%;height:auto;margin-inline:auto;display:block}.E0GbtG_canvas[hidden]{display:none}.E0GbtG_status{color:var(--dsw-alias-label-secondary);align-items:center;gap:8px;padding:10px;font-size:13px;line-height:1.5;display:flex}.E0GbtG_opening{box-sizing:border-box;justify-content:center;width:100%;min-height:100%}";
|
|
26477
26675
|
const tagId$1 = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/PdfBody.module.css";
|
|
26478
26676
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
26479
26677
|
const tag = document.createElement("style");
|
|
@@ -26485,6 +26683,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26485
26683
|
var PdfBody_module_css_default = {
|
|
26486
26684
|
"body": "E0GbtG_body",
|
|
26487
26685
|
"canvas": "E0GbtG_canvas",
|
|
26686
|
+
"opening": "E0GbtG_opening",
|
|
26488
26687
|
"page": "E0GbtG_page",
|
|
26489
26688
|
"placeholder": "E0GbtG_placeholder",
|
|
26490
26689
|
"status": "E0GbtG_status"
|
|
@@ -26554,7 +26753,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26554
26753
|
children: t("unsupported")
|
|
26555
26754
|
});
|
|
26556
26755
|
if (load?.data !== data) return (0, react_jsx_runtime.jsx)(LoadingIndicator, {
|
|
26557
|
-
className: PdfBody_module_css_default.status,
|
|
26756
|
+
className: clsx(PdfBody_module_css_default.status, PdfBody_module_css_default.opening),
|
|
26558
26757
|
label: t("loading")
|
|
26559
26758
|
});
|
|
26560
26759
|
if (load.kind === "failed") return (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -26634,13 +26833,11 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26634
26833
|
className: PdfBody_module_css_default.page,
|
|
26635
26834
|
"data-pdf-page": page,
|
|
26636
26835
|
children: [
|
|
26637
|
-
failure === void 0 && state !== "ready" && (0, react_jsx_runtime.jsx)("div", {
|
|
26836
|
+
failure === void 0 && state !== "ready" && (requested ? (0, react_jsx_runtime.jsx)("div", {
|
|
26638
26837
|
className: PdfBody_module_css_default.placeholder,
|
|
26639
|
-
|
|
26640
|
-
|
|
26641
|
-
|
|
26642
|
-
})
|
|
26643
|
-
}),
|
|
26838
|
+
role: "status",
|
|
26839
|
+
"aria-label": t("rendering")
|
|
26840
|
+
}) : (0, react_jsx_runtime.jsx)("div", { className: PdfBody_module_css_default.placeholder })),
|
|
26644
26841
|
failure !== void 0 && (0, react_jsx_runtime.jsxs)("div", {
|
|
26645
26842
|
className: PdfBody_module_css_default.status,
|
|
26646
26843
|
role: "alert",
|
|
@@ -26673,19 +26870,19 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26673
26870
|
const zh$1 = {
|
|
26674
26871
|
title: "PDF",
|
|
26675
26872
|
pageImage: "PDF 第 {page} 页",
|
|
26676
|
-
loading: "
|
|
26873
|
+
loading: "正在读取…",
|
|
26677
26874
|
rendering: "正在绘制页面…",
|
|
26678
26875
|
failed: "无法显示 PDF:{message}",
|
|
26679
|
-
password: "此 PDF
|
|
26680
|
-
workerFailed: "PDF
|
|
26681
|
-
unsupported: "PDF
|
|
26876
|
+
password: "此 PDF 需要密码,暂不支持预览",
|
|
26877
|
+
workerFailed: "PDF 渲染进程无法继续,请重试",
|
|
26878
|
+
unsupported: "PDF 预览需要完整文件内容",
|
|
26682
26879
|
retry: "重试"
|
|
26683
26880
|
};
|
|
26684
26881
|
/** English PDF-renderer dictionary. */
|
|
26685
26882
|
const en$1 = {
|
|
26686
26883
|
title: "PDF",
|
|
26687
26884
|
pageImage: "PDF page {page}",
|
|
26688
|
-
loading: "
|
|
26885
|
+
loading: "Reading…",
|
|
26689
26886
|
rendering: "Rendering page…",
|
|
26690
26887
|
failed: "Cannot display PDF: {message}",
|
|
26691
26888
|
password: "This PDF requires a password; password-protected previews are not supported.",
|
|
@@ -26706,6 +26903,7 @@ fn fs_main(in : VertexOutput) -> @location(0) vec4<f32> {
|
|
|
26706
26903
|
return {
|
|
26707
26904
|
id: PDF_BODY_ID,
|
|
26708
26905
|
extensions: ["pdf"],
|
|
26906
|
+
binaryExtensions: ["pdf"],
|
|
26709
26907
|
priority: "builtin",
|
|
26710
26908
|
title,
|
|
26711
26909
|
loading: "bytes-complete",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/** Shared indeterminate loading feedback for document reads and rendering. */
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* @param props - localized status label, carried as the accessible name with
|
|
5
|
+
* no visible text, and optional placement style.
|
|
6
|
+
* @returns an animated, accessible loading status.
|
|
7
|
+
*/
|
|
4
8
|
export declare function LoadingIndicator({ label, className }: {
|
|
5
9
|
label: string;
|
|
6
10
|
className?: string | undefined;
|
|
@@ -6,6 +6,12 @@ export interface DocumentPreviewDefinition {
|
|
|
6
6
|
readonly id: string;
|
|
7
7
|
/** File suffixes without a leading dot; compound suffixes such as tar.gz are accepted. */
|
|
8
8
|
readonly extensions: readonly string[];
|
|
9
|
+
/**
|
|
10
|
+
* Suffixes among `extensions` whose bytes are not readable text; a file
|
|
11
|
+
* matching one loses the plain-text fallback among its viewer choices.
|
|
12
|
+
* Every entry must appear in `extensions`; `register` rejects strays.
|
|
13
|
+
*/
|
|
14
|
+
readonly binaryExtensions?: readonly string[];
|
|
9
15
|
/** External implementations win over product implementations; defaults to extension. */
|
|
10
16
|
readonly priority?: 'builtin' | 'extension';
|
|
11
17
|
/** Localized implementation label, evaluated when the toolbar renders. @returns the visible name. */
|
|
@@ -22,6 +28,13 @@ export interface DocumentPreviewDefinition {
|
|
|
22
28
|
* @returns matching implementations, external band first, then longest suffix.
|
|
23
29
|
*/
|
|
24
30
|
export declare function matchingDocumentPreviews(definitions: readonly DocumentPreviewDefinition[], path: string): readonly DocumentPreviewDefinition[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether any registered implementation declares the filename's suffix binary.
|
|
33
|
+
* @param definitions - registered implementations.
|
|
34
|
+
* @param path - decoded filename or file path.
|
|
35
|
+
* @returns true when a declared binary suffix matches the filename.
|
|
36
|
+
*/
|
|
37
|
+
export declare function binaryDocumentPath(definitions: readonly DocumentPreviewDefinition[], path: string): boolean;
|
|
25
38
|
/** Observable registry of all live implementations, including lower-priority alternatives. */
|
|
26
39
|
export declare class DocumentPreviewRegistry {
|
|
27
40
|
private readonly registered;
|
|
@@ -40,8 +53,10 @@ export declare class DocumentPreviewRegistry {
|
|
|
40
53
|
readonly subscribe: (listener: () => void) => (() => void);
|
|
41
54
|
/**
|
|
42
55
|
* Register metadata separately from the matching keyed slot component.
|
|
43
|
-
* @param definition - unique implementation and recognized suffixes
|
|
44
|
-
*
|
|
56
|
+
* @param definition - unique implementation and recognized suffixes; every
|
|
57
|
+
* `binaryExtensions` entry must appear in `extensions`.
|
|
58
|
+
* @returns an idempotent disposer; duplicate live implementation names and
|
|
59
|
+
* binary suffixes outside `extensions` throw.
|
|
45
60
|
*/
|
|
46
61
|
register(definition: DocumentPreviewDefinition): () => void;
|
|
47
62
|
/**
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filename suffix matching shared by the preview registry and the owner's
|
|
3
|
+
* unviewable list, so every consumer normalizes paths and suffixes alike.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Normalize one declared suffix for comparison.
|
|
7
|
+
* @param extension - declared file suffix, with or without a leading dot.
|
|
8
|
+
* @returns the suffix lowercased with any leading dot dropped.
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeSuffix(extension: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The filename a path's suffixes are matched against.
|
|
13
|
+
* @param path - decoded filename or file path; `\` is accepted as a separator.
|
|
14
|
+
* @returns the lowercased final path segment.
|
|
15
|
+
*/
|
|
16
|
+
export declare function documentFileName(path: string): string;
|
|
17
|
+
/**
|
|
18
|
+
* The longest declared suffix ending the filename.
|
|
19
|
+
* @param name - lowercased filename from {@link documentFileName}.
|
|
20
|
+
* @param extensions - declared suffixes; compound suffixes such as `tar.gz` are accepted.
|
|
21
|
+
* @returns the matched suffix's normalized length, or 0 when none matches.
|
|
22
|
+
*/
|
|
23
|
+
export declare function matchedSuffixLength(name: string, extensions: readonly string[]): number;
|
|
24
|
+
//# sourceMappingURL=suffix.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a filename's suffix is a known binary container that no renderer claims.
|
|
3
|
+
* @param path - decoded filename or file path.
|
|
4
|
+
* @returns true when the suffix belongs to the unviewable binary list.
|
|
5
|
+
*/
|
|
6
|
+
export declare function unviewableBinaryPath(path: string): boolean;
|
|
7
|
+
//# sourceMappingURL=unviewable.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Complete image bytes rendered
|
|
1
|
+
/** Complete image bytes rendered rounded within an inset frame, scaled down to the pane's width. */
|
|
2
2
|
import { type ReactNode } from 'react';
|
|
3
3
|
import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
4
|
import type { DocumentPreviewProps } from '../document/contract.ts';
|
|
@@ -22,9 +22,11 @@ export type ImageBodyProps = DocumentPreviewProps & PropsLocale<'sidebarImage'>;
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function imageMediaType(path: string): ImageMediaType | undefined;
|
|
24
24
|
/**
|
|
25
|
-
* Present complete image bytes
|
|
25
|
+
* Present complete image bytes fitted to the pane's width.
|
|
26
26
|
* @param props - document bytes, resource identity, and locale.
|
|
27
|
-
* @returns
|
|
27
|
+
* @returns a rounded image scaled down to the pane's width at its aspect
|
|
28
|
+
* ratio — a smaller image centres at its intrinsic size — whose containing
|
|
29
|
+
* document body provides vertical scrolling.
|
|
28
30
|
*/
|
|
29
31
|
export declare function ImageBody({ content, resourceAddress, t }: ImageBodyProps): ReactNode;
|
|
30
32
|
export {};
|
|
@@ -5,6 +5,8 @@ import type { DocumentPreviewDefinition } from '../document/registry.ts';
|
|
|
5
5
|
export declare const IMAGE_BODY_ID = "@deepseek-ai/dsh-client-ui-sidebar-documentpreview/image";
|
|
6
6
|
/** File suffixes rendered by the builtin image body. */
|
|
7
7
|
export declare const IMAGE_EXTENSIONS: readonly ["png", "jpg", "jpeg", "gif", "webp", "bmp", "ico", "svg"];
|
|
8
|
+
/** Bitmap suffixes whose bytes are unreadable as text; SVG stays out because its XML source is worth reading. */
|
|
9
|
+
export declare const BINARY_IMAGE_EXTENSIONS: readonly ["png", "jpg", "jpeg", "gif", "webp", "bmp", "ico"];
|
|
8
10
|
/**
|
|
9
11
|
* Describe the builtin image renderer independently from its keyed body slot.
|
|
10
12
|
* @param title - locale-owned implementation name.
|
|
@@ -19,6 +19,7 @@ export declare const zh: {
|
|
|
19
19
|
'viewer.text': string;
|
|
20
20
|
resourceUnavailable: string;
|
|
21
21
|
rendererUnavailable: string;
|
|
22
|
+
unsupportedFile: string;
|
|
22
23
|
'error.notFound': string;
|
|
23
24
|
'error.tooLarge': string;
|
|
24
25
|
'error.notText': string;
|
|
@@ -42,6 +43,7 @@ export declare const en: {
|
|
|
42
43
|
'viewer.text': string;
|
|
43
44
|
resourceUnavailable: string;
|
|
44
45
|
rendererUnavailable: string;
|
|
46
|
+
unsupportedFile: string;
|
|
45
47
|
'error.notFound': string;
|
|
46
48
|
'error.tooLarge': string;
|
|
47
49
|
'error.notText': string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-client-ui-sidebar-documentpreview",
|
|
3
3
|
"description": "Extensible document previews for Sidebar files: Markdown, highlighted code, images, PDF, HTML, and plain text",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6-alpha.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -50,21 +50,21 @@
|
|
|
50
50
|
"react": "^18.2.0",
|
|
51
51
|
"react-dom": "^18.2.0",
|
|
52
52
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
53
|
-
"@deepseek-ai/dsh-api-
|
|
54
|
-
"@deepseek-ai/dsh-api-
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-client-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-client-store": "^0.1.
|
|
59
|
-
"@deepseek-ai/dsh-client-test-runtime": "^0.1.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-
|
|
65
|
-
"@deepseek-ai/dsh-client-ui-slots": "^0.1.
|
|
66
|
-
"@deepseek-ai/dsh-session": "^0.1.
|
|
67
|
-
"@deepseek-ai/dsh-util-workspace-path": "^0.1.
|
|
53
|
+
"@deepseek-ai/dsh-api-gateway": "^0.1.6-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-api-remotes": "^0.1.6-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-api-workspace-files": "^0.1.6-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-client-resources": "^0.1.6-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-client-test-runtime": "^0.1.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-client-ui-dockkit": "^0.1.6-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-client-ui-sidebar-right": "^0.1.6-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-client-ui-session": "^0.1.6-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
|
|
66
|
+
"@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
|
|
67
|
+
"@deepseek-ai/dsh-util-workspace-path": "^0.1.6-alpha.1"
|
|
68
68
|
},
|
|
69
69
|
"files": [
|
|
70
70
|
"lib/index.js",
|