- 新增 src/client/service.ts: BetterSidebarService 接口 + createBetterSidebarService 工厂
- registerTab/registerFileViewer 返回 disposer(HMR-safe,挂到 cordis fiber)
- matchFileViewer 三段匹配: priority desc → detect(内容嗅探) → exts
- openTab 走 descriptor.dedupeKey 统一去重(替代 isSingle/editor-path/diff-id 三分支)
- createTab 回调支持自定义 tab 创建(terminal 的 nextTerminal 递增)
- 新增 src/client/builtins.tsx: 6 内置 tab + 6 内置 viewer 自我注册(吃狗粮)
- tab: editor(hidden) / explorer / git / subagent / terminal / diff(hidden)
- viewer: image / pdf / docx / xlsx / pptx / binary-download
- code/markdown 保持 EditorView 内联兜底(matchFileViewer 返回 undefined 时走 fsRead+CodeMirror)
- 新增 src/client/OrphanedTab.tsx: 未注册 type 的占位组件(插件未加载时降级)
- 改造 src/client/index.tsx: ctx.provide('betterSidebar') + registerBuiltins
- 改造 src/client/Sidebar.tsx: TabContent/buildNewTabOptions/onNewTab 查表替代 switch
- 改造 src/client/EditorView.tsx: 预览路由查 matchFileViewer 替代 if/else 链
- 改造 src/client/state.ts: TabType 联合字面量→string,sanitizeNode 接受所有 string type
- 改造 src/context-types.ts: declare module 'cordis' 加 betterSidebar 字段
- 改造 src/index.ts: re-export Context + 服务类型(consumer import type 触发 augmentation)
- package.json: 加 ./client/service exports 子路径,version 0.4.0
- tests/service.spec.ts: 12 个新测试覆盖 register/dispose/matchFileViewer/dedupe/createTab
- tests/unit.spec.ts: sanitizeState 测试更新(未知 type 不再被拒绝,渲染 OrphanedTab)
46 lines
2.3 KiB
TypeScript
46 lines
2.3 KiB
TypeScript
/**
|
|
* Icons the sidebar needs beyond the primitives set: a terminal glyph (the
|
|
* icon library has none) and the per-tab-type icon mapping shared by the
|
|
* tab strip, the + menu, and the empty-pane cards.
|
|
*/
|
|
import type { ReactNode } from 'react'
|
|
import {
|
|
IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconThinkOutline16,
|
|
} from '@deepseek-ai/dsh-client-ui-primitives'
|
|
import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
import type { TabType } from './state.ts'
|
|
|
|
/**
|
|
* Terminal glyph in the app's outline style (1.5px stroke, currentColor):
|
|
* a rounded frame with a prompt chevron and underscore cursor.
|
|
*/
|
|
export const IconTerminalOutline16 = ({ size = 16, className }: IconProps) => (
|
|
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="1.5" y="2.5" width="13" height="11" rx="2" stroke="currentColor" strokeWidth="1.5" />
|
|
<path d="M4.5 6.25 6.75 8 4.5 9.75" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
<path d="M8.5 10.4h3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
</svg>
|
|
)
|
|
|
|
/** Diff glyph in the app's outline style: a file frame with a plus and a minus row. */
|
|
export const IconDiffOutline16 = ({ size = 16, className }: IconProps) => (
|
|
<svg width={size} height={size} className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<rect x="1.5" y="1.5" width="13" height="13" rx="2.5" stroke="currentColor" strokeWidth="1.5" />
|
|
<path d="M4 5h3M5.5 3.5v3" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
<path d="M9.5 12.5h2.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
|
|
</svg>
|
|
)
|
|
|
|
/** The one icon per tab type (shared by tabs, the + menu, and pane cards). */
|
|
export function tabTypeIcon(type: TabType, size = 16): ReactNode {
|
|
switch (type) {
|
|
case 'explorer': return <IconFolderOpen16 size={size} />
|
|
case 'git': return <IconBranchOutline16 size={size} />
|
|
case 'terminal': return <IconTerminalOutline16 size={size} />
|
|
case 'subagent': return <IconThinkOutline16 size={size} />
|
|
case 'editor': return <IconCodeOutline16 size={size} />
|
|
case 'diff': return <IconDiffOutline16 size={size} />
|
|
default: return null
|
|
}
|
|
}
|