fix: 卡片反选(高亮跟随引擎开合) + 设置面板:排序/管理同字体·宽280·排序改左右两按钮·去恢复默认

This commit is contained in:
Aisland
2026-08-18 00:40:02 +08:00
parent fc8c6119c5
commit 07e6a5e49f
5 changed files with 50 additions and 44 deletions
+22 -24
View File
@@ -635,18 +635,6 @@ function WorktableSection(props: any) {
}) })
} }
// 恢复默认:还原排序/隐藏/改名/图标/视图覆盖;已删除的项目不复活(真删除,可在「已删除的项目」重新添加)
const resetProjects = () => {
persistProjects((prev) => ({
...prev,
order: [],
hidden: [],
nameOverrides: {},
iconOverrides: {},
views: {},
}))
}
/** 重新添加已删除的入驻项目(删除 ↔ 重新添加 完全可逆) */ /** 重新添加已删除的入驻项目(删除 ↔ 重新添加 完全可逆) */
const readdProject = (id: string) => { const readdProject = (id: string) => {
persistProjects((prev) => ({ persistProjects((prev) => ({
@@ -794,10 +782,17 @@ function WorktableSection(props: any) {
if (ovr) icon.setAttribute('data-wt-icon', ovr) if (ovr) icon.setAttribute('data-wt-icon', ovr)
else icon.removeAttribute('data-wt-icon') else icon.removeAttribute('data-wt-icon')
} }
// 选中态统一由工作台判定(引擎打开的项目高亮;未打开时若引擎空闲则保留卡片自带状态, // 选中态统一由工作台判定:
// 兼容自带分栏实现的插件) // - 有视图覆盖的项目:点击恒由工作台接管 → 高亮完全跟随 activeSplitId(关闭即熄灭);
if (activeSplitId === id) el.setAttribute('data-on', 'true') // - 无视图覆盖:引擎打开时点亮;引擎开着别的时熄灭;引擎空闲时保留卡片自带状态
else if (activeSplitId != null) el.setAttribute('data-on', 'false') // (兼容自带分栏实现的插件)。
if (projects.views[id]) {
el.setAttribute('data-on', activeSplitId === id ? 'true' : 'false')
} else if (activeSplitId === id) {
el.setAttribute('data-on', 'true')
} else if (activeSplitId != null) {
el.setAttribute('data-on', 'false')
}
} else { } else {
el.removeAttribute('data-wt-id') el.removeAttribute('data-wt-id')
} }
@@ -807,7 +802,7 @@ function WorktableSection(props: any) {
const mo = new MutationObserver(sync) const mo = new MutationObserver(sync)
mo.observe(document.body, { childList: true, subtree: true }) mo.observe(document.body, { childList: true, subtree: true })
return () => mo.disconnect() return () => mo.disconnect()
}, [aliveRegisteredIds, projects.iconOverrides, activeSplitId]) }, [aliveRegisteredIds, projects.iconOverrides, projects.views, activeSplitId])
useEffect(() => { useEffect(() => {
const onDocClick = (e: MouseEvent) => { const onDocClick = (e: MouseEvent) => {
@@ -1070,12 +1065,16 @@ function WorktableSection(props: any) {
{viewOptionsOpen && <div className="dsh-wt_popBackdrop" onClick={() => setViewOptionsOpen(false)} />} {viewOptionsOpen && <div className="dsh-wt_popBackdrop" onClick={() => setViewOptionsOpen(false)} />}
{viewOptionsOpen && ( {viewOptionsOpen && (
<div className="dsh-wt_manage dsh-wt_pop dsh-wt_settings" style={{ position: 'fixed', left: popLeft, top: popTop, width: 316, zIndex: 80 }}> <div className="dsh-wt_manage dsh-wt_pop dsh-wt_settings" style={{ position: 'fixed', left: popLeft, top: popTop, width: 280, zIndex: 80 }}>
<span className="dsh-wt_menuLabel">{t('sort.label')}</span> <div className="dsh-wt_manageHead">
<button type="button" className="dsh-wt_menuItem" data-on={view.orderBy === 'manual'} <span className="dsh-wt_manageTitle">{t('sort.label')}</span>
onClick={() => persistView({ orderBy: 'manual' })}>{t('sort.manual')}</button> </div>
<button type="button" className="dsh-wt_menuItem" data-on={view.orderBy === 'recent'} <div className="dsh-wt_sortRow">
onClick={() => persistView({ orderBy: 'recent' })}>{t('sort.recent')}</button> <button type="button" className="dsh-wt_sortBtn" data-on={view.orderBy === 'manual'}
onClick={() => persistView({ orderBy: 'manual' })}>{t('sort.manual')}</button>
<button type="button" className="dsh-wt_sortBtn" data-on={view.orderBy === 'recent'}
onClick={() => persistView({ orderBy: 'recent' })}>{t('sort.recent')}</button>
</div>
<div className="dsh-wt_menuSep" /> <div className="dsh-wt_menuSep" />
<div className="dsh-wt_manageHead"> <div className="dsh-wt_manageHead">
<span className="dsh-wt_manageTitle">{t('manage.title')}</span> <span className="dsh-wt_manageTitle">{t('manage.title')}</span>
@@ -1149,7 +1148,6 @@ function WorktableSection(props: any) {
<button type="button" className="dsh-wt_manageBtn" title={t('manage.deleteShortcut')} onClick={() => askDelete('shortcut', s.id, s.name)}>✕</button> <button type="button" className="dsh-wt_manageBtn" title={t('manage.deleteShortcut')} onClick={() => askDelete('shortcut', s.id, s.name)}>✕</button>
</div> </div>
))} ))}
<button type="button" className="dsh-wt_manageReset" onClick={resetProjects}>{t('manage.reset')}</button>
{projects.removed.length > 0 && ( {projects.removed.length > 0 && (
<> <>
<div className="dsh-wt_menuSep" /> <div className="dsh-wt_menuSep" />
-2
View File
@@ -37,7 +37,6 @@ export const zh = {
'manage.removed': '已删除的项目', 'manage.removed': '已删除的项目',
'manage.readd': '重新添加', 'manage.readd': '重新添加',
'manage.deleteShortcut': '删除快捷方式', 'manage.deleteShortcut': '删除快捷方式',
'manage.reset': '恢复默认',
'shortcut.badge': '本地', 'shortcut.badge': '本地',
'add.chooseLayout': '选择布局模式', 'add.chooseLayout': '选择布局模式',
'add.layoutNamePh': '布局名称', 'add.layoutNamePh': '布局名称',
@@ -117,7 +116,6 @@ export const en = {
'manage.removed': 'Removed projects', 'manage.removed': 'Removed projects',
'manage.readd': 'Re-add', 'manage.readd': 'Re-add',
'manage.deleteShortcut': 'Delete shortcut', 'manage.deleteShortcut': 'Delete shortcut',
'manage.reset': 'Reset defaults',
'shortcut.badge': 'Local', 'shortcut.badge': 'Local',
'add.chooseLayout': 'Choose a layout', 'add.chooseLayout': 'Choose a layout',
'add.layoutNamePh': 'Layout name', 'add.layoutNamePh': 'Layout name',
+5
View File
@@ -48,6 +48,11 @@ export const css = [
'.dsh-wt_manageTitle{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-primary,#e6e8eb)}', '.dsh-wt_manageTitle{font-size:11px;font-weight:600;letter-spacing:.04em;color:var(--dsw-alias-label-primary,#e6e8eb)}',
'.dsh-wt_manageDone{display:flex;align-items:center;padding:2px 8px;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:11px;line-height:16px;cursor:pointer}', '.dsh-wt_manageDone{display:flex;align-items:center;padding:2px 8px;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-state-accent-primary,#4f8ef7);font:inherit;font-size:11px;line-height:16px;cursor:pointer}',
'.dsh-wt_manageDone:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}', '.dsh-wt_manageDone:hover{background:var(--dsw-alias-fill-l1,rgba(255,255,255,.05))}',
// 排序方式:一左一右两个选择按钮(手动 / 最近)
'.dsh-wt_sortRow{display:flex;gap:6px;padding:0 8px 2px}',
'.dsh-wt_sortBtn{flex:1;display:flex;align-items:center;justify-content:center;padding:4px 8px;border:1px solid var(--dsw-alias-border-l1,#262b36);border-radius:6px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.02));color:var(--dsw-alias-label-secondary,#9aa4b2);font:inherit;font-size:11px;line-height:16px;cursor:pointer}',
'.dsh-wt_sortBtn:hover{color:var(--dsw-alias-label-primary,#e6e8eb);border-color:var(--dsw-alias-border-l2,#3a4150)}',
'.dsh-wt_sortBtn[data-on=true]{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,.05))}',
'.dsh-wt_manageRow{display:flex;align-items:center;gap:6px;padding:4px 6px;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.03))}', '.dsh-wt_manageRow{display:flex;align-items:center;gap:6px;padding:4px 6px;border:1px solid transparent;border-radius:6px;background:var(--dsw-alias-fill-l1,rgba(255,255,255,.03))}',
'.dsh-wt_manageRow:hover{border-color:var(--dsw-alias-border-l2,#3a4150)}', '.dsh-wt_manageRow:hover{border-color:var(--dsw-alias-border-l2,#3a4150)}',
'.dsh-wt_manageRowOff{opacity:.45}', '.dsh-wt_manageRowOff{opacity:.45}',
+15 -12
View File
@@ -639,17 +639,20 @@
原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动—— 原生视图内部是浏览器扩展页,插件注入不了按钮、拦不到鼠标,也驱动不了内部滚动——
向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目 向用户如实说明后按指示回退);② 视图选项改名「设置」,点开直接内嵌「排序方式 + 管理项目
展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑, 展开列表」,不再二次点击;③ 管理项目里给现有项目加「变更视图」:换预设拓扑,
## 补记(工作台状态持久化修复 + tsx/css 代码预览) ## 补记(反选修复 + 设置面板五项微调)
- 用户反馈:① 项目里布置好的窗口内容(如上面浏览器/下面终端)切换项目再切回就变回默认, - 用户反馈(五项):① 旅行 Atlas 卡片再点一次应反选(取消选中),现在做不到;
每个项目的工作台应保持上一次的样子;② 资源管理器要能点开 tsx/css 文件预览(同 MD)。 ② 「排序方式」与「管理项目」应同级别同字体(按管理项目样式);③ 设置弹窗宽度略宽;
- 根因:onSpecMutated 回写只写 layouts 条目,漏了 views(视图覆盖工作区)→ 常驻项目 ④ 排序方式两行字改成一左一右两个选择按钮(手动/最近);⑤ 去掉「恢复默认」按钮。
内容丢失;布局路径本身正常(实测确认)。 - 处理:
- 处理:① onSpecMutated 按 spec.id 分流:layouts 里有该 id → 更新布局条目;否则写回 ① 根因:引擎其实已关,但 DOM 同步写入的 data-on=true 在引擎空闲时从不还原 → 高亮残留。
views[id](入驻项目工作区)。所有变更(开标签/关标签/换聊天边/换位)都会触发; 修正规则:有视图覆盖的项目高亮完全跟随 activeSplitId(关闭即 false);无覆盖项目引擎空闲时
② 资源管理器文件匹配加 tsx/ts/jsx/js/css/json → kind:file 预览(等宽文本展示,同 TXT 模式)。 保留卡片自带状态;
- 验证(functional-diag 重排 STEP14/15 到 STEP10 前 + STEP9 改查 planreview 键 + ② 「排序方式」标签改用 manageHead/manageTitle(与管理项目同款 11px/600);
恢复误删的 STEP13 打印行):STEP14 布局 layoutSavedTabs=1/layoutRestoredTabs=1、 ③ 设置弹窗 316 → 280px;
视图覆盖 taSavedTabs=1/taRestoredTabs=1(切换回来内容保持);STEP15 tsx/css 双标签预览 ④ 排序改为 .dsh-wt_sortRow 一左一右两个 .dsh-wt_sortBtn(选中态描边高亮);
且内容含 WorktableSection;全 15 STEP ERROR_COUNT: 0。 ⑤ 删除恢复默认按钮与 resetProjects 函数、locale manage.reset(zh/en)。
- 验证(functional-diag):STEP13 增反选断言 activeAfterSecondClick=false +
taOnAfterSecondClick='false';STEP8 sortItems=2;STEP10 resetBtnGone=true 且删除/重新添加
流程保持;全 15 STEP ERROR_COUNT: 0。
- 备注:纯客户端改动,F5 即生效。 - 备注:纯客户端改动,F5 即生效。
+8 -6
View File
@@ -157,7 +157,7 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
if(btn){ btn.click(); } if(btn){ btn.click(); }
await new Promise(function(r){setTimeout(r,300)}); await new Promise(function(r){setTimeout(r,300)});
var sp=document.querySelector('.dsh-wt_settings'); 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; } if(sp){ var cs=getComputedStyle(sp); out.pos=cs.position; out.left=sp.getBoundingClientRect().left; out.sortItems=sp.querySelectorAll('.dsh-wt_sortBtn').length; out.rows=sp.querySelectorAll('.dsh-wt_manageRow').length; }
out.backdrop=!!document.querySelector('.dsh-wt_popBackdrop'); out.backdrop=!!document.querySelector('.dsh-wt_popBackdrop');
var bd=document.querySelector('.dsh-wt_popBackdrop'); var bd=document.querySelector('.dsh-wt_popBackdrop');
if(bd){ bd.click(); } if(bd){ bd.click(); }
@@ -342,12 +342,9 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await new Promise(function(r){setTimeout(r,300)}); await new Promise(function(r){setTimeout(r,300)});
out.layoutGoneAfterDelete=!document.querySelector('.dsh-wt_layout'); out.layoutGoneAfterDelete=!document.querySelector('.dsh-wt_layout');
try{ out.layoutsLeft=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts.length }catch(e){ out.layoutsLeft='ERR' } try{ out.layoutsLeft=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).layouts.length }catch(e){ out.layoutsLeft='ERR' }
// 恢复默认不应复活已删除项目;「已删除的项目」里重新添加可逆 // 真删除:「已删除的项目」区块出现,重新添加可逆(无恢复默认按钮)
var resetBtn=document.querySelector('.dsh-wt_manageReset');
if(resetBtn){ resetBtn.click(); }
await new Promise(function(r){setTimeout(r,250)});
try{ out.removedAfterReset=JSON.parse(localStorage.getItem('dsh.worktable.projects.v1')).removed }catch(e){ out.removedAfterReset='ERR' }
out.removedSection=!!document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved'); out.removedSection=!!document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved');
out.resetBtnGone=!document.querySelector('.dsh-wt_manageReset');
var readdBtn=document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved .dsh-wt_manageBtn'); var readdBtn=document.querySelector('.dsh-wt_settings .dsh-wt_manageRowRemoved .dsh-wt_manageBtn');
if(readdBtn){ readdBtn.click(); } if(readdBtn){ readdBtn.click(); }
await new Promise(function(r){setTimeout(r,300)}); await new Promise(function(r){setTimeout(r,300)});
@@ -445,6 +442,11 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
await new Promise(function(r){setTimeout(r,500)}); await new Promise(function(r){setTimeout(r,500)});
out.taOnAfterOpen=ta?ta.getAttribute('data-on'):null; out.taOnAfterOpen=ta?ta.getAttribute('data-on'):null;
out.engineSpecId=st.spec?st.spec.id:null; out.engineSpecId=st.spec?st.spec.id:null;
// 反选:再点一次卡片 → 引擎关闭 + 高亮熄灭
if(ta){ ta.click(); }
await new Promise(function(r){setTimeout(r,400)});
out.activeAfterSecondClick=st.active;
out.taOnAfterSecondClick=ta?ta.getAttribute('data-on'):null;
st.close(); st.close();
await new Promise(function(r){setTimeout(r,300)}); await new Promise(function(r){setTimeout(r,300)});
}catch(err){ out.err=String(err) } }catch(err){ out.err=String(err) }