feat: 内置 HTML 预览器 + 沙箱浏览器 tab(v0.5.0)
- html viewer:.html/.htm 沙箱 iframe 预览(相对资源经 /sidebar/html 路径编码路由加载,CSP sandbox + 信任围栏 + cwd 边界),预览/编辑切换 - browser tab:+ 菜单多开(browser:<n>),地址栏后退/前进/刷新/在浏览器中打开(15px 图标),URL 与标题按会话持久化(patchTab + nextBrowser) - 安全:iframe 一律不透明源沙箱(无 allow-same-origin/top-navigation、no-referrer、权限策略全禁、禁 srcdoc);地址栏仅 http(s)、拒绝 localhost 等本机地址;界面实时沙箱状态(关闭红色警示)+ 临时解锁/恢复 - 设置:htmlViewerNoSandbox / htmlViewerDefaultUnsafe / browserNoSandbox / browserInterceptLinks 四个声明式二级设置(默认安全) - 外链拦截:聊天/界面 http(s) 外链默认在侧边栏打开(openTab seed.url),Ctrl/Cmd 点击放行 - 嵌入拒绝面板:browser.probe 路由探测 X-Frame-Options/frame-ancestors,被拒站点显示原因 + 在浏览器中打开/仍然加载 - 设置页可滚动;内置 7 tab + 9 viewer;新增设计文档;268 个测试全绿
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
/**
|
||||
* The 6 built-in tab descriptors: the plugin registers its own pages
|
||||
* (explorer / git / terminal / subagent / editor / diff) through the same
|
||||
* {@link BetterSidebarService} external plugins use — eating its own
|
||||
* dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`) and
|
||||
* mints `terminal:<n>` ids through `createTab`.
|
||||
* The 7 built-in tab descriptors: the plugin registers its own pages
|
||||
* (explorer / git / terminal / browser / subagent / editor / diff) through
|
||||
* the same {@link BetterSidebarService} external plugins use — eating its
|
||||
* own dogfood. The terminal descriptor owns its quota (`TERMINAL_LIMIT`)
|
||||
* and mints `terminal:<n>` ids through `createTab`; the browser mints
|
||||
* `browser:<n>` the same way (no quota).
|
||||
*/
|
||||
import { IconBranchOutline16, IconCodeOutline16, IconFolderOpen16, IconThinkOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
|
||||
import type { Context } from '../../context-types.ts'
|
||||
@@ -16,7 +17,8 @@ import { TerminalView } from '../TerminalView.tsx'
|
||||
import { GitView } from '../GitView.tsx'
|
||||
import { DiffTab } from '../DiffTab.tsx'
|
||||
import { SubagentView } from '../SubagentView.tsx'
|
||||
import { IconTerminalOutline16, IconDiffOutline16 } from '../icons.tsx'
|
||||
import { BrowserView } from '../BrowserView.tsx'
|
||||
import { IconTerminalOutline16, IconDiffOutline16, IconGlobeOutline16 } from '../icons.tsx'
|
||||
import type { TabDescriptor } from '../service.ts'
|
||||
|
||||
/** How many UI-owned terminals may be open at once (agent-owned ones are uncapped). */
|
||||
@@ -29,7 +31,7 @@ function uiTerminalCount(state: SidebarState): number {
|
||||
.filter(tab => tab.type === 'terminal' && !isAgentTabId(tab.id)).length
|
||||
}
|
||||
|
||||
/** The 6 built-in tab descriptors. */
|
||||
/** The 7 built-in tab descriptors. */
|
||||
export function builtinTabs(ctx: Context): readonly TabDescriptor[] {
|
||||
return [
|
||||
{
|
||||
@@ -130,6 +132,35 @@ export function builtinTabs(ctx: Context): readonly TabDescriptor[] {
|
||||
<TerminalView scope={scope} tabId={tab.id} store={store} />
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'browser',
|
||||
title: () => t('browser'),
|
||||
icon: (size: number) => <IconGlobeOutline16 size={size} />,
|
||||
order: 50,
|
||||
// Declarative settings: the sandbox escape hatch and the link
|
||||
// takeover render under this tab's row in the Side card settings
|
||||
// page (the sandbox one is warned on).
|
||||
settings: {
|
||||
toggles: [{
|
||||
key: 'browserNoSandbox',
|
||||
title: () => t('settingsBrowserSandboxTitle'),
|
||||
desc: () => t('settingsBrowserSandboxDesc'),
|
||||
}, {
|
||||
key: 'browserInterceptLinks',
|
||||
title: () => t('settingsBrowserLinksTitle'),
|
||||
desc: () => t('settingsBrowserLinksDesc'),
|
||||
}],
|
||||
},
|
||||
createTab: (state) => ({
|
||||
tab: {
|
||||
id: `browser:${state.nextBrowser}`,
|
||||
type: 'browser',
|
||||
title: t('browser'),
|
||||
},
|
||||
patch: { nextBrowser: state.nextBrowser + 1 },
|
||||
}),
|
||||
component: (props) => <BrowserView {...props} />,
|
||||
},
|
||||
{
|
||||
id: 'diff',
|
||||
title: () => t('git'),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* The 8 built-in file viewer descriptors: every preview surface is a
|
||||
* registered viewer (image / pdf / docx / xlsx / pptx / markdown / code /
|
||||
* binary-download), exactly like external plugins register theirs. The
|
||||
* `binary-download` viewer sniffs NUL bytes via `detect` for unknown
|
||||
* The 9 built-in file viewer descriptors: every preview surface is a
|
||||
* registered viewer (image / pdf / docx / xlsx / pptx / markdown / html /
|
||||
* code / binary-download), exactly like external plugins register theirs.
|
||||
* The `binary-download` viewer sniffs NUL bytes via `detect` for unknown
|
||||
* binaries and serves doc/xls/ppt by extension; `code` is the catch-all
|
||||
* (`exts: []`, lowest priority) that claims any file no other viewer did.
|
||||
*
|
||||
@@ -23,12 +23,13 @@ import {
|
||||
IconDocxOutline16,
|
||||
IconXlsxOutline16,
|
||||
IconPptxOutline16,
|
||||
IconHtmlOutline16,
|
||||
} from '../icons.tsx'
|
||||
import type { FileViewerDescriptor } from '../service.ts'
|
||||
import { t } from '../locales.ts'
|
||||
import css from '../sidebar.module.css'
|
||||
|
||||
/** The 8 built-in file viewer descriptors. */
|
||||
/** The 9 built-in file viewer descriptors. */
|
||||
export function builtinViewers(): readonly FileViewerDescriptor[] {
|
||||
return [
|
||||
{
|
||||
@@ -91,6 +92,28 @@ export function builtinViewers(): readonly FileViewerDescriptor[] {
|
||||
fetchStrategy: 'fsRead',
|
||||
component: (props) => <TextEditor {...props} />,
|
||||
},
|
||||
{
|
||||
id: 'html',
|
||||
title: () => t('viewerHtml'),
|
||||
icon: (size: number) => <IconHtmlOutline16 size={size} />,
|
||||
exts: ['html', 'htm'],
|
||||
fetchStrategy: 'fsRead',
|
||||
// Declarative settings: the sandbox escape hatch and the default-unsafe
|
||||
// start state render under this viewer's row in the Side card settings
|
||||
// page (both warned on).
|
||||
settings: {
|
||||
toggles: [{
|
||||
key: 'htmlViewerNoSandbox',
|
||||
title: () => t('settingsHtmlSandboxTitle'),
|
||||
desc: () => t('settingsHtmlSandboxDesc'),
|
||||
}, {
|
||||
key: 'htmlViewerDefaultUnsafe',
|
||||
title: () => t('settingsHtmlDefaultUnsafeTitle'),
|
||||
desc: () => t('settingsHtmlDefaultUnsafeDesc'),
|
||||
}],
|
||||
},
|
||||
component: (props) => <TextEditor {...props} />,
|
||||
},
|
||||
{
|
||||
id: 'code',
|
||||
title: () => t('viewerCode'),
|
||||
|
||||
Reference in New Issue
Block a user