diff --git a/locales/de.json b/locales/de.json index 7525a2ba..71192ce0 100644 --- a/locales/de.json +++ b/locales/de.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "Stammverzeichnis", + "moreOptions": "Weitere Optionen", "collapseAll": "Alle Ordner einklappen", "pinSidebar": "Sidebar anheften", "unpinSidebar": "Sidebar lösen", + "hideOnThisPage": "Seitenleiste auf dieser Seite ausblenden", + "showSidebar": "Seitenleiste anzeigen", + "sidebarHiddenNotification": "Seitenleiste auf der Seite {page} ausgeblendet", "switchToListView": "Zur Listenansicht wechseln", "switchToTreeView": "Zur Baumansicht wechseln", "recursiveOn": "Unterordner einbeziehen", diff --git a/locales/en.json b/locales/en.json index a22db9c6..e563bb75 100644 --- a/locales/en.json +++ b/locales/en.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "Root", + "moreOptions": "More options", "collapseAll": "Collapse All Folders", "pinSidebar": "Pin Sidebar", "unpinSidebar": "Unpin Sidebar", + "hideOnThisPage": "Hide sidebar on this page", + "showSidebar": "Show sidebar", + "sidebarHiddenNotification": "Folder sidebar hidden on {page} page", "switchToListView": "Switch to List View", "switchToTreeView": "Switch to Tree View", "recursiveOn": "Include subfolders", diff --git a/locales/es.json b/locales/es.json index 0b76dce8..ed36d0e5 100644 --- a/locales/es.json +++ b/locales/es.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "Raíz", + "moreOptions": "Más opciones", "collapseAll": "Colapsar todas las carpetas", "pinSidebar": "Fijar barra lateral", "unpinSidebar": "Desfijar barra lateral", + "hideOnThisPage": "Ocultar barra lateral en esta página", + "showSidebar": "Mostrar barra lateral", + "sidebarHiddenNotification": "Barra lateral oculta en la página {page}", "switchToListView": "Cambiar a vista de lista", "switchToTreeView": "Cambiar a vista de árbol", "recursiveOn": "Incluir subcarpetas", diff --git a/locales/fr.json b/locales/fr.json index a8e72a54..847f4ddb 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "Racine", + "moreOptions": "Plus d'options", "collapseAll": "Réduire tous les dossiers", "pinSidebar": "Épingler la barre latérale", "unpinSidebar": "Désépingler la barre latérale", + "hideOnThisPage": "Masquer la barre latérale sur cette page", + "showSidebar": "Afficher la barre latérale", + "sidebarHiddenNotification": "Barre latérale masquée sur la page {page}", "switchToListView": "Passer en vue liste", "switchToTreeView": "Passer en vue arborescence", "recursiveOn": "Inclure les sous-dossiers", diff --git a/locales/he.json b/locales/he.json index 0d05baf7..38369163 100644 --- a/locales/he.json +++ b/locales/he.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "שורש", + "moreOptions": "אפשרויות נוספות", "collapseAll": "כווץ את כל התיקיות", "pinSidebar": "נעל סרגל צד", "unpinSidebar": "שחרר סרגל צד", + "hideOnThisPage": "הסתר סרגל צד בדף זה", + "showSidebar": "הצג סרגל צד", + "sidebarHiddenNotification": "סרגל הצד מוסתר בדף {page}", "switchToListView": "עבור לתצוגת רשימה", "switchToTreeView": "תצוגת עץ", "recursiveOn": "כלול תיקיות משנה", diff --git a/locales/ja.json b/locales/ja.json index 1e54e4a4..7ac682e1 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "ルート", + "moreOptions": "その他のオプション", "collapseAll": "すべてのフォルダを折りたたむ", "pinSidebar": "サイドバーを固定", "unpinSidebar": "サイドバーの固定を解除", + "hideOnThisPage": "このページでサイドバーを非表示", + "showSidebar": "サイドバーを表示", + "sidebarHiddenNotification": "{page}ページでサイドバーが非表示になっています", "switchToListView": "リストビューに切り替え", "switchToTreeView": "ツリー表示に切り替え", "recursiveOn": "サブフォルダーを含める", diff --git a/locales/ko.json b/locales/ko.json index 7a2b7d75..b1c8baee 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "루트", + "moreOptions": "더 많은 옵션", "collapseAll": "모든 폴더 접기", "pinSidebar": "사이드바 고정", "unpinSidebar": "사이드바 고정 해제", + "hideOnThisPage": "이 페이지에서 사이드바 숨기기", + "showSidebar": "사이드바 표시", + "sidebarHiddenNotification": "{page} 페이지에서 사이드바가 숨겨져 있습니다", "switchToListView": "목록 보기로 전환", "switchToTreeView": "트리 보기로 전환", "recursiveOn": "하위 폴더 포함", diff --git a/locales/ru.json b/locales/ru.json index f4262164..31040b00 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "Корень", + "moreOptions": "Дополнительные параметры", "collapseAll": "Свернуть все папки", "pinSidebar": "Закрепить боковую панель", "unpinSidebar": "Открепить боковую панель", + "hideOnThisPage": "Скрыть боковую панель на этой странице", + "showSidebar": "Показать боковую панель", + "sidebarHiddenNotification": "Боковая панель скрыта на странице {page}", "switchToListView": "Переключить на вид списка", "switchToTreeView": "Переключить на древовидный вид", "recursiveOn": "Включать вложенные папки", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index de4380e6..f87d4c92 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "根目录", + "moreOptions": "更多选项", "collapseAll": "折叠所有文件夹", "pinSidebar": "固定侧边栏", "unpinSidebar": "取消固定侧边栏", + "hideOnThisPage": "隐藏此页面侧边栏", + "showSidebar": "显示侧边栏", + "sidebarHiddenNotification": "{page}页面的文件夹侧边栏已隐藏", "switchToListView": "切换到列表视图", "switchToTreeView": "切换到树状视图", "recursiveOn": "包含子文件夹", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 247e4c89..ba596dd8 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -953,9 +953,13 @@ }, "sidebar": { "modelRoot": "根目錄", + "moreOptions": "更多選項", "collapseAll": "全部摺疊資料夾", "pinSidebar": "固定側邊欄", "unpinSidebar": "取消固定側邊欄", + "hideOnThisPage": "隱藏此頁面側邊欄", + "showSidebar": "顯示側邊欄", + "sidebarHiddenNotification": "{page}頁面的資料夾側邊欄已隱藏", "switchToListView": "切換至列表檢視", "switchToTreeView": "切換到樹狀檢視", "recursiveOn": "包含子資料夾", diff --git a/static/css/components/sidebar.css b/static/css/components/sidebar.css index 11158500..65e4a87f 100644 --- a/static/css/components/sidebar.css +++ b/static/css/components/sidebar.css @@ -84,6 +84,7 @@ border-bottom: 1px solid var(--border-color); cursor: pointer; transition: var(--transition-base); + position: relative; } .sidebar-header:hover { @@ -150,6 +151,120 @@ display: none; } +/* ===== Sidebar More Options Dropdown ===== */ +.sidebar-more-dropdown { + position: absolute; + top: 100%; + right: 8px; + min-width: 190px; + background: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-xs); + box-shadow: var(--shadow-lg); + z-index: calc(var(--z-overlay) + 20); + display: none; + overflow: hidden; + margin-top: 2px; +} + +.sidebar-more-dropdown.open { + display: block; + animation: dropdownFadeIn 0.15s ease; +} + +@keyframes dropdownFadeIn { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} + +.sidebar-dropdown-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + cursor: pointer; + font-size: 0.85em; + color: var(--text-color); + transition: var(--transition-base); + white-space: nowrap; +} + +.sidebar-dropdown-item:hover { + background: var(--lora-surface); +} + +.sidebar-dropdown-item i { + width: 16px; + text-align: center; + color: var(--text-muted); + font-size: 0.9em; + flex-shrink: 0; +} + +.sidebar-dropdown-item:hover i { + color: var(--text-color); +} + +.sidebar-dropdown-item.disabled { + opacity: 0.4; + pointer-events: none; +} + +/* ===== Sidebar Hidden Indicator (left edge) ===== */ +.sidebar-hidden-indicator { + position: fixed; + left: 0; + top: 50%; + transform: translateY(-50%); + z-index: var(--z-overlay); + width: 14px; + height: 44px; + display: flex; + align-items: center; + justify-content: center; + background: var(--border-color); + opacity: 0.3; + border-radius: 0 4px 4px 0; + cursor: pointer; + transition: opacity 0.15s ease, background 0.15s ease; +} + +.sidebar-hidden-indicator:hover { + opacity: 0.7; + background: var(--lora-accent); +} + +.sidebar-hidden-indicator i { + font-size: 9px; + color: var(--text-muted); + transition: color 0.15s ease; +} + +.sidebar-hidden-indicator:hover i { + color: white; +} + +.sidebar-hidden-indicator-tooltip { + position: absolute; + left: 100%; + top: 50%; + transform: translateY(-50%); + margin-left: 8px; + padding: 4px 10px; + background: var(--text-color); + color: var(--bg-color); + font-size: 0.8em; + border-radius: 4px; + white-space: nowrap; + pointer-events: none; + opacity: 0; + transition: opacity 0.15s ease; +} + +.sidebar-hidden-indicator:hover .sidebar-hidden-indicator-tooltip { + opacity: 1; +} + .sidebar-content { flex: 1; overflow: hidden; diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js index d5371650..8d4e27bf 100644 --- a/static/js/components/SidebarManager.js +++ b/static/js/components/SidebarManager.js @@ -36,6 +36,8 @@ export class SidebarManager { this.currentDropTarget = null; this.lastPageControls = null; this.isDisabledBySetting = false; + this.isDisabledByPage = false; + this.isMoreDropdownOpen = false; this.initializationPromise = null; this.isCreatingFolder = false; this._pendingDragState = null; // 用于保存拖拽创建文件夹时的状态 @@ -68,6 +70,10 @@ export class SidebarManager { this.handleSidebarDrop = this.handleSidebarDrop.bind(this); this.handleCreateFolderSubmit = this.handleCreateFolderSubmit.bind(this); this.handleCreateFolderCancel = this.handleCreateFolderCancel.bind(this); + this.handleMoreToggle = this.handleMoreToggle.bind(this); + this.handleMoreDropdownItemClick = this.handleMoreDropdownItemClick.bind(this); + this.handleDocumentClickForMore = this.handleDocumentClickForMore.bind(this); + this.getPageDisplayName = this.getPageDisplayName.bind(this); } setHostPageControls(pageControls) { @@ -100,6 +106,8 @@ export class SidebarManager { this.initializeDragAndDrop(); this.updateSidebarTitle(); this.restoreSidebarState(); + // Re-apply DOM visibility now that per-page state is known + this.updateDomVisibility(!this.isDisabledBySetting); await this.loadFolderTree(); if (this.isDisabledBySetting && !forceInitialize) { this.cleanup(); @@ -143,6 +151,13 @@ export class SidebarManager { this.sidebarDragHandlersInitialized = false; } + const moreDropdown = document.getElementById('sidebarMoreDropdown'); + if (moreDropdown) { + moreDropdown.classList.remove('open'); + } + this.isMoreDropdownOpen = false; + this.hideSidebarHiddenIndicator(); + // Reset state this.pageControls = null; this.pageType = null; @@ -151,6 +166,7 @@ export class SidebarManager { this.expandedNodes = new Set(); this.openDropdown = null; this.isHovering = false; + this.isDisabledByPage = false; this.apiClient = null; this.isInitialized = false; this.recursiveSearchEnabled = true; @@ -217,6 +233,18 @@ export class SidebarManager { if (recursiveToggleBtn) { recursiveToggleBtn.removeEventListener('click', this.handleRecursiveToggle); } + + const moreToggle = document.getElementById('sidebarMoreToggle'); + if (moreToggle) { + moreToggle.removeEventListener('click', this.handleMoreToggle); + } + + const moreDropdown = document.getElementById('sidebarMoreDropdown'); + if (moreDropdown) { + moreDropdown.removeEventListener('click', this.handleMoreDropdownItemClick); + } + + document.removeEventListener('click', this.handleDocumentClickForMore); } initializeDragAndDrop() { @@ -1045,6 +1073,19 @@ export class SidebarManager { } }); } + + // More options dropdown + const moreToggle = document.getElementById('sidebarMoreToggle'); + if (moreToggle) { + moreToggle.addEventListener('click', this.handleMoreToggle); + } + + const moreDropdown = document.getElementById('sidebarMoreDropdown'); + if (moreDropdown) { + moreDropdown.addEventListener('click', this.handleMoreDropdownItemClick); + } + + document.addEventListener('click', this.handleDocumentClickForMore); } handleDocumentClick(event) { @@ -1066,6 +1107,7 @@ export class SidebarManager { this.isPinned = !this.isPinned; this.updateAutoHideState(); this.updatePinButton(); + this.updateMoreDropdownLabels(); this.saveSidebarState(); this.updateContainerMargin(); } @@ -1129,7 +1171,7 @@ export class SidebarManager { } updateAutoHideState() { - if (this.isDisabledBySetting) return; + if (this.isDisabledBySetting || this.isDisabledByPage) return; const sidebar = document.getElementById('folderSidebar'); const hoverArea = document.getElementById('sidebarHoverArea'); @@ -1174,9 +1216,12 @@ export class SidebarManager { if (!container || !sidebar || this.isDisabledBySetting) return; - // Reset margin to default + // Always reset margin first — needed when transitioning from visible to hidden container.style.marginLeft = ''; + // When per-page disabled, skip adjustment but margin is already reset + if (this.isDisabledByPage) return; + // Only adjust margin if sidebar is visible and pinned if ((this.isPinned || this.isHovering) && this.isVisible) { const sidebarWidth = sidebar.offsetWidth; @@ -1193,20 +1238,29 @@ export class SidebarManager { } updateDomVisibility(enabled) { + // Per-page disable adds on top of global setting + const isVisible = enabled && !this.isDisabledByPage; const sidebar = document.getElementById('folderSidebar'); const hoverArea = document.getElementById('sidebarHoverArea'); if (sidebar) { - sidebar.classList.toggle('hidden-by-setting', !enabled); - sidebar.setAttribute('aria-hidden', (!enabled).toString()); + sidebar.classList.toggle('hidden-by-setting', !isVisible); + sidebar.setAttribute('aria-hidden', (!isVisible).toString()); } if (hoverArea) { - hoverArea.classList.toggle('hidden-by-setting', !enabled); - if (!enabled) { + hoverArea.classList.toggle('hidden-by-setting', !isVisible); + if (!isVisible) { hoverArea.classList.add('disabled'); } } + + // Show or hide the "sidebar hidden" notification + if (enabled && this.isDisabledByPage) { + this.showSidebarHiddenIndicator(); + } else { + this.hideSidebarHiddenIndicator(); + } } async setSidebarEnabled(enabled) { @@ -1266,6 +1320,133 @@ export class SidebarManager { } } + // ===== More Options Dropdown ===== + + handleMoreToggle(event) { + event.stopPropagation(); + const dropdown = document.getElementById('sidebarMoreDropdown'); + if (!dropdown) return; + + this.isMoreDropdownOpen = !dropdown.classList.contains('open'); + dropdown.classList.toggle('open', this.isMoreDropdownOpen); + this.updateMoreDropdownLabels(); + } + + handleMoreDropdownItemClick(event) { + const item = event.target.closest('.sidebar-dropdown-item'); + if (!item) return; + + const action = item.dataset.action; + if (!action) return; + + const dropdown = document.getElementById('sidebarMoreDropdown'); + if (dropdown) { + dropdown.classList.remove('open'); + this.isMoreDropdownOpen = false; + } + + switch (action) { + case 'toggle-pin': + this.handlePinToggle(event); + break; + case 'toggle-hide': + this.toggleHideOnThisPage(); + break; + } + } + + handleDocumentClickForMore(event) { + const dropdown = document.getElementById('sidebarMoreDropdown'); + const toggle = document.getElementById('sidebarMoreToggle'); + if (!dropdown || !toggle) return; + + if (!dropdown.contains(event.target) && !toggle.contains(event.target)) { + dropdown.classList.remove('open'); + this.isMoreDropdownOpen = false; + } + } + + updateMoreDropdownLabels() { + const pinLabel = document.getElementById('sidebarMorePinLabel'); + if (pinLabel) { + pinLabel.textContent = this.isPinned + ? translate('sidebar.unpinSidebar') + : translate('sidebar.pinSidebar'); + } + + const hideItem = document.querySelector('.sidebar-dropdown-item[data-action="toggle-hide"]'); + if (hideItem) { + const hideIcon = hideItem.querySelector('i'); + const hideLabel = hideItem.querySelector('span'); + if (this.isDisabledByPage) { + hideLabel.textContent = translate('sidebar.showSidebar'); + if (hideIcon) { + hideIcon.className = 'fas fa-eye'; + } + } else { + hideLabel.textContent = translate('sidebar.hideOnThisPage'); + if (hideIcon) { + hideIcon.className = 'fas fa-eye-slash'; + } + } + } + } + + toggleHideOnThisPage() { + this.isDisabledByPage = !this.isDisabledByPage; + setStorageItem(`${this.pageType}_sidebarDisabled`, this.isDisabledByPage); + this.updateDomVisibility(!this.isDisabledBySetting); + this.updateAutoHideState(); + this.updateContainerMargin(); + this.updateMoreDropdownLabels(); + + if (!this.isDisabledByPage) { + this.hideSidebarHiddenIndicator(); + } else { + showToast( + 'sidebar.sidebarHiddenNotification', + { page: this.getPageDisplayName() }, + 'info', + `Sidebar hidden on ${this.getPageDisplayName()} page` + ); + } + } + + getPageDisplayName() { + const names = { + loras: 'LoRAs', + recipes: 'Recipes', + checkpoints: 'Checkpoints', + embeddings: 'Embeddings', + }; + return names[this.pageType] || this.pageType; + } + + showSidebarHiddenIndicator() { + if (document.getElementById('sidebarHiddenIndicator')) return; + + const indicator = document.createElement('div'); + indicator.id = 'sidebarHiddenIndicator'; + indicator.className = 'sidebar-hidden-indicator'; + indicator.innerHTML = ` + + + `; + + indicator.addEventListener('click', () => { + this.toggleHideOnThisPage(); + }); + + document.body.appendChild(indicator); + } + + hideSidebarHiddenIndicator() { + const indicator = document.getElementById('sidebarHiddenIndicator'); + if (indicator) { + indicator.remove(); + } + } + async loadFolderTree() { try { if (this.displayMode === 'tree') { @@ -1911,6 +2092,7 @@ export class SidebarManager { const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []); const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree'); // 'tree' or 'list', default to 'tree' const recursiveSearchEnabled = getStorageItem(`${this.pageType}_recursiveSearch`, true); + this.isDisabledByPage = getStorageItem(`${this.pageType}_sidebarDisabled`, false); this.isPinned = isPinned; this.expandedNodes = new Set(expandedPaths); diff --git a/templates/components/folder_sidebar.html b/templates/components/folder_sidebar.html index 5f3748c8..fb7a88a1 100644 --- a/templates/components/folder_sidebar.html +++ b/templates/components/folder_sidebar.html @@ -18,6 +18,20 @@ + + + +