feat(editor): 文件窗口与资源管理器二合一 + 声明式设置 select 行 (#151)
* feat(editor): merged explorer mode — path input, docked file tree, global name search
New editorExplorer pref (default on, editor card gear): the editor tab gains
a path-input header and a toggleable right-docked tree panel (per-tab state
in tab.meta.treeOpen; file tabs default closed, the seeded path-less home
tab defaults open). Fresh sessions seed that empty editor tab instead of
the explorer tab when merged mode is on; the standalone explorer tab stays
in the + menu unchanged.
- Extract FileTree.tsx from ExplorerView (shared by both hosts)
- Host fs.search route: recursive case-insensitive name search rooted at
the session cwd, budget-capped (200 matches / 100k visited), skips .git,
never descends symlink dirs
- EditorHost reads the pref reactively (boolean snapshot) and keeps the
pre-merge layout verbatim when off
* feat(settings): select row type for declarative settings; editorExplorer as iconed select
- SidebarSettingToggle gains type 'select' with options {value,title,desc,icon}
and multi (default false; stores the picked values as an array in options
order). Any option carrying an icon renders big-icon option cards in the
dropdown and the icon in the closed anchor; icon-less rows are one-line text.
- SelectRow in SideCardSection rides the primitives Menu (portal); single-pick
commits and closes, multi toggles and stays open. Wired for both host-prefs
toggles and plugin-owned pluginToggles via the new onSelectValue seam.
- The editorExplorer merged-mode pref is now an iconed select (merged vs
separate) instead of a switch; the stored value stays boolean.
- Advertise the capability as 'settingSelect' in SIDEBAR_FEATURES.
* feat(editor): unify explorer into the editor surface, resizable tree dock, hoisted toolbar
- One tree implementation, two presentations: the shared TreePanel (global
name search + refresh + FileTree) docks as the editor tab's right panel
and — in merged mode — fills the explorer tab's window; the pref-off
explorer keeps its legacy header shell.
- The docked panel drag-resizes from its left edge (160-480px clamp),
persisted as tab.meta.treeWidth alongside treeOpen.
- The text editor's preview/edit/save toolbar moves into the path-input
header row: FileViewerProps gains internal toolbar fields ('host' mode
skips the viewer's own row, reports EditorToolbarState and registers
EditorToolbarControls); non-merged mode is byte-identical to before.
* feat(editor): single files window with in-place switching; explorer descriptor removed
- editor tab replaces explorer on the + menu (order 10, Files); persisted
explorer tabs migrate to the editor home tab via sanitizeState
- editorExplorer now only controls open behavior: merged = in-place
updateTab, independent = openSidebarFile; both modes share the merged
chrome (path input + hoisted preview/edit/save toolbar + tree toggle)
- file tree context menu gains 'open in new tab' / 'open to the side'
(mintTabId avoids the id safety net; insertLeafAt splits right)
- tree dock resize uses pointer capture (touch-action none, 6px handle)
- defaults: openByDefault off, sidebar width 35%
- file tree clips horizontal overflow (no sideways scroll)
- e2e: mount lane arms the editor-chunk wait before goto and clicks the
tree row's left edge (hover @-reference button sits mid-row); drag lane
expands the panel first (collapsed push writes 0px), waits for the
slide-in to settle, and picks the leftmost col-resize strip; 'Files'
title assertions require draggable to skip the bottom panel's welcome
cards
* feat(editor): split mode turns the path-less window into the standalone explorer
In independent (split) mode a path-less files window renders ONLY the
tree panel (search + FileTree, full-window form) — no path input, no
hoisted toolbar, no tree toggle. File clicks open per-path editor tabs
(full chrome, tree docked closed by default) through openSidebarFile,
unchanged. Merged mode is untouched (chrome + docked tree, in-place
switching). TreePanel's previously reserved 'full' prop now has its
consumer.
This commit is contained in:
@@ -666,6 +666,8 @@
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
/* Deep trees truncate their labels instead of scrolling horizontally. */
|
||||
overflow-x: hidden;
|
||||
padding: 2px 6px 8px;
|
||||
}
|
||||
|
||||
@@ -677,7 +679,12 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
/* border-box keeps the inline indent padding (depth * 22 + 6) INSIDE the
|
||||
row's 100% width — without it deep rows overflow and force a horizontal
|
||||
scrollbar on the tree body. */
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 34px;
|
||||
padding: 0 8px;
|
||||
border: none;
|
||||
@@ -716,8 +723,12 @@
|
||||
}
|
||||
|
||||
.explorerName {
|
||||
/* min-width: 0 lets the flex item shrink below its content so the label
|
||||
truncates with an ellipsis instead of widening the row. */
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* The hover-revealed @-reference button at the row's far right (and the
|
||||
@@ -799,6 +810,139 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Merged mode (the editorExplorer pref): the header's path input replaces
|
||||
the plain title, and a toggleable file tree docks at the tab's right edge. */
|
||||
.editorPathInput {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--dsw-alias-border-l1);
|
||||
border-radius: 6px;
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font: var(--dsw-font-xxs-12);
|
||||
}
|
||||
|
||||
.editorPathInput:focus {
|
||||
outline: none;
|
||||
border-color: var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
/* The tree-panel toggle in the header: active = panel open (the selected
|
||||
fill of the tab strip). */
|
||||
.editorTreeToggleActive {
|
||||
color: var(--dsw-alias-label-primary);
|
||||
background: var(--dsw-alias-interactive-bg-active);
|
||||
}
|
||||
|
||||
.editorBody {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.editorMain {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* The docked file tree: the wrapper owns the (drag-adjustable) width and
|
||||
the hairline separation; the resize handle is a wider invisible strip on
|
||||
its left edge with a hover affordance. */
|
||||
.editorTreeDock {
|
||||
position: relative;
|
||||
flex: none;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
border-left: 1px solid var(--dsw-alias-border-l1);
|
||||
}
|
||||
|
||||
.editorTreeResize {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
/* Inside the dock (left edge): pointer capture on the handle keeps the
|
||||
drag alive even when the pointer leaves the strip. */
|
||||
left: 0;
|
||||
width: 6px;
|
||||
cursor: col-resize;
|
||||
touch-action: none;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.editorTreeResize:hover {
|
||||
background: var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
.editorTreePanel {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* The tree panel's full-window form (the `full` prop): fill the tab body. */
|
||||
.editorTreePanelFull {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.editorTreeSearch {
|
||||
flex: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 8px;
|
||||
border-bottom: 1px solid var(--dsw-alias-border-l1);
|
||||
}
|
||||
|
||||
.editorSearchInput {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 26px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--dsw-alias-border-l1);
|
||||
border-radius: 6px;
|
||||
background: var(--dsw-alias-bg-layer-1);
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font: var(--dsw-font-xxs-12);
|
||||
}
|
||||
|
||||
.editorSearchInput:focus {
|
||||
outline: none;
|
||||
border-color: var(--dsw-alias-border-l2);
|
||||
}
|
||||
|
||||
.editorSearchHint {
|
||||
padding: 8px 12px;
|
||||
font: var(--dsw-font-xxs-12);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
}
|
||||
|
||||
.editorSearchResult {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 4px 8px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
background: transparent;
|
||||
color: var(--dsw-alias-label-primary);
|
||||
font: var(--dsw-font-xxs-12);
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.editorSearchResult:hover {
|
||||
background: var(--dsw-alias-interactive-bg-hover);
|
||||
}
|
||||
|
||||
.editorStatus {
|
||||
font: var(--dsw-font-xxxs-11);
|
||||
color: var(--dsw-alias-label-tertiary);
|
||||
|
||||
Reference in New Issue
Block a user