Merge pull request #1853 from nesquena/fix/1793-workspace-prefs-kebab
fix(workspace): move 'Show hidden files' toggle into kebab + accent-dot state indicator (#1793)
This commit is contained in:
commit
5005f1c8ba
5 changed files with 319 additions and 13 deletions
|
|
@ -312,6 +312,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'No workspace selected. Set a workspace in Settings \u2192 Workspace to browse files.',
|
||||
workspace_empty_dir: 'This workspace is empty.',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
dialog_confirm_title: 'Confirm action',
|
||||
dialog_prompt_title: 'Enter a value',
|
||||
dialog_confirm_btn: 'Confirm',
|
||||
|
|
@ -1326,6 +1330,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'ワークスペースが選択されていません。設定 → ワークスペースで選択してください。',
|
||||
workspace_empty_dir: 'このワークスペースは空です。',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
dialog_confirm_title: '操作の確認',
|
||||
dialog_prompt_title: '値を入力',
|
||||
dialog_confirm_btn: '確認',
|
||||
|
|
@ -2239,6 +2247,10 @@ const LOCALES = {
|
|||
settings_autosave_retry: 'Повторить',
|
||||
workspace_empty_dir: 'Это рабочее пространство пусто.',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
workspace_empty_no_path: 'Рабочее пространство не выбрано. Настройте его в Настройки → Рабочее пространство.',
|
||||
available_personalities: 'Доступные личности:',
|
||||
personality_switch_hint: '\n\nИспользуйте `/personality <name>` для переключения или `/personality none` для сброса.',
|
||||
|
|
@ -3196,6 +3208,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'No hay espacio de trabajo seleccionado. Configure un espacio de trabajo en Ajustes \u2192 Workspace para explorar archivos.',
|
||||
workspace_empty_dir: 'Este espacio de trabajo está vacío.',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
// workspace.js
|
||||
unsaved_confirm: 'Tienes cambios sin guardar en la vista previa. ¿Descartar y navegar?',
|
||||
save: 'Guardar',
|
||||
|
|
@ -4125,6 +4141,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'Kein Workspace ausgewählt. Wähle einen Workspace unter Einstellungen \u2192 Workspace, um Dateien zu durchsuchen.',
|
||||
workspace_empty_dir: 'Dieser Workspace ist leer.',
|
||||
workspace_show_hidden_files: 'Versteckte Dateien anzeigen',
|
||||
workspace_show_hidden_files_desc: '.DS_Store, .git, node_modules und weitere versteckte / Systemdateien im Dateibaum anzeigen.',
|
||||
workspace_hidden_files_visible: 'versteckt sichtbar',
|
||||
workspace_hidden_files_visible_title: 'Versteckte Dateien sind sichtbar — Klicken für Optionen',
|
||||
workspace_options: 'Arbeitsbereich-Optionen',
|
||||
dialog_confirm_title: 'Aktion bestätigen',
|
||||
dialog_prompt_title: 'Wert eingeben',
|
||||
dialog_confirm_btn: 'Bestätigen',
|
||||
|
|
@ -5069,6 +5089,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: '未选择工作区。请在 设置 → 工作区 中设置工作区以浏览文件。',
|
||||
workspace_empty_dir: '此工作区为空。',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
no_personalities: '\u6ca1\u6709\u627e\u5230\u4eba\u8bbe\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09',
|
||||
available_personalities: '\u53ef\u7528\u4eba\u8bbe\uff1a',
|
||||
personality_switch_hint: '\n\n\u4f7f\u7528 `/personality <name>` \u5207\u6362\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002',
|
||||
|
|
@ -5972,6 +5996,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: '未選擇工作區。請在 設定 → 工作區 中設定工作區以瀏覽檔案。',
|
||||
workspace_empty_dir: '此工作區為空。',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
no_personalities: '\u6c92\u6709\u627e\u5230\u4eba\u8a2d\uff08\u53ef\u6dfb\u52a0\u5230 ~/.hermes/personalities/\uff09',
|
||||
available_personalities: '\u53ef\u7528\u4eba\u8a2d\uff1a',
|
||||
personality_switch_hint: '\n\n\u4f7f\u7528 `/personality <name>` \u5207\u63db\uff0c\u6216\u7528 `/personality none` \u6e05\u7a7a\u3002',
|
||||
|
|
@ -7025,6 +7053,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'Nenhum workspace selecionado. Configure em Configurações → Workspace.',
|
||||
workspace_empty_dir: 'Este workspace está vazio.',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
dialog_confirm_title: 'Confirmar ação',
|
||||
dialog_prompt_title: 'Digite um valor',
|
||||
dialog_confirm_btn: 'Confirmar',
|
||||
|
|
@ -7922,6 +7954,10 @@ const LOCALES = {
|
|||
workspace_empty_no_path: 'No workspace selected. Set a workspace in Settings \u2192 Workspace to browse files.',
|
||||
workspace_empty_dir: 'This workspace is empty.',
|
||||
workspace_show_hidden_files: 'Show hidden files',
|
||||
workspace_show_hidden_files_desc: 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.',
|
||||
workspace_hidden_files_visible: 'hidden visible',
|
||||
workspace_hidden_files_visible_title: 'Hidden files are visible — click for options',
|
||||
workspace_options: 'Workspace options',
|
||||
dialog_confirm_title: 'Confirm action',
|
||||
dialog_prompt_title: 'Enter a value',
|
||||
dialog_confirm_btn: 'Confirm',
|
||||
|
|
|
|||
|
|
@ -1118,7 +1118,7 @@
|
|||
<aside class="rightpanel">
|
||||
<div class="resize-handle" id="rightpanelResize"></div>
|
||||
<div class="panel-header">
|
||||
<span id="workspacePanelHeading" class="workspace-panel-heading" role="button" tabindex="0" title="Workspace root">Workspace</span>
|
||||
<span id="workspacePanelHeading" class="workspace-panel-heading" role="button" tabindex="0" title="Workspace root">Workspace</span><span id="workspaceHiddenIndicator" class="workspace-hidden-indicator" data-i18n-title="workspace_hidden_files_visible_title" title="Hidden files are visible — click for options" hidden onclick="toggleWorkspacePrefsMenu(event)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg><span data-i18n="workspace_hidden_files_visible">hidden visible</span></span>
|
||||
<span class="git-badge" id="gitBadge" style="display:none"></span>
|
||||
<div class="panel-actions">
|
||||
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnCollapseWorkspacePanel" data-tooltip="Hide workspace panel" onclick="toggleWorkspacePanel(false)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||||
|
|
@ -1126,15 +1126,12 @@
|
|||
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnNewFile" data-tooltip="New file" onclick="promptNewFile()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></button>
|
||||
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnNewFolder" data-tooltip="New folder" onclick="promptNewFolder()"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg></button>
|
||||
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnRefreshPanel" data-tooltip="Refresh" onclick="if(S.session)loadDir(S.currentDir)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg></button>
|
||||
<button class="panel-icon-btn has-tooltip has-tooltip--bottom" id="btnWorkspacePrefs" data-tooltip="Workspace options" data-i18n-title="workspace_options" aria-haspopup="true" aria-expanded="false" onclick="toggleWorkspacePrefsMenu(event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="5" r="1.5"/><circle cx="12" cy="12" r="1.5"/><circle cx="12" cy="19" r="1.5"/></svg><span class="workspace-prefs-dot" id="workspacePrefsDot" hidden></span></button>
|
||||
<button class="panel-icon-btn close-preview has-tooltip has-tooltip--bottom" id="btnClearPreview" data-tooltip="Close preview"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
|
||||
<button class="panel-icon-btn mobile-close-btn" onclick="handleWorkspaceClose()" title="Close" aria-label="Close workspace panel">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="breadcrumb-bar" id="breadcrumbBar" style="display:none"></div>
|
||||
<label class="workspace-hidden-toggle" title="Show hidden and system files">
|
||||
<input type="checkbox" id="workspaceShowHiddenFiles" onchange="toggleWorkspaceHiddenFiles(this.checked)">
|
||||
<span data-i18n="workspace_show_hidden_files">Show hidden files</span>
|
||||
</label>
|
||||
<div class="file-tree" id="fileTree"></div>
|
||||
<div id="wsEmptyState" style="display:none;flex:1;align-items:center;justify-content:center;padding:24px 16px;text-align:center;color:var(--muted);font-size:12px;line-height:1.6"></div>
|
||||
<div class="preview-area" id="previewArea">
|
||||
|
|
|
|||
|
|
@ -1187,9 +1187,39 @@
|
|||
.close-preview{cursor:pointer;opacity:.6;}.close-preview:hover{opacity:1;}
|
||||
/* Breadcrumb navigation */
|
||||
.breadcrumb-bar{display:flex;align-items:center;gap:2px;padding:6px 12px;font-size:12px;border-bottom:1px solid var(--border);flex-shrink:0;overflow:hidden;white-space:nowrap;}
|
||||
.workspace-hidden-toggle{display:flex;align-items:center;gap:7px;padding:6px 12px;border-bottom:1px solid var(--border);color:var(--muted);font-size:11px;line-height:1.2;user-select:none;cursor:pointer;}
|
||||
.workspace-hidden-toggle:hover{color:var(--text);background:var(--hover-bg);}
|
||||
.workspace-hidden-toggle input{width:13px;height:13px;margin:0;accent-color:var(--accent-text);}
|
||||
/* ── Workspace prefs kebab menu (#1793) ─────────────────────────── */
|
||||
/* Replaces the always-visible "Show hidden files" inline row that used
|
||||
to sit below the breadcrumb. The toggle now lives behind the kebab
|
||||
button in the panel-actions row; the small indicator next to the
|
||||
panel heading flags the non-default "hidden files visible" state. */
|
||||
.workspace-hidden-indicator{display:inline-flex;align-items:center;gap:4px;margin-left:6px;padding:2px 7px 2px 6px;background:var(--hover-bg);color:var(--muted);font-size:10px;line-height:1.3;border-radius:8px;cursor:pointer;user-select:none;font-weight:600;letter-spacing:.02em;text-transform:none;flex-shrink:0;transition:background .12s,color .12s;border:1px solid var(--border);}
|
||||
.workspace-hidden-indicator:hover{background:var(--accent-bg);color:var(--text);border-color:var(--accent-bg-strong,var(--border2));}
|
||||
.workspace-hidden-indicator[hidden]{display:none;}
|
||||
.workspace-hidden-indicator svg{flex-shrink:0;opacity:.85;}
|
||||
/* On narrow workspace panels (the default 300px width), hide the chip
|
||||
entirely — the kebab dot (rendered absolute over the kebab icon) is
|
||||
enough of a "non-default state" signal and costs zero horizontal
|
||||
space. The tooltip on the kebab itself still labels what's going on.
|
||||
On wider panels (resized by the user, or some future layout that
|
||||
gives the panel more room), the full chip with text appears alongside
|
||||
the heading. Uses the existing `rightpanel` container declared on
|
||||
.rightpanel. */
|
||||
@container rightpanel (max-width: 420px){
|
||||
.workspace-hidden-indicator{display:none!important;}
|
||||
}
|
||||
/* Small accent dot on the kebab button when a non-default pref is on */
|
||||
#btnWorkspacePrefs{position:relative;}
|
||||
#btnWorkspacePrefs .workspace-prefs-dot{position:absolute;top:3px;right:3px;width:6px;height:6px;border-radius:50%;background:var(--accent-text);box-shadow:0 0 0 1.5px var(--surface);pointer-events:none;}
|
||||
#btnWorkspacePrefs .workspace-prefs-dot[hidden]{display:none;}
|
||||
#btnWorkspacePrefs.active{background:var(--accent-bg);color:var(--text);}
|
||||
/* Floating menu — pattern shared with .session-action-menu but scoped */
|
||||
.workspace-prefs-menu{display:block;position:fixed;left:0;top:0;min-width:240px;max-width:min(280px,calc(100vw - 16px));background:var(--surface);border:1px solid var(--border2);border-radius:10px;box-shadow:0 8px 28px rgba(0,0,0,.32);z-index:999;overflow:hidden;}
|
||||
.workspace-prefs-item{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;color:var(--text);cursor:pointer;font-size:13px;line-height:1.35;user-select:none;transition:background .12s;}
|
||||
.workspace-prefs-item:hover{background:var(--hover-bg);}
|
||||
.workspace-prefs-item input{flex-shrink:0;width:14px;height:14px;margin:2px 0 0;accent-color:var(--accent-text);cursor:pointer;}
|
||||
.workspace-prefs-item .workspace-prefs-copy{display:flex;flex-direction:column;gap:2px;min-width:0;}
|
||||
.workspace-prefs-item .workspace-prefs-name{font-weight:500;}
|
||||
.workspace-prefs-item .workspace-prefs-meta{font-size:11px;color:var(--muted);line-height:1.35;opacity:.85;}
|
||||
.breadcrumb-seg{padding:1px 3px;border-radius:3px;}
|
||||
.breadcrumb-link{color:var(--muted);cursor:pointer;transition:color .12s;}
|
||||
.breadcrumb-link:hover{color:var(--text);background:var(--hover-bg);}
|
||||
|
|
|
|||
102
static/ui.js
102
static/ui.js
|
|
@ -6112,6 +6112,19 @@ function _visibleWorkspaceEntries(entries){
|
|||
function _syncWorkspaceHiddenToggle(){
|
||||
const el=$('workspaceShowHiddenFiles');
|
||||
if(el)el.checked=!!S.showHiddenWorkspaceFiles;
|
||||
// Reflect "hidden files are visible" state on the panel heading + kebab dot,
|
||||
// so users can see they've flipped a non-default workspace pref without
|
||||
// having to open the menu. The menu itself stays out of the way otherwise.
|
||||
const ind=$('workspaceHiddenIndicator');
|
||||
if(ind){
|
||||
if(S.showHiddenWorkspaceFiles){ ind.hidden=false; ind.removeAttribute('hidden'); }
|
||||
else { ind.hidden=true; ind.setAttribute('hidden',''); }
|
||||
}
|
||||
const dot=$('workspacePrefsDot');
|
||||
if(dot){
|
||||
if(S.showHiddenWorkspaceFiles){ dot.hidden=false; dot.removeAttribute('hidden'); }
|
||||
else { dot.hidden=true; dot.setAttribute('hidden',''); }
|
||||
}
|
||||
}
|
||||
function toggleWorkspaceHiddenFiles(value){
|
||||
S.showHiddenWorkspaceFiles=!!value;
|
||||
|
|
@ -6120,6 +6133,95 @@ function toggleWorkspaceHiddenFiles(value){
|
|||
renderFileTree();
|
||||
}
|
||||
try{S.showHiddenWorkspaceFiles=localStorage.getItem('hermes-workspace-show-hidden-files')==='1';}catch(_){}
|
||||
|
||||
// ── Workspace preferences kebab menu (#1793 UX refinement) ───────────────
|
||||
// The "Show hidden files" toggle used to live as a permanent inline row
|
||||
// below the breadcrumb bar. That ate ~32px of vertical space on every
|
||||
// panel view (root, subdir, file preview), even though the toggle is a
|
||||
// set-once preference — most users flip it once or never. Moving the
|
||||
// control into a kebab dropdown reclaims the space; the small "(hidden
|
||||
// files visible)" indicator on the heading reflects the non-default state
|
||||
// so the affordance isn't lost.
|
||||
let _workspacePrefsMenu = null;
|
||||
let _workspacePrefsAnchor = null;
|
||||
function _closeWorkspacePrefsMenu(){
|
||||
if(_workspacePrefsMenu){ _workspacePrefsMenu.remove(); _workspacePrefsMenu=null; }
|
||||
if(_workspacePrefsAnchor){
|
||||
_workspacePrefsAnchor.classList.remove('active');
|
||||
_workspacePrefsAnchor.setAttribute('aria-expanded','false');
|
||||
_workspacePrefsAnchor=null;
|
||||
}
|
||||
}
|
||||
function _positionWorkspacePrefsMenu(anchorEl){
|
||||
if(!_workspacePrefsMenu||!anchorEl) return;
|
||||
const rect=anchorEl.getBoundingClientRect();
|
||||
const menuW=Math.min(260, Math.max(220, _workspacePrefsMenu.scrollWidth||220));
|
||||
let left=rect.right-menuW;
|
||||
if(left<8) left=8;
|
||||
if(left+menuW>window.innerWidth-8) left=window.innerWidth-menuW-8;
|
||||
let top=rect.bottom+6;
|
||||
const menuH=_workspacePrefsMenu.offsetHeight||0;
|
||||
if(top+menuH>window.innerHeight-8 && rect.top>menuH+12) top=rect.top-menuH-6;
|
||||
if(top<8) top=8;
|
||||
_workspacePrefsMenu.style.left=left+'px';
|
||||
_workspacePrefsMenu.style.top=top+'px';
|
||||
}
|
||||
function _buildWorkspacePrefsMenu(){
|
||||
const menu=document.createElement('div');
|
||||
menu.className='workspace-prefs-menu open';
|
||||
menu.setAttribute('role','menu');
|
||||
// The checkbox keeps id="workspaceShowHiddenFiles" so existing call
|
||||
// sites (and the existing test_issue1793_file_tree_cruft_filter test)
|
||||
// can find it the same way as before. Only the parent container moves.
|
||||
const labelTxt = (typeof t==='function' ? t('workspace_show_hidden_files') : 'Show hidden files');
|
||||
const descTxt = (typeof t==='function' ? t('workspace_show_hidden_files_desc') : 'Include .DS_Store, .git, node_modules, and other hidden / system files in the file tree.');
|
||||
const row=document.createElement('label');
|
||||
row.className='workspace-prefs-item';
|
||||
row.setAttribute('role','menuitemcheckbox');
|
||||
row.innerHTML=
|
||||
'<input type="checkbox" id="workspaceShowHiddenFiles" '+
|
||||
'onchange="toggleWorkspaceHiddenFiles(this.checked)">'+
|
||||
'<span class="workspace-prefs-copy">'+
|
||||
'<span class="workspace-prefs-name">'+esc(labelTxt)+'</span>'+
|
||||
'<span class="workspace-prefs-meta">'+esc(descTxt)+'</span>'+
|
||||
'</span>';
|
||||
const cb=row.querySelector('input');
|
||||
if(cb) cb.checked=!!S.showHiddenWorkspaceFiles;
|
||||
menu.appendChild(row);
|
||||
return menu;
|
||||
}
|
||||
function toggleWorkspacePrefsMenu(e){
|
||||
if(e&&e.preventDefault) e.preventDefault();
|
||||
if(e&&e.stopPropagation) e.stopPropagation();
|
||||
// Anchor preference: the kebab button. The indicator chip can also open
|
||||
// the same menu (click on "(hidden visible)"), but anchor positioning
|
||||
// always references the kebab so the menu lands in the same place.
|
||||
const anchor=$('btnWorkspacePrefs')||(e&&e.currentTarget)||null;
|
||||
if(_workspacePrefsMenu&&_workspacePrefsAnchor===anchor){ _closeWorkspacePrefsMenu(); return; }
|
||||
_closeWorkspacePrefsMenu();
|
||||
const menu=_buildWorkspacePrefsMenu();
|
||||
document.body.appendChild(menu);
|
||||
_workspacePrefsMenu=menu;
|
||||
_workspacePrefsAnchor=anchor;
|
||||
if(anchor){ anchor.classList.add('active'); anchor.setAttribute('aria-expanded','true'); }
|
||||
_positionWorkspacePrefsMenu(anchor);
|
||||
}
|
||||
document.addEventListener('click',e=>{
|
||||
if(!_workspacePrefsMenu) return;
|
||||
if(_workspacePrefsMenu.contains(e.target)) return;
|
||||
if(_workspacePrefsAnchor&&_workspacePrefsAnchor.contains(e.target)) return;
|
||||
// Indicator chip is also an opener — clicking it should toggle, not close.
|
||||
const ind=$('workspaceHiddenIndicator');
|
||||
if(ind&&ind.contains(e.target)) return;
|
||||
_closeWorkspacePrefsMenu();
|
||||
});
|
||||
document.addEventListener('keydown',e=>{
|
||||
if(e.key==='Escape'&&_workspacePrefsMenu) _closeWorkspacePrefsMenu();
|
||||
});
|
||||
window.addEventListener('resize',()=>{
|
||||
if(_workspacePrefsMenu&&_workspacePrefsAnchor) _positionWorkspacePrefsMenu(_workspacePrefsAnchor);
|
||||
});
|
||||
|
||||
if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',_syncWorkspaceHiddenToggle);
|
||||
else _syncWorkspaceHiddenToggle();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,47 @@
|
|||
"""Regression coverage for #1793 — workspace file-tree cruft filter.
|
||||
|
||||
Original v0.51.21 work added an inline "Show hidden files" toggle that sat
|
||||
permanently between the breadcrumb and the file tree, eating ~32px of
|
||||
vertical space on every panel view (root, subdir, file preview).
|
||||
|
||||
Follow-up UX refinement (this commit) moves the toggle behind a kebab
|
||||
dropdown in the panel-actions row and surfaces the non-default
|
||||
"hidden-files-visible" state via a small indicator next to the panel
|
||||
heading. The original filtering behavior is unchanged; only the affordance
|
||||
shape moved.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
INDEX_HTML = (ROOT / "static" / "index.html").read_text(encoding="utf-8")
|
||||
UI_JS = (ROOT / "static" / "ui.js").read_text(encoding="utf-8")
|
||||
STYLE_CSS = (ROOT / "static" / "style.css").read_text(encoding="utf-8")
|
||||
I18N_JS = (ROOT / "static" / "i18n.js").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
# ── Original filtering behavior (must stay green) ────────────────────────
|
||||
|
||||
|
||||
def test_workspace_panel_has_show_hidden_files_toggle():
|
||||
"""File-tree cruft must be recoverable via an explicit user toggle."""
|
||||
assert 'id="workspaceShowHiddenFiles"' in INDEX_HTML
|
||||
"""File-tree cruft must be recoverable via an explicit user toggle.
|
||||
|
||||
The toggle now lives behind the kebab; the checkbox itself is built by
|
||||
`_buildWorkspacePrefsMenu` in ui.js (so it's literally referenced there
|
||||
by id), but the existing call site in i18n still resolves the localized
|
||||
label.
|
||||
"""
|
||||
assert "toggleWorkspaceHiddenFiles" in UI_JS
|
||||
assert 'id="workspaceShowHiddenFiles"' in UI_JS # built dynamically; id preserved
|
||||
assert "workspace_show_hidden_files" in I18N_JS
|
||||
|
||||
|
||||
def test_file_tree_filters_common_cruft_by_default():
|
||||
"""macOS/Windows/VCS/cache noise should not render by default."""
|
||||
assert "WORKSPACE_HIDDEN_FILE_NAMES" in UI_JS
|
||||
for name in [".DS_Store", "Thumbs.db", "Desktop.ini", ".git", "__pycache__", "node_modules"]:
|
||||
for name in [".DS_Store", "Thumbs.db", "Desktop.ini", ".git",
|
||||
"__pycache__", "node_modules"]:
|
||||
assert name in UI_JS
|
||||
assert "_visibleWorkspaceEntries" in UI_JS
|
||||
assert "S.showHiddenWorkspaceFiles" in UI_JS
|
||||
|
|
@ -27,5 +51,122 @@ def test_file_tree_filters_common_cruft_by_default():
|
|||
def test_hidden_file_toggle_invalidates_tree_render_without_refetch():
|
||||
"""The toggle should re-render cached entries instead of changing workspace state."""
|
||||
assert "function toggleWorkspaceHiddenFiles" in UI_JS
|
||||
assert "renderFileTree()" in UI_JS[UI_JS.index("function toggleWorkspaceHiddenFiles"):]
|
||||
assert "localStorage.setItem('hermes-workspace-show-hidden-files'" in UI_JS
|
||||
body_start = UI_JS.index("function toggleWorkspaceHiddenFiles")
|
||||
body_end = UI_JS.index("\n}", body_start)
|
||||
body = UI_JS[body_start:body_end]
|
||||
assert "renderFileTree()" in body
|
||||
assert "localStorage.setItem('hermes-workspace-show-hidden-files'" in body
|
||||
|
||||
|
||||
# ── Kebab-affordance UX refinement ───────────────────────────────────────
|
||||
|
||||
|
||||
def test_no_inline_workspace_hidden_toggle_row():
|
||||
"""The always-on inline `<label class="workspace-hidden-toggle">` row
|
||||
must be gone — it ate vertical space below the breadcrumb on every
|
||||
panel view. Toggle now lives behind the kebab.
|
||||
"""
|
||||
assert "workspace-hidden-toggle" not in INDEX_HTML, (
|
||||
"inline hidden-files row should have been removed in favor of the "
|
||||
"kebab menu (#1793 follow-up)"
|
||||
)
|
||||
# CSS for the inline row should also be gone — leaving stale rules
|
||||
# invites future drift where someone re-adds the row and it picks up
|
||||
# accidental styling.
|
||||
assert ".workspace-hidden-toggle" not in STYLE_CSS
|
||||
|
||||
|
||||
def test_panel_actions_row_has_workspace_prefs_kebab():
|
||||
"""A kebab button (`btnWorkspacePrefs`) must exist in the workspace
|
||||
panel actions row to expose the menu.
|
||||
"""
|
||||
assert 'id="btnWorkspacePrefs"' in INDEX_HTML
|
||||
assert 'onclick="toggleWorkspacePrefsMenu(event)"' in INDEX_HTML
|
||||
# Tooltip is i18n-aware
|
||||
assert 'data-i18n-title="workspace_options"' in INDEX_HTML
|
||||
# Kebab carries an accent dot for non-default state
|
||||
assert 'id="workspacePrefsDot"' in INDEX_HTML
|
||||
|
||||
|
||||
def test_panel_heading_has_hidden_files_indicator():
|
||||
"""The non-default "hidden files visible" state must surface as a small
|
||||
indicator next to the WORKSPACE heading so users don't forget they
|
||||
flipped the pref. Hidden by default via the `hidden` attribute.
|
||||
"""
|
||||
assert 'id="workspaceHiddenIndicator"' in INDEX_HTML
|
||||
# The indicator opens the same menu when clicked (no separate code path)
|
||||
block = INDEX_HTML[INDEX_HTML.index('id="workspaceHiddenIndicator"'):]
|
||||
block = block[: block.index("</span>") + 7]
|
||||
assert "toggleWorkspacePrefsMenu" in block
|
||||
# Default-hidden so the chip doesn't clutter normal state
|
||||
assert " hidden " in block or block.rstrip().endswith("hidden")
|
||||
|
||||
|
||||
def test_kebab_menu_javascript_exists():
|
||||
"""The dropdown must be self-contained: open/close/position handlers
|
||||
follow the canonical floating-menu pattern from
|
||||
`_openSessionActionMenu`.
|
||||
"""
|
||||
assert "function toggleWorkspacePrefsMenu" in UI_JS
|
||||
assert "function _buildWorkspacePrefsMenu" in UI_JS
|
||||
assert "function _closeWorkspacePrefsMenu" in UI_JS
|
||||
assert "function _positionWorkspacePrefsMenu" in UI_JS
|
||||
# Built menu still contains the canonical input id so existing call
|
||||
# sites and the toggle test above keep working.
|
||||
build_start = UI_JS.index("function _buildWorkspacePrefsMenu")
|
||||
build_end = UI_JS.index("\n}", build_start)
|
||||
build_body = UI_JS[build_start:build_end]
|
||||
assert 'id="workspaceShowHiddenFiles"' in build_body
|
||||
|
||||
|
||||
def test_kebab_menu_closes_on_escape_and_outside_click():
|
||||
"""Standard keyboard / click-out close behavior."""
|
||||
# Escape closes
|
||||
assert "Escape" in UI_JS and "_closeWorkspacePrefsMenu" in UI_JS
|
||||
# Outside-click close listener
|
||||
assert "_workspacePrefsMenu" in UI_JS
|
||||
assert "if(_workspacePrefsMenu.contains(e.target)) return" in UI_JS
|
||||
|
||||
|
||||
def test_indicator_reflects_localStorage_state_on_load():
|
||||
"""`_syncWorkspaceHiddenToggle` must drive both the dropdown checkbox
|
||||
AND the indicator/dot so a page reload with the pref ON shows the
|
||||
"hidden visible" indicator without the user having to open the menu.
|
||||
"""
|
||||
sync_start = UI_JS.index("function _syncWorkspaceHiddenToggle")
|
||||
sync_end = UI_JS.index("\n}", sync_start)
|
||||
body = UI_JS[sync_start:sync_end]
|
||||
assert "workspaceHiddenIndicator" in body
|
||||
assert "workspacePrefsDot" in body
|
||||
# Drives the existing checkbox if it's mounted
|
||||
assert "workspaceShowHiddenFiles" in body
|
||||
|
||||
|
||||
def test_kebab_menu_styles_replace_inline_row():
|
||||
"""CSS must define the kebab dot, indicator, and floating menu — but
|
||||
not the legacy inline-row styling (the test above pins removal).
|
||||
"""
|
||||
assert ".workspace-prefs-menu{" in STYLE_CSS
|
||||
assert ".workspace-prefs-item{" in STYLE_CSS
|
||||
assert ".workspace-hidden-indicator{" in STYLE_CSS
|
||||
assert "#btnWorkspacePrefs" in STYLE_CSS
|
||||
|
||||
|
||||
def test_new_i18n_keys_present_in_all_locales():
|
||||
"""The new copy must exist in every locale block so the kebab menu
|
||||
description and indicator chip don't render `undefined` in non-en
|
||||
sessions.
|
||||
"""
|
||||
# Total locale blocks today: 9 (en, ja, ru, es, de, zh, zh-Hant, pt, ko)
|
||||
n_locales = I18N_JS.count("workspace_show_hidden_files:")
|
||||
assert n_locales >= 8, f"unexpected locale count: {n_locales}"
|
||||
for key in (
|
||||
"workspace_show_hidden_files_desc:",
|
||||
"workspace_hidden_files_visible:",
|
||||
"workspace_hidden_files_visible_title:",
|
||||
"workspace_options:",
|
||||
):
|
||||
assert I18N_JS.count(key) == n_locales, (
|
||||
f"key {key!r} missing in some locales (expected {n_locales}, "
|
||||
f"got {I18N_JS.count(key)})"
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue