revert: PDF回退原生阅读器(自绘抓手不可行) + feat: 视图选项改名设置并直接内嵌排序+管理列表 + 变更视图(内容自动迁入新拓扑)
This commit is contained in:
@@ -14,15 +14,8 @@ import { fileURLToPath } from 'node:url'
|
||||
const here = dirname(fileURLToPath(import.meta.url))
|
||||
mkdirSync(join(here, 'lib'), { recursive: true })
|
||||
|
||||
// pdf.js 工作线程源码(legacy ESM 构建)注入客户端全局,避免任何服务端依赖
|
||||
const pdfWorkerSrc = readFileSync(
|
||||
join(here, 'node_modules', 'pdfjs-dist', 'legacy', 'build', 'pdf.worker.min.mjs'),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
const clientBanner = {
|
||||
js: "var __WT_PDF_WORKER__ = " + JSON.stringify(pdfWorkerSrc) + ";\n" +
|
||||
"window.__ModuleLoader__.load({ id: 'dsh-worktable', factory: (require) => { var module = { exports: {} }; var exports = module.exports;",
|
||||
js: "window.__ModuleLoader__.load({ id: 'dsh-worktable', factory: (require) => { var module = { exports: {} }; var exports = module.exports;",
|
||||
}
|
||||
const clientFooter = { js: 'return module.exports; } });' }
|
||||
|
||||
|
||||
Generated
-291
@@ -11,7 +11,6 @@
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"markdown-it": "^15.0.0",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -473,283 +472,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-0.1.100.tgz",
|
||||
"integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"workspaces": [
|
||||
"e2e/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-x64": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "0.1.100",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.100"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz",
|
||||
"integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz",
|
||||
"integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"glibc"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"libc": [
|
||||
"musl"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-3.0.0.tgz",
|
||||
@@ -877,19 +599,6 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pdfjs-dist": {
|
||||
"version": "4.10.38",
|
||||
"resolved": "https://registry.npmmirror.com/pdfjs-dist/-/pdfjs-dist-4.10.38.tgz",
|
||||
"integrity": "sha512-/Y3fcFrXEAsMjJXeL9J8+ZG9U01LbuWaYypvDW2ycW1jL269L3js3DVBjDJ0Up9Np1uqDXsDrRihHANhZOlwdQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas": "^0.1.65"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode.js": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
|
||||
|
||||
@@ -53,7 +53,6 @@
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"markdown-it": "^15.0.0",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,6 @@ function WorktableSection(props: any) {
|
||||
const [projects, setProjects] = useState<ProjectsState>(loadProjects)
|
||||
const [metas, setMetas] = useState<Record<string, ProjectMeta>>({})
|
||||
const [registeredIds, setRegisteredIds] = useState<string[]>(() => [...registryStore.ids])
|
||||
const [managing, setManaging] = useState(false)
|
||||
const [addOpen, setAddOpen] = useState(false)
|
||||
const [viewOptionsOpen, setViewOptionsOpen] = useState(false)
|
||||
const [wsPreset, setWsPreset] = useState<string>('2h')
|
||||
@@ -318,6 +317,8 @@ function WorktableSection(props: any) {
|
||||
const [iconPick, setIconPick] = useState<{ kind: 'layout' | 'shortcut' | 'project'; id: string; x: number; y: number } | null>(null)
|
||||
/** 删除二次确认:kind + 目标 id + 显示名 */
|
||||
const [requestDelete, setRequestDelete] = useState<{ kind: 'layout' | 'shortcut' | 'project'; id: string; name: string } | null>(null)
|
||||
/** 变更视图:正在挑选新拓扑的布局 id */
|
||||
const [viewPickFor, setViewPickFor] = useState<string | null>(null)
|
||||
const [float, setFloat] = useState<FloatRect | null>(() =>
|
||||
view.dock === 'float' && view.floatTop != null ? { top: view.floatTop } : null,
|
||||
)
|
||||
@@ -526,7 +527,7 @@ function WorktableSection(props: any) {
|
||||
const ownerProps = {
|
||||
query: view.query.trim(),
|
||||
wide,
|
||||
managing,
|
||||
managing: viewOptionsOpen,
|
||||
order: effectiveOrder,
|
||||
hidden: [...projects.hidden, ...projects.removed],
|
||||
nameOverrides: projects.nameOverrides,
|
||||
@@ -669,6 +670,57 @@ function WorktableSection(props: any) {
|
||||
persistProjects((prev) => ({ ...prev, layouts: prev.layouts.filter((l) => l.id !== id) }))
|
||||
}
|
||||
|
||||
// ── 变更视图:按预设重建布局拓扑,现有窗内容(标签)按序迁入,不丢失 ──
|
||||
const applyLayoutChange = (id: string, presetId: string) => {
|
||||
const layout = projects.layouts.find((l) => l.id === id)
|
||||
if (!layout) return
|
||||
const sources = [...(layout.top ?? []), ...layout.main]
|
||||
.map((pp) => pp.tabs ?? [])
|
||||
.filter((tabs) => tabs.length > 0)
|
||||
const next = buildLayout(presetId, layout.title)
|
||||
next.id = layout.id
|
||||
next.icon = layout.icon
|
||||
const targets = [...(next.left ? [next.left] : []), ...(next.top ?? []), ...next.main]
|
||||
let si = 0
|
||||
for (const pane of targets) {
|
||||
if (si < sources.length) {
|
||||
pane.tabs = sources[si]
|
||||
pane.active = 0
|
||||
pane.content = null
|
||||
si++
|
||||
}
|
||||
}
|
||||
const overflow = sources.slice(si).flat()
|
||||
if (overflow.length > 0 && targets.length > 0) {
|
||||
const last = targets[targets.length - 1]
|
||||
last.tabs = [...(last.tabs ?? []), ...overflow]
|
||||
last.active = 0
|
||||
}
|
||||
persistProjects((prev) => ({
|
||||
...prev,
|
||||
layouts: prev.layouts.map((l) => (l.id === id ? next : l)),
|
||||
}))
|
||||
// 该布局当前打开时:关旧开新,工作区即时变为新视图
|
||||
const wasOpen = splitStore.active && splitStore.spec?.id === id
|
||||
if (wasOpen) {
|
||||
splitStore.close()
|
||||
openSplit(next)
|
||||
}
|
||||
setViewPickFor(null)
|
||||
}
|
||||
|
||||
/** 布局当前拓扑对应的预设 id(用于视图选择器高亮) */
|
||||
const presetOf = (l: LayoutSpec): string => {
|
||||
const leftCount = l.left ? 1 : 0
|
||||
const topCount = (l.top ?? []).length
|
||||
const contentCount = l.main.length
|
||||
const chatFull = l.chatFullHeight === true
|
||||
const def = PRESET_DEFS.find((d) =>
|
||||
d.leftCount === leftCount && d.topCount === topCount && d.contentCount === contentCount && d.chatFull === chatFull,
|
||||
)
|
||||
return def ? def.id : '2h'
|
||||
}
|
||||
|
||||
// ── 删除(全部走二次确认;常驻项目 = 移出工作台,插件不卸载) ──
|
||||
const removeProject = (id: string) => {
|
||||
persistProjects((prev) => ({
|
||||
@@ -983,24 +1035,16 @@ function WorktableSection(props: any) {
|
||||
|
||||
{viewOptionsOpen && <div className="dsh-wt_popBackdrop" onClick={() => setViewOptionsOpen(false)} />}
|
||||
{viewOptionsOpen && (
|
||||
<div className="dsh-wt_menu dsh-wt_pop" style={{ position: 'fixed', left: popLeft, top: popTop, width: 200, zIndex: 80 }}>
|
||||
<div className="dsh-wt_manage dsh-wt_pop dsh-wt_settings" style={{ position: 'fixed', left: popLeft, top: popTop, width: 316, zIndex: 80 }}>
|
||||
<span className="dsh-wt_menuLabel">{t('sort.label')}</span>
|
||||
<button type="button" className="dsh-wt_menuItem" data-on={view.orderBy === 'manual'}
|
||||
onClick={() => { persistView({ orderBy: 'manual' }); setViewOptionsOpen(false) }}>{t('sort.manual')}</button>
|
||||
onClick={() => persistView({ orderBy: 'manual' })}>{t('sort.manual')}</button>
|
||||
<button type="button" className="dsh-wt_menuItem" data-on={view.orderBy === 'recent'}
|
||||
onClick={() => { persistView({ orderBy: 'recent' }); setViewOptionsOpen(false) }}>{t('sort.recent')}</button>
|
||||
onClick={() => persistView({ orderBy: 'recent' })}>{t('sort.recent')}</button>
|
||||
<div className="dsh-wt_menuSep" />
|
||||
<button type="button" className="dsh-wt_menuItem"
|
||||
onClick={() => { setManaging(true); setViewOptionsOpen(false) }}>{t('menu.manage')}</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{managing && <div className="dsh-wt_popBackdrop" onClick={() => setManaging(false)} />}
|
||||
{managing && (
|
||||
<div className="dsh-wt_manage dsh-wt_pop" style={{ position: 'fixed', left: popLeft, top: popTop, width: 316, zIndex: 80 }}>
|
||||
<div className="dsh-wt_manageHead">
|
||||
<span className="dsh-wt_manageTitle">{t('manage.title')}</span>
|
||||
<button type="button" className="dsh-wt_manageDone" onClick={() => setManaging(false)}>{t('manage.done')}</button>
|
||||
<button type="button" className="dsh-wt_manageDone" onClick={() => setViewOptionsOpen(false)}>{t('manage.done')}</button>
|
||||
</div>
|
||||
{effectiveOrder.map((id) => {
|
||||
const meta = metas[id]
|
||||
@@ -1048,6 +1092,9 @@ function WorktableSection(props: any) {
|
||||
<button type="button" className="dsh-wt_manageBtn" title={isHidden ? t('manage.show') : t('manage.hide')} onClick={() => toggleHidden(id)}>
|
||||
{isHidden ? '🙈' : '👁'}
|
||||
</button>
|
||||
{layout && (
|
||||
<button type="button" className="dsh-wt_manageBtn" title={t('manage.changeView')} onClick={() => setViewPickFor(id)}>🧩</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="dsh-wt_manageBtn"
|
||||
@@ -1075,6 +1122,29 @@ function WorktableSection(props: any) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{viewPickFor && <div className="dsh-wt_popBackdrop" style={{ zIndex: 81 }} onClick={() => setViewPickFor(null)} />}
|
||||
{viewPickFor && (
|
||||
<div className="dsh-wt_menu dsh-wt_pop" style={{ position: 'fixed', left: popLeft, top: popTop, width: 320, zIndex: 82 }}>
|
||||
<span className="dsh-wt_menuLabel">{t('viewPick.title')}</span>
|
||||
<div className="dsh-wt_presets">
|
||||
{PRESET_DEFS.map((def) => {
|
||||
const cur = projects.layouts.find((l) => l.id === viewPickFor)
|
||||
return (
|
||||
<button
|
||||
key={def.id}
|
||||
type="button"
|
||||
className="dsh-wt_preset"
|
||||
data-on={cur && presetOf(cur) === def.id ? 'true' : 'false'}
|
||||
onClick={() => applyLayoutChange(viewPickFor, def.id)}
|
||||
>
|
||||
{presetThumb(def.id)}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{requestDelete && <div className="dsh-wt_confirmBackdrop" onClick={() => setRequestDelete(null)} />}
|
||||
{requestDelete && (
|
||||
<div className="dsh-wt_confirm" role="alertdialog">
|
||||
@@ -1093,7 +1163,7 @@ function WorktableSection(props: any) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="dsh-wt_projects" data-managing={managing ? 'true' : undefined}>
|
||||
<div className="dsh-wt_projects" data-managing={viewOptionsOpen ? 'true' : undefined}>
|
||||
{renderProjectSlot
|
||||
? renderProjectSlot('sidebar.worktable.project', ownerProps)
|
||||
: <div className="dsh-wt_empty">{t('empty')}</div>}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const zh = {
|
||||
'handle.title': '按住拖动位置 · 双击复位',
|
||||
'handle.aria': '拖动工作台位置',
|
||||
'menu.search': '搜索项目',
|
||||
'menu.viewOptions': '视图选项',
|
||||
'menu.viewOptions': '设置',
|
||||
'menu.add': '添加项目',
|
||||
'search.placeholder': '搜索项目…',
|
||||
'search.close': '关闭搜索',
|
||||
@@ -45,6 +45,8 @@ export const zh = {
|
||||
'add.layoutInvalid': '请填写布局名称',
|
||||
'manage.deleteLayout': '删除布局',
|
||||
'manage.deleteProject': '删除项目',
|
||||
'manage.changeView': '变更视图',
|
||||
'viewPick.title': '选择新视图(内容窗自动迁入)',
|
||||
'confirm.title': '删除项目',
|
||||
'confirm.cancel': '取消',
|
||||
'confirm.delete': '删除',
|
||||
@@ -64,10 +66,6 @@ export const zh = {
|
||||
'pane.openLater': '该类型暂不支持预览',
|
||||
'file.loading': '加载中…',
|
||||
'file.fail': '文件读取失败',
|
||||
'pdf.pan': '抓手(按住空格临时拖动 / H 切换常开)',
|
||||
'pdf.zoomIn': '放大',
|
||||
'pdf.zoomOut': '缩小',
|
||||
'pdf.fit': '适应宽度',
|
||||
'pane.gitNotRepo': '当前目录不是 git 仓库',
|
||||
'pane.gitClean': '工作区干净',
|
||||
'pane.jobsEmpty': '当前会话暂无后台任务',
|
||||
@@ -93,7 +91,7 @@ export const en = {
|
||||
'handle.title': 'Drag to move · double-click to reset',
|
||||
'handle.aria': 'Drag worktable position',
|
||||
'menu.search': 'Search projects',
|
||||
'menu.viewOptions': 'View options',
|
||||
'menu.viewOptions': 'Settings',
|
||||
'menu.add': 'Add project',
|
||||
'search.placeholder': 'Search projects…',
|
||||
'search.close': 'Close search',
|
||||
@@ -127,6 +125,8 @@ export const en = {
|
||||
'add.layoutInvalid': 'Enter a layout name',
|
||||
'manage.deleteLayout': 'Delete layout',
|
||||
'manage.deleteProject': 'Delete project',
|
||||
'manage.changeView': 'Change layout',
|
||||
'viewPick.title': 'Pick a new layout (panes migrate automatically)',
|
||||
'confirm.title': 'Delete project',
|
||||
'confirm.cancel': 'Cancel',
|
||||
'confirm.delete': 'Delete',
|
||||
@@ -146,10 +146,6 @@ export const en = {
|
||||
'pane.openLater': 'Preview not supported for this type',
|
||||
'file.loading': 'Loading…',
|
||||
'file.fail': 'Failed to read file',
|
||||
'pdf.pan': 'Pan tool (hold Space to drag temporarily / H to toggle)',
|
||||
'pdf.zoomIn': 'Zoom in',
|
||||
'pdf.zoomOut': 'Zoom out',
|
||||
'pdf.fit': 'Fit width',
|
||||
'pane.gitNotRepo': 'Not a git repository',
|
||||
'pane.gitClean': 'Working tree clean',
|
||||
'pane.jobsEmpty': 'No background jobs in the current session',
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Terminal } from 'xterm'
|
||||
import 'xterm/css/xterm.css'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.min.mjs'
|
||||
|
||||
/**
|
||||
* dsh-worktable 乐高式工作区 M1:通用分栏引擎(PRD §13)。
|
||||
@@ -1098,201 +1097,12 @@ const mdRenderer = new MarkdownIt({ linkify: true })
|
||||
const IMAGE_EXTS = /[.](png|jpe?g|gif|webp|svg|bmp|ico)$/i
|
||||
const MD_EXTS = /[.](md|markdown|mdown)$/i
|
||||
|
||||
/** 自绘 PDF 阅读器:pdf.js 画布渲染 + 抓手(空格按住临时 / H 切换常开 / 顶部按钮) */
|
||||
function PdfViewer(props: { path: string }) {
|
||||
const fileUrl = '/api/worktable/file?path=' + encodeURIComponent(props.path)
|
||||
const wrapRef = useRef<HTMLDivElement | null>(null)
|
||||
const canvasesRef = useRef<Record<number, HTMLCanvasElement | null>>({})
|
||||
const tasksRef = useRef<Record<number, any>>({})
|
||||
const rafRef = useRef(0)
|
||||
const [failed, setFailed] = useState(false)
|
||||
const [doc, setDoc] = useState<any | null>(null)
|
||||
const [pageCount, setPageCount] = useState(0)
|
||||
const [scale, setScale] = useState(1)
|
||||
const [fitScale, setFitScale] = useState<number | null>(null)
|
||||
const [panOn, setPanOn] = useState(false)
|
||||
const [spaceHeld, setSpaceHeld] = useState(false)
|
||||
const dragRef = useRef<{ x: number; y: number; sl: number; st: number; active: boolean } | null>(null)
|
||||
|
||||
// 载入文档(失败回退原生 iframe)
|
||||
useEffect(() => {
|
||||
let dead = false
|
||||
;(async () => {
|
||||
try {
|
||||
if (!pdfjsLib.GlobalWorkerOptions.workerSrc) {
|
||||
// worker 源码由构建时注入(__WT_PDF_WORKER__),Blob URL 起 module worker,零服务端依赖
|
||||
const code = (window as any).__WT_PDF_WORKER__
|
||||
if (code) {
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = URL.createObjectURL(
|
||||
new Blob([code], { type: 'text/javascript' }),
|
||||
)
|
||||
}
|
||||
}
|
||||
const d: any = await pdfjsLib.getDocument({ url: fileUrl }).promise
|
||||
if (dead) { try { d.destroy() } catch {}; return }
|
||||
setDoc(d)
|
||||
setPageCount(d.numPages)
|
||||
const p0 = await d.getPage(1)
|
||||
const vp = p0.getViewport({ scale: 1 })
|
||||
if (!dead) setFitScale(vp.width)
|
||||
} catch {
|
||||
if (!dead) setFailed(true)
|
||||
}
|
||||
})()
|
||||
return () => { dead = true; tasksRef.current = {} }
|
||||
}, [fileUrl])
|
||||
|
||||
// 首次按容器宽度自适应
|
||||
useEffect(() => {
|
||||
if (fitScale == null || scale !== 1 || !doc) return
|
||||
const wrap = wrapRef.current
|
||||
if (!wrap) return
|
||||
const w = wrap.clientWidth - 24
|
||||
if (w > 80) setScale(Math.max(0.2, Math.min(3, w / fitScale)))
|
||||
}, [fitScale, doc, scale])
|
||||
|
||||
// 渲染一页(canvas 尺寸随缩放,DPR 上限 2)
|
||||
const renderPage = useCallback(async (n: number, pageScale: number) => {
|
||||
const canvas = canvasesRef.current[n]
|
||||
const d = doc
|
||||
if (!canvas || !d) return
|
||||
const pending = tasksRef.current[n]
|
||||
if (pending) { try { pending.cancel() } catch {} }
|
||||
try {
|
||||
const page = await d.getPage(n)
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2)
|
||||
const vp = page.getViewport({ scale: pageScale * dpr })
|
||||
canvas.width = Math.floor(vp.width)
|
||||
canvas.height = Math.floor(vp.height)
|
||||
canvas.style.width = Math.floor(vp.width / dpr) + 'px'
|
||||
canvas.style.height = Math.floor(vp.height / dpr) + 'px'
|
||||
const task = page.render({ canvasContext: canvas.getContext('2d'), viewport: vp })
|
||||
tasksRef.current[n] = task
|
||||
await task.promise.catch(() => {})
|
||||
tasksRef.current[n] = null
|
||||
} catch {}
|
||||
}, [doc])
|
||||
|
||||
// 视口按需渲染(滚动节流 rAF)
|
||||
useEffect(() => {
|
||||
const wrap = wrapRef.current
|
||||
if (!wrap || !doc) return
|
||||
const visible = () => {
|
||||
const top = wrap.scrollTop - 420
|
||||
const bottom = wrap.scrollTop + wrap.clientHeight + 420
|
||||
const phs = Array.from(wrap.querySelectorAll<HTMLElement>('[data-page]'))
|
||||
for (const el of phs) {
|
||||
const n = Number(el.getAttribute('data-page'))
|
||||
const y = el.offsetTop
|
||||
const h = el.offsetHeight
|
||||
if (y + h >= top && y <= bottom) renderPage(n, scale)
|
||||
}
|
||||
}
|
||||
const onScroll = () => {
|
||||
cancelAnimationFrame(rafRef.current)
|
||||
rafRef.current = requestAnimationFrame(visible)
|
||||
}
|
||||
wrap.addEventListener('scroll', onScroll, { passive: true })
|
||||
visible()
|
||||
return () => {
|
||||
wrap.removeEventListener('scroll', onScroll)
|
||||
cancelAnimationFrame(rafRef.current)
|
||||
}
|
||||
}, [doc, scale, renderPage])
|
||||
|
||||
// 键盘:空格按住临时抓手 / H 常开抓手(输入框内不劫持)
|
||||
useEffect(() => {
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
const t = e.target as HTMLElement | null
|
||||
if (t && (t.isContentEditable || t.tagName === 'INPUT' || t.tagName === 'TEXTAREA')) return
|
||||
if (e.code === 'Space') {
|
||||
e.preventDefault()
|
||||
setSpaceHeld(true)
|
||||
} else if (e.key === 'h' || e.key === 'H') {
|
||||
setPanOn((v) => !v)
|
||||
}
|
||||
}
|
||||
const onKeyUp = (e: KeyboardEvent) => {
|
||||
if (e.code === 'Space') setSpaceHeld(false)
|
||||
}
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
window.addEventListener('keyup', onKeyUp)
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKeyDown)
|
||||
window.removeEventListener('keyup', onKeyUp)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const panActive = panOn || spaceHeld
|
||||
|
||||
const onPointerDown = (e: any) => {
|
||||
if (!panActive) return
|
||||
const wrap = wrapRef.current
|
||||
if (!wrap) return
|
||||
e.preventDefault()
|
||||
dragRef.current = { x: e.clientX, y: e.clientY, sl: wrap.scrollLeft, st: wrap.scrollTop, active: true }
|
||||
try { (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId) } catch {}
|
||||
}
|
||||
const onPointerMove = (e: any) => {
|
||||
const p = dragRef.current
|
||||
const wrap = wrapRef.current
|
||||
if (!p || !p.active || !wrap) return
|
||||
wrap.scrollLeft = p.sl - (e.clientX - p.x)
|
||||
wrap.scrollTop = p.st - (e.clientY - p.y)
|
||||
}
|
||||
const onPointerUp = () => {
|
||||
if (dragRef.current) dragRef.current.active = false
|
||||
}
|
||||
|
||||
const zoom = (factor: number) => setScale((s) => Math.max(0.2, Math.min(5, Math.round(s * factor * 100) / 100)))
|
||||
const fitWidth = () => {
|
||||
const wrap = wrapRef.current
|
||||
if (!wrap || fitScale == null) return
|
||||
const w = wrap.clientWidth - 24
|
||||
if (w > 80) setScale(Math.max(0.2, Math.min(3, w / fitScale)))
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
return <iframe className="dsh-wt_paneFrame" src={fileUrl} title={basenameOf(props.path)} />
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div className="dsh-wt_pdfBar">
|
||||
<button
|
||||
type="button"
|
||||
className={'dsh-wt_pdfBtn' + (panOn ? ' dsh-wt_pdfBtnOn' : '')}
|
||||
title={T('pdf.pan')}
|
||||
onClick={() => setPanOn((v) => !v)}
|
||||
>✋</button>
|
||||
<button type="button" className="dsh-wt_pdfBtn" title={T('pdf.zoomOut')} onClick={() => zoom(1 / 1.25)}>−</button>
|
||||
<span className="dsh-wt_pdfPct">{Math.round(scale * 100)}%</span>
|
||||
<button type="button" className="dsh-wt_pdfBtn" title={T('pdf.zoomIn')} onClick={() => zoom(1.25)}>+</button>
|
||||
<button type="button" className="dsh-wt_pdfBtn" title={T('pdf.fit')} onClick={fitWidth}>⤢</button>
|
||||
</div>
|
||||
<div
|
||||
ref={wrapRef}
|
||||
className={'dsh-wt_pdf' + (panActive ? ' dsh-wt_pdfPan' : '')}
|
||||
onPointerDown={onPointerDown}
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerUp={onPointerUp}
|
||||
onPointerCancel={onPointerUp}
|
||||
>
|
||||
{Array.from({ length: pageCount }, (_, i) => (
|
||||
<div key={i} className="dsh-wt_pdfPage" data-page={i + 1}>
|
||||
<canvas ref={(el) => { canvasesRef.current[i + 1] = el }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/** 本地文件预览:PDF 自绘阅读器(抓手)、图片居中、MD 渲染、其余纯文本 */
|
||||
/** 本地文件预览:PDF 走原生 iframe(Chrome 内置阅读器)、图片居中、MD 渲染、其余纯文本 */
|
||||
function FileViewer(props: { path: string }) {
|
||||
const ext = (props.path.split('.').pop() || '').toLowerCase()
|
||||
const fileUrl = '/api/worktable/file?path=' + encodeURIComponent(props.path)
|
||||
if (ext === 'pdf') {
|
||||
return <PdfViewer path={props.path} />
|
||||
return <iframe className="dsh-wt_paneFrame" src={fileUrl} title={basenameOf(props.path)} />
|
||||
}
|
||||
if (IMAGE_EXTS.test('.' + ext)) {
|
||||
return (
|
||||
|
||||
@@ -100,17 +100,7 @@ export const css = [
|
||||
'.dsh-wt_md img{max-width:100%}',
|
||||
'.dsh-wt_imgView{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;overflow:auto;padding:10px;background:var(--dsw-alias-bg-base,#0b0e14)}',
|
||||
'.dsh-wt_imgView img{max-width:100%;max-height:100%;object-fit:contain}',
|
||||
// PDF 阅读器:工具栏 + 滚动容器 + 抓手光标
|
||||
'.dsh-wt_pdfBar{flex:none;display:flex;align-items:center;gap:6px;padding:4px 8px;border-bottom:1px solid var(--dsw-alias-border-l1,#262b36);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02))}',
|
||||
'.dsh-wt_pdfBtn{flex:none;display:flex;align-items:center;justify-content:center;width:24px;height:22px;padding:0;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary,#9aa4b2);font-size:13px;line-height:1;cursor:pointer}',
|
||||
'.dsh-wt_pdfBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}',
|
||||
'.dsh-wt_pdfBtnOn{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);color:var(--dsw-alias-state-accent-primary,#4f8ef7);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.06))}',
|
||||
'.dsh-wt_pdfPct{font-size:11px;color:var(--dsw-alias-label-secondary,#9aa4b2);min-width:42px;text-align:center}',
|
||||
'.dsh-wt_pdf{flex:1;min-height:0;overflow:auto;padding:12px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.03))}',
|
||||
'.dsh-wt_pdfPan{cursor:grab}',
|
||||
'.dsh-wt_pdfPan:active{cursor:grabbing}',
|
||||
'.dsh-wt_pdfPage{margin:0 auto 14px;width:max-content;background:#fff;box-shadow:var(--dsw-shadow-lv2,0 8px 24px rgba(0,0,0,.4))}',
|
||||
'.dsh-wt_pdfPage canvas{display:block}',
|
||||
|
||||
'.dsh-wt_pane[data-drop-hover=true]{border-color:var(--dsw-alias-state-accent-primary,#4f8ef7);box-shadow:0 0 0 2px rgba(79,142,247,.4),var(--dsw-shadow-lv2,0 8px 24px rgba(0,0,0,.4));transform:scale(1.012)}',
|
||||
'.dsh-wt_paneBar{flex:none;display:flex;align-items:center;height:22px;padding:0 8px;border-bottom:1px solid var(--dsw-alias-border-l1,#262b36);background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02))}',
|
||||
'.dsh-wt_paneTitle{flex:1;min-width:0;font-size:11px;font-weight:600;color:var(--dsw-alias-label-secondary,#9aa4b2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
||||
|
||||
+1
-1
@@ -85,7 +85,7 @@ agent 级项目**(如旅行图鉴 TravelAtlas),让用户可以像管理应
|
||||
| 按钮 | 图标(primitives) | 行为 |
|
||||
| --- | --- | --- |
|
||||
| 搜索 | 🔍 | 点击展开输入行(Esc / 点 ✕ 收起);输入即过滤项目卡片与快捷方式(query 经座位 owner props 传给每个卡片,卡片自行判断是否隐藏) |
|
||||
| 视图选项 | ☰ | 排序方式(手动/最近),选择即持久化到 `dsh.worktable.view.v1`;**默认「手动」**(v2 起;最近为可选——点击即置顶的体验取舍);另含「管理项目…」入口(§5.5)。分组方式已按用户定案取消 |
|
||||
| 设置(原「视图选项」,2026-08-17 更名) | 滑块 icon | 右侧 fixed 弹窗直接内嵌「排序方式(手动/最近,默认手动)+ 管理项目展开列表(含变更视图)」,不再二次点击进入管理面板 |
|
||||
| 添加 | + | 展开「添加项目」面板:接入指引(注册即入驻说明 + 插件市场外链)+ 本地快捷方式表单(§5.6) |
|
||||
|
||||
> 官方工作区头部三按钮 = 搜索 / 视图选项(ViewOptionsMenu) / 添加工作区(+),已逆向确认。
|
||||
|
||||
@@ -633,3 +633,25 @@
|
||||
空格按住 panClass=true/松开 false;H 开/关正确;放大至 281% 溢出后合成指针拖拽
|
||||
scrollTopAfterDrag=140(平移生效);全 STEP 回归 ERROR_COUNT: 0。
|
||||
- 备注:全部客户端改动,F5 即生效,无需重启 dsh web。
|
||||
## 补记(PDF 回退原生阅读器 + 设置面板合并 + 变更视图)
|
||||
|
||||
- 用户反馈:① 自绘 PDF 阅读器不要了,回退之前的原生 Chrome PDF 浏览器(其无法加抓手:
|
||||
原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动——
|
||||
向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目
|
||||
展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑,
|
||||
自定义内容(标签)自动迁入新视图。
|
||||
- 处理:
|
||||
① PDF 回退:删 PdfViewer/pdfjs 依赖/worker banner/CSS/locale pdf.*/fixture;
|
||||
FileViewer pdf → 原生 iframe(服务端 application/pdf MIME 保留,供原生内嵌渲染);
|
||||
bundle 从 2.6MB 回到 747KB;build.mjs 保留 esbuild JS API;
|
||||
② 设置面板:managing 状态并入 viewOptionsOpen;设置弹窗 = 排序项 + 分隔 + 管理标题/完成 +
|
||||
管理行列表 + 恢复默认,宽 316;「管理项目…」菜单项与独立管理弹窗删除;卡区弱化跟随设置开关;
|
||||
③ 变更视图:布局行加 🧩 按钮 → 弹 6 预设缩略图选择器(当前拓扑高亮);applyLayoutChange
|
||||
按预设重建拓扑(保留 id/名称/图标),现有窗标签按序迁入新窗格、多余并入末窗不丢失;
|
||||
当前打开的布局即时关旧开新生效。locale:menu.viewOptions→设置/Settings,
|
||||
新增 manage.changeView/viewPick.title。
|
||||
- 验证(functional-diag STEP8/9 重写 + STEP10 适配):STEP8 设置弹窗 fixed/left=288/
|
||||
sortItems=2/rows=4 直接展开;STEP9 布局行 5 按钮 → 🧩 → 6 预设 → 选 l2 →
|
||||
newTop=1/newMain=1/chatFull=true + migrated=1(标签迁入顶行);STEP10 删除确认流程在新面板
|
||||
内全部保持;STEP11 MD/TXT 不变;ERRORS_COUNT: 0。
|
||||
- 备注:纯客户端改动,F5 即生效,无需重启 dsh web。
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
%PDF-1.4
|
||||
1 0 obj
|
||||
<< /Type /Catalog /Pages 2 0 R >>
|
||||
endobj
|
||||
2 0 obj
|
||||
<< /Type /Pages /Kids [3 0 R] /Count 1 >>
|
||||
endobj
|
||||
3 0 obj
|
||||
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>
|
||||
endobj
|
||||
4 0 obj
|
||||
<< /Length 74 >>
|
||||
stream
|
||||
BT /F1 24 Tf 72 770 Td (Hello Worktable PDF) Tj ET
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000009 00000 n
|
||||
0000000058 00000 n
|
||||
0000000115 00000 n
|
||||
0000000241 00000 n
|
||||
0000000341 00000 n
|
||||
trailer
|
||||
<< /Size 6 /Root 1 0 R >>
|
||||
startxref
|
||||
411
|
||||
%%EOF
|
||||
+27
-70
@@ -48,7 +48,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await send('Log.enable');
|
||||
await send('Page.enable');
|
||||
await send('Page.addScriptToEvaluateOnNewDocument', {
|
||||
source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:[],lastUsed:{},hidden:[],nameOverrides:{},shortcuts:[{id:'t-sc',name:'\u6d4b\u8bd5\u5feb\u6377',icon:'\ud83d\udd17',href:'https://example.com'}],layouts:[{id:'t-layout',title:'\u6d4b\u8bd5\u5e03\u5c40',top:null,left:null,main:[{id:'p1',title:'\u5185\u5bb91',min:200,content:null}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}]})); } }catch(e){}",
|
||||
source: "try{ if(location.origin==='http://127.0.0.1:3080'){ localStorage.setItem('dsh.worktable.view.v1', JSON.stringify({query:'',searchOpen:false,orderBy:'manual',dock:'footer',floatTop:null,sortMigratedV2:true})); localStorage.setItem('dsh.worktable.projects.v1', JSON.stringify({order:[],lastUsed:{},hidden:[],nameOverrides:{},shortcuts:[{id:'t-sc',name:'\u6d4b\u8bd5\u5feb\u6377',icon:'\ud83d\udd17',href:'https://example.com'}],layouts:[{id:'t-layout',title:'\u6d4b\u8bd5\u5e03\u5c40',top:null,left:null,main:[{id:'p1',title:'\u5185\u5bb91',min:200,content:null,tabs:[{id:'t1',title:'Browser',content:{kind:'builtin',type:'browser'}}],active:0}],leftWidth:{default:260,min:160,max:480},chatWidth:{default:360,min:240,max:600},topHeight:{default:200,min:120,max:480},chatSide:'right',chatFullHeight:false}]})); } }catch(e){}",
|
||||
});
|
||||
await send('Page.navigate', { url: 'http://127.0.0.1:3080/' });
|
||||
await sleep(11000);
|
||||
@@ -150,39 +150,48 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
})()`);
|
||||
console.log('STEP7:', JSON.stringify(step7));
|
||||
|
||||
// 视图选项:右侧 fixed 弹窗(对齐 + 弹窗),不再下方内联展开
|
||||
// 设置弹窗:右侧 fixed,直接内嵌「排序方式 + 管理项目(展开列表)」
|
||||
const step8 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
var btn=document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)');
|
||||
if(btn){ btn.click(); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
var menu=document.querySelector('.dsh-wt_menu.dsh-wt_pop');
|
||||
if(menu){ var cs=getComputedStyle(menu); out.menuPos=cs.position; out.menuLeft=menu.getBoundingClientRect().left; }
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
var sp=document.querySelector('.dsh-wt_settings');
|
||||
if(sp){ var cs=getComputedStyle(sp); out.pos=cs.position; out.left=sp.getBoundingClientRect().left; out.sortItems=sp.querySelectorAll('.dsh-wt_menuItem').length; out.rows=sp.querySelectorAll('.dsh-wt_manageRow').length; }
|
||||
out.backdrop=!!document.querySelector('.dsh-wt_popBackdrop');
|
||||
var bd=document.querySelector('.dsh-wt_popBackdrop');
|
||||
if(bd){ bd.click(); }
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.closedAfterBackdrop=!document.querySelector('.dsh-wt_menu.dsh-wt_pop');
|
||||
out.closedAfterBackdrop=!document.querySelector('.dsh-wt_settings');
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP8:', JSON.stringify(step8));
|
||||
|
||||
// 管理项目:从视图选项弹窗同锚点往下展开为 fixed 面板(不再内联展开)
|
||||
// 变更视图:布局行 🧩 → 选择新预设 → 拓扑重建 + 内容标签自动迁入
|
||||
const step9 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
var btn=document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)');
|
||||
if(btn){ btn.click(); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
var items=document.querySelectorAll('.dsh-wt_menu.dsh-wt_pop .dsh-wt_menuItem');
|
||||
var manageItem=items[items.length-1];
|
||||
if(manageItem){ manageItem.click(); }
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
var mp=document.querySelector('.dsh-wt_manage.dsh-wt_pop');
|
||||
if(mp){ var cs=getComputedStyle(mp); out.managePos=cs.position; out.manageLeft=mp.getBoundingClientRect().left; out.manageTop=mp.getBoundingClientRect().top; out.rows=mp.querySelectorAll('.dsh-wt_manageRow').length; }
|
||||
var rows=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)');
|
||||
var layoutRow=rows[rows.length-1];
|
||||
if(layoutRow){ out.layoutRowBtns=layoutRow.querySelectorAll('.dsh-wt_manageBtn').length; }
|
||||
var puzzle=layoutRow&&layoutRow.querySelectorAll('.dsh-wt_manageBtn')[layoutRow.querySelectorAll('.dsh-wt_manageBtn').length-2];
|
||||
if(puzzle){ puzzle.click(); }
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
out.viewPicker=!!document.querySelector('.dsh-wt_pop .dsh-wt_presets');
|
||||
out.presetCount=document.querySelectorAll('.dsh-wt_presets .dsh-wt_preset').length;
|
||||
var presets=document.querySelectorAll('.dsh-wt_presets .dsh-wt_preset');
|
||||
if(presets[2]){ presets[2].click(); }
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
try{
|
||||
var lay=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts[0];
|
||||
out.newTop=(lay.top||[]).length; out.newMain=lay.main.length; out.chatFull=lay.chatFullHeight===true;
|
||||
out.migrated=(lay.top&&lay.top[0]&&lay.top[0].tabs||[]).length; out.mainTabs=lay.main[0].tabs?lay.main[0].tabs.length:0;
|
||||
}catch(e){ out.savedErr=String(e) }
|
||||
var bd=document.querySelector('.dsh-wt_popBackdrop');
|
||||
if(bd){ bd.click(); }
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.closedAfterBackdrop=!document.querySelector('.dsh-wt_manage.dsh-wt_pop');
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP9:', JSON.stringify(step9));
|
||||
@@ -207,11 +216,8 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
out.cardsBefore={ta:document.querySelectorAll('.dsh-wt_projects .ta_card').length,pr:document.querySelectorAll('.dsh-wt_projects .pr_card').length};
|
||||
var btn=document.querySelector('.dsh-wt_actions .dsh-wt_iconBtn:nth-child(2)');
|
||||
if(btn){ btn.click(); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
var items=document.querySelectorAll('.dsh-wt_menu.dsh-wt_pop .dsh-wt_menuItem');
|
||||
if(items.length){ items[items.length-1].click(); }
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
var rows=document.querySelectorAll('.dsh-wt_manage.dsh-wt_pop .dsh-wt_manageRow');
|
||||
var rows=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow');
|
||||
out.rows0=rows.length;
|
||||
if(rows[0]) out.row0BtnCount=rows[0].querySelectorAll('.dsh-wt_manageBtn').length;
|
||||
var xBtn=rows[0]&&rows[0].querySelectorAll('.dsh-wt_manageBtn')[rows[0].querySelectorAll('.dsh-wt_manageBtn').length-1];
|
||||
@@ -223,7 +229,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.confirmGoneAfterCancel=!document.querySelector('.dsh-wt_confirm');
|
||||
out.taStillThere=!!document.querySelector('.dsh-wt_projects .ta_card');
|
||||
var rows2=document.querySelectorAll('.dsh-wt_manage.dsh-wt_pop .dsh-wt_manageRow');
|
||||
var rows2=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow');
|
||||
var xBtn2=rows2[0]&&rows2[0].querySelectorAll('.dsh-wt_manageBtn')[rows2[0].querySelectorAll('.dsh-wt_manageBtn').length-1];
|
||||
if(xBtn2){ xBtn2.click(); }
|
||||
await new Promise(function(r){setTimeout(r,250)});
|
||||
@@ -232,8 +238,8 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
out.cardsAfter={ta:document.querySelectorAll('.dsh-wt_projects .ta_card').length,pr:document.querySelectorAll('.dsh-wt_projects .pr_card').length};
|
||||
try{ out.removed=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).removed }catch(e){ out.removed='ERR' }
|
||||
out.rowsAfterDelete=document.querySelectorAll('.dsh-wt_manage.dsh-wt_pop .dsh-wt_manageRow').length;
|
||||
var rows3=document.querySelectorAll('.dsh-wt_manage.dsh-wt_pop .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)');
|
||||
out.rowsAfterDelete=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow').length;
|
||||
var rows3=document.querySelectorAll('.dsh-wt_settings .dsh-wt_manageRow:not(.dsh-wt_manageRowSc)');
|
||||
var lastRow=rows3[rows3.length-1];
|
||||
var xBtn3=lastRow&&lastRow.querySelectorAll('.dsh-wt_manageBtn')[lastRow.querySelectorAll('.dsh-wt_manageBtn').length-1];
|
||||
if(xBtn3){ xBtn3.click(); }
|
||||
@@ -272,55 +278,6 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
})()`);
|
||||
console.log('STEP11:', JSON.stringify(step11));
|
||||
|
||||
// PDF 阅读器:自绘渲染 + 抓手(H 切换/空格临时/拖拽平移)+ 缩放
|
||||
const step12 = await evaluate(`(async function(){
|
||||
var out={};
|
||||
try{
|
||||
var st=window.__dshWorktable.splitStore;
|
||||
st.open({id:'t-pdf',title:'pdf',top:null,main:[{id:'q1',title:'q',min:200,content:null}],chatWidth:{default:320,min:240,max:600}});
|
||||
await new Promise(function(r){setTimeout(r,400)});
|
||||
st.openTab('main',0,{kind:'file',path:'E:\\\\AI_Workspace\\\\DeepseekHarness\\\\Projects\\\\dsh-worktable\\\\04_test\\\\fixture.pdf'});
|
||||
await new Promise(function(r){setTimeout(r,6500)});
|
||||
out.bar=!!document.querySelector('.dsh-wt_pdfBar');
|
||||
out.pages=document.querySelectorAll('.dsh-wt_pdfPage canvas').length;
|
||||
out.iframeFallback=!!document.querySelector('.dsh-wt_paneFrame');
|
||||
var c=document.querySelector('.dsh-wt_pdfPage canvas');
|
||||
out.canvasW=c?c.width:0;
|
||||
var pdfEl=document.querySelector('.dsh-wt_pdf');
|
||||
if(pdfEl){
|
||||
// 空格临时抓手
|
||||
window.dispatchEvent(new KeyboardEvent('keydown',{key:' ',code:'Space',bubbles:true}));
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.panClassAfterSpace=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
|
||||
window.dispatchEvent(new KeyboardEvent('keyup',{key:' ',code:'Space',bubbles:true}));
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.panClassAfterSpaceUp=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
|
||||
// H 常开抓手
|
||||
window.dispatchEvent(new KeyboardEvent('keydown',{key:'h',code:'KeyH',bubbles:true}));
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.panClassAfterH=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
|
||||
// 放大到内容溢出(可滚动;btns 顺序:抓手/缩小/放大/适应宽度)
|
||||
var btns=document.querySelectorAll('.dsh-wt_pdfBtn');
|
||||
for(var z=0;z<6 && btns[2];z++){ btns[2].click(); }
|
||||
await new Promise(function(r){setTimeout(r,1200)});
|
||||
out.pct=document.querySelector('.dsh-wt_pdfPct')?document.querySelector('.dsh-wt_pdfPct').textContent:null;
|
||||
out.scrollable=pdfEl.scrollHeight>pdfEl.clientHeight;
|
||||
var r0=pdfEl.getBoundingClientRect();
|
||||
var x=r0.left+r0.width/2, y=r0.top+r0.height/2;
|
||||
pdfEl.dispatchEvent(new PointerEvent('pointerdown',{clientX:x,clientY:y,bubbles:true,pointerId:1}));
|
||||
pdfEl.dispatchEvent(new PointerEvent('pointermove',{clientX:x,clientY:y-140,bubbles:true,pointerId:1}));
|
||||
pdfEl.dispatchEvent(new PointerEvent('pointerup',{clientX:x,clientY:y-140,bubbles:true,pointerId:1}));
|
||||
await new Promise(function(r){setTimeout(r,300)});
|
||||
out.scrollTopAfterDrag=pdfEl.scrollTop;
|
||||
window.dispatchEvent(new KeyboardEvent('keydown',{key:'h',code:'KeyH',bubbles:true}));
|
||||
await new Promise(function(r){setTimeout(r,200)});
|
||||
out.panClassAfterH2=pdfEl.className.indexOf('dsh-wt_pdfPan')>=0;
|
||||
}
|
||||
st.close();
|
||||
}catch(err){ out.err=String(err) }
|
||||
return JSON.stringify(out);
|
||||
})()`);
|
||||
console.log('STEP12:', JSON.stringify(step12));
|
||||
|
||||
const errors = events.filter((e) => {
|
||||
}).filter((e) =>
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// 生成最小合法 PDF 测试夹具(单页 Hello,带正确 xref 偏移)
|
||||
const fs = require('fs');
|
||||
const objects = [
|
||||
'1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n',
|
||||
'2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n',
|
||||
'3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842] /Contents 4 0 R /Resources << /Font << /F1 5 0 R >> >> >>\nendobj\n',
|
||||
'4 0 obj\n<< /Length 74 >>\nstream\nBT /F1 24 Tf 72 770 Td (Hello Worktable PDF) Tj ET\nendstream\nendobj\n',
|
||||
'5 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\nendobj\n',
|
||||
];
|
||||
let body = '%PDF-1.4\n';
|
||||
const offsets = [0];
|
||||
for (const o of objects) { offsets.push(body.length); body += o; }
|
||||
const xrefPos = body.length;
|
||||
body += 'xref\n0 ' + (objects.length + 1) + '\n';
|
||||
body += '0000000000 65535 f \n';
|
||||
for (let i = 1; i <= objects.length; i++) body += String(offsets[i]).padStart(10, '0') + ' 00000 n \n';
|
||||
body += 'trailer\n<< /Size ' + (objects.length + 1) + ' /Root 1 0 R >>\n';
|
||||
body += 'startxref\n' + xrefPos + '\n%%EOF\n';
|
||||
const out = 'E:/AI_Workspace/DeepseekHarness/Projects/dsh-worktable/04_test/fixture.pdf';
|
||||
fs.writeFileSync(out, body, 'latin1');
|
||||
console.log('fixture written', fs.statSync(out).size, 'bytes');
|
||||
Reference in New Issue
Block a user