catalog: add dsh-video-preview (video previewer) to the file-previewer recommended plugins (#126)

* catalog: add dsh-video-preview to the file-previewer recommended plugins

Adds the video preview plugin (dsh-video-preview) to the "add preview plugin" modal catalog, with zh/en descriptions. Data integrity is guarded by tests/plugin-list.spec.ts.

* test: make add-plugin-modal window.open count catalog-driven

The test pinned the viewer catalog to a single entry (2 "Open:" buttons).
Adding a second recommended plugin (dsh-video-preview) renders 4 buttons
and broke the hardcoded assertion. Derive the expected count from
builtinViewerPlugins so adding/removing plugins keeps the test honest.
This commit is contained in:
zemul
2026-08-16 22:32:17 +08:00
committed by GitHub
parent fc4031e65f
commit 5c138ca851
3 changed files with 24 additions and 8 deletions
+2
View File
@@ -228,6 +228,7 @@ export const zh = {
pluginOfficeDesc: '为 better-sidebar 编辑器提供 Office 三件套预览(.docx / .xlsx / .pptx),把重型 Office 渲染库拆出主包、按需安装',
pluginSentinelDesc: '条件驱动的 agent 唤醒系统:文件/进程/端口/HTTP/命令/webhook 传感器,条件达成自动唤醒休眠会话;注册「哨兵」Tab 展示服务器全局监控表',
pluginSidebarQaDesc: '基于 better-sidebar 的划选提问tab分页: 对话划选 → 右侧面板提问 → 同工作区独立追问会话(❓追问·主题):快速无思考模型压缩主对话上下文后与引文一起注入,不打断主对话;追问可嵌套、可继续、可归档',
pluginVideoPreviewDesc: '在 better-sidebar 编辑器内联预览视频文件(.mp4/.webm/.mov/.mkv/.avi 等),自带支持 HTTP Range(206)的 /video 宿主路由,可拖动进度条、不受 20MB mediaLimit 限制',
}
/** The en dictionary (key-set-equal to zh, enforced by the type annotation). */
@@ -449,6 +450,7 @@ export const en: Record<keyof typeof zh, string> = {
pluginOfficeDesc: 'Office-suite preview (.docx / .xlsx / .pptx) for the better-sidebar editor, keeping the heavy Office render libraries out of the core bundle',
pluginSentinelDesc: 'Condition-driven agent wakeup: file/process/port/http/command/webhook sensors wake dormant sessions when conditions fire; registers a "Sentinel" tab with the server-wide watch table',
pluginSidebarQaDesc: 'Select-and-ask: Select conversation text → ask in the right-side panel → a dedicated follow-up session (❓追问) in the same workspace; a fast no-thinking model compresses the main context and injects it with the quote, without interrupting the main conversation. Follow-ups nest, continue, and archive',
pluginVideoPreviewDesc: 'Inline video preview (.mp4/.webm/.mov/.mkv/.avi etc.) for the better-sidebar editor, backed by a dedicated /video host route with HTTP Range (206) support — scrubbing works and files are not capped by the 20MB mediaLimit',
}
/**
+7
View File
@@ -20,4 +20,11 @@ export const builtinViewerPlugins: readonly PluginEntry[] = [
description: () => t('pluginOfficeDesc'),
install: 'cd ~/.dsh && dsh plugin --profile web add @huanlin/dsh-plugin-better-sidebar-plugin-office',
},
{
id: 'dsh-video-preview',
name: '视频预览插件',
url: 'https://github.com/zemul/dsh-video-preview',
description: () => t('pluginVideoPreviewDesc'),
install: 'cd ~/.dsh && dsh plugin --profile web add dsh-video-preview',
},
]
+15 -8
View File
@@ -155,7 +155,7 @@ describe('PluginListBody copy click (interactive)', () => {
vi.restoreAllMocks()
})
it('both the name button and the jump button open the repo in a NEW browser tab (window.open, not a link)', () => {
it('the name and jump buttons open each catalog entry in a NEW browser tab (window.open, not a link)', () => {
const store = createSidebarStore()
const service = createBetterSidebarService(store)
const opened: Array<[string, string, string]> = []
@@ -171,14 +171,21 @@ describe('PluginListBody copy click (interactive)', () => {
act(() => {
root.render(createElement(PluginListBody, { service, kind: 'viewer' }))
})
// Two window.open buttons per catalog entry — the name button and the
// jump button. The expected count is catalog-driven so adding or
// removing recommended plugins keeps the test honest instead of
// pinning a hardcoded total.
const jumpButtons = [...container.querySelectorAll('button[aria-label^="Open:"]')]
expect(jumpButtons).toHaveLength(2) // the name + the jump button
act(() => { (jumpButtons[0] as HTMLButtonElement).click() })
act(() => { (jumpButtons[1] as HTMLButtonElement).click() })
expect(opened).toEqual([
[officeEntry.url, '_blank', 'noopener'],
[officeEntry.url, '_blank', 'noopener'],
])
expect(jumpButtons).toHaveLength(builtinViewerPlugins.length * 2)
for (const button of jumpButtons) {
act(() => { (button as HTMLButtonElement).click() })
}
expect(opened).toEqual(
builtinViewerPlugins.flatMap((entry) => [
[entry.url, '_blank', 'noopener'],
[entry.url, '_blank', 'noopener'],
]),
)
act(() => { root.unmount() })
container.remove()
} finally {