feat(settings): add search functionality to settings modal (P2)

Implement Phase 2 search bar feature for settings modal:

- Add search input to settings modal header with icon and clear button
- Implement real-time filtering with 150ms debounce for performance
- Add visual highlighting for matched search terms using accent color
- Implement empty search results state with user-friendly message
- Add keyboard shortcuts (Escape to clear search)
- Auto-expand sections containing matching content during search
- Fix header layout to prevent overlap with close button
- Update progress tracker documenting P2 completion
- Add translation keys for search feature (placeholder, clear, no results)
- Sync translations across all language files

Files changed:
- templates/components/modals/settings_modal.html
- static/css/components/modal/settings-modal.css
- static/js/managers/SettingsManager.js
- locales/*.json (10 language files)
- docs/ui-ux-optimization/progress-tracker.md
This commit is contained in:
Will Miao
2026-02-24 06:36:49 +08:00
parent 528225ffbd
commit 3f0227ba9d
14 changed files with 1403 additions and 788 deletions

View File

@@ -3,7 +3,7 @@
## Project Overview ## Project Overview
**Goal**: Optimize Settings Modal UI/UX with left navigation sidebar **Goal**: Optimize Settings Modal UI/UX with left navigation sidebar
**Started**: 2026-02-23 **Started**: 2026-02-23
**Current Phase**: P0 - Left Navigation Sidebar **Current Phase**: P2 - Search Bar (Completed)
--- ---
@@ -74,25 +74,42 @@ None currently
## Phase 1: Section Collapse/Expand (P1) ## Phase 1: Section Collapse/Expand (P1)
### Status: Planned ### Status: Completed ✓
### Completion Notes
- All sections now have collapse/expand functionality
- Chevron icon rotates smoothly on toggle
- State persistence via localStorage working correctly
- CSS animations for smooth height transitions
- Settings order reorganized to match sidebar navigation
### Tasks ### Tasks
- [ ] Add collapse/expand toggle to section headers - [x] Add collapse/expand toggle to section headers
- [ ] Add chevron icon with rotation animation - [x] Add chevron icon with rotation animation
- [ ] Implement localStorage for state persistence - [x] Implement localStorage for state persistence
- [ ] Add CSS animations for smooth transitions - [x] Add CSS animations for smooth transitions
- [x] Reorder settings sections to match sidebar navigation
--- ---
## Phase 2: Search Bar (P1) ## Phase 2: Search Bar (P1)
### Status: Planned ### Status: Completed ✓
### Completion Notes
- Search input added to settings modal header with icon and clear button
- Real-time filtering with debounced input (150ms delay)
- Highlight matching terms with accent color background
- Handle empty search results with user-friendly message
- Keyboard shortcuts: Escape to clear search
- Sections with matches are automatically expanded
- All translation keys added and synchronized across languages
### Tasks ### Tasks
- [ ] Add search input to header area - [x] Add search input to header area
- [ ] Implement real-time filtering - [x] Implement real-time filtering
- [ ] Add highlight for matched terms - [x] Add highlight for matched terms
- [ ] Handle empty search results - [x] Handle empty search results
--- ---
@@ -121,7 +138,29 @@ None currently
## Change Log ## Change Log
### 2026-02-23 ### 2026-02-23 (P2)
- Completed Phase 2: Search Bar
- Added search input to settings modal header with search icon and clear button
- Implemented real-time filtering with 150ms debounce for performance
- Added visual highlighting for matched search terms using accent color
- Implemented empty search results state with user-friendly message
- Added keyboard shortcuts (Escape to clear search)
- Sections with matching content are automatically expanded during search
- Updated SettingsManager.js with search initialization and filtering logic
- Added comprehensive CSS styles for search input, highlights, and responsive design
- Added translation keys for search feature (placeholder, clear, no results)
- Synchronized translations across all language files
### 2026-02-23 (P1)
- Completed Phase 1: Section Collapse/Expand
- Added collapse/expand functionality to all settings sections
- Implemented chevron icon with smooth rotation animation
- Added localStorage persistence for collapse state
- Reorganized settings sections to match sidebar navigation order
- Updated SettingsManager.js with section collapse initialization
- Added CSS styles for smooth transitions and animations
### 2026-02-23 (P0)
- Created project documentation - Created project documentation
- Started Phase 0 implementation - Started Phase 0 implementation
- Analyzed existing code structure - Analyzed existing code structure

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "Portabler Modus", "locationLabel": "Portabler Modus",
"locationHelp": "Aktiviere, um settings.json im Repository zu belassen; deaktiviere, um es im Benutzerkonfigurationsordner zu speichern." "locationHelp": "Aktiviere, um settings.json im Repository zu belassen; deaktiviere, um es im Benutzerkonfigurationsordner zu speichern."

View File

@@ -275,6 +275,11 @@
"download": "Download", "download": "Download",
"advanced": "Advanced" "advanced": "Advanced"
}, },
"search": {
"placeholder": "Search settings...",
"clear": "Clear search",
"noResults": "No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "Portable mode", "locationLabel": "Portable mode",
"locationHelp": "Enable to keep settings.json inside the repository; disable to store it in your user config directory." "locationHelp": "Enable to keep settings.json inside the repository; disable to store it in your user config directory."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "Modo portátil", "locationLabel": "Modo portátil",
"locationHelp": "Activa para mantener settings.json dentro del repositorio; desactívalo para guardarlo en tu directorio de configuración de usuario." "locationHelp": "Activa para mantener settings.json dentro del repositorio; desactívalo para guardarlo en tu directorio de configuración de usuario."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "Mode portable", "locationLabel": "Mode portable",
"locationHelp": "Activez pour garder settings.json dans le dépôt ; désactivez pour le placer dans votre dossier de configuration utilisateur." "locationHelp": "Activez pour garder settings.json dans le dépôt ; désactivez pour le placer dans votre dossier de configuration utilisateur."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "מצב נייד", "locationLabel": "מצב נייד",
"locationHelp": "הפעל כדי לשמור את settings.json בתוך המאגר; בטל כדי לשמור אותו בתיקיית ההגדרות של המשתמש." "locationHelp": "הפעל כדי לשמור את settings.json בתוך המאגר; בטל כדי לשמור אותו בתיקיית ההגדרות של המשתמש."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "ポータブルモード", "locationLabel": "ポータブルモード",
"locationHelp": "有効にすると settings.json をリポジトリ内に保持し、無効にするとユーザー設定ディレクトリに格納します。" "locationHelp": "有効にすると settings.json をリポジトリ内に保持し、無効にするとユーザー設定ディレクトリに格納します。"

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "휴대용 모드", "locationLabel": "휴대용 모드",
"locationHelp": "활성화하면 settings.json을 리포지토리에 유지하고, 비활성화하면 사용자 구성 디렉터리에 저장합니다." "locationHelp": "활성화하면 settings.json을 리포지토리에 유지하고, 비활성화하면 사용자 구성 디렉터리에 저장합니다."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "Портативный режим", "locationLabel": "Портативный режим",
"locationHelp": "Включите, чтобы хранить settings.json в репозитории; выключите, чтобы сохранить его в папке конфигурации пользователя." "locationHelp": "Включите, чтобы хранить settings.json в репозитории; выключите, чтобы сохранить его в папке конфигурации пользователя."

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "便携模式", "locationLabel": "便携模式",
"locationHelp": "开启可将 settings.json 保存在仓库中;关闭则保存在用户配置目录。" "locationHelp": "开启可将 settings.json 保存在仓库中;关闭则保存在用户配置目录。"

View File

@@ -275,6 +275,11 @@
"download": "[TODO: Translate] Download", "download": "[TODO: Translate] Download",
"advanced": "[TODO: Translate] Advanced" "advanced": "[TODO: Translate] Advanced"
}, },
"search": {
"placeholder": "[TODO: Translate] Search settings...",
"clear": "[TODO: Translate] Clear search",
"noResults": "[TODO: Translate] No settings found matching \"{query}\""
},
"storage": { "storage": {
"locationLabel": "可攜式模式", "locationLabel": "可攜式模式",
"locationHelp": "啟用可將 settings.json 保存在儲存庫中;停用則保存在使用者設定目錄。" "locationHelp": "啟用可將 settings.json 保存在儲存庫中;停用則保存在使用者設定目錄。"

View File

@@ -120,9 +120,115 @@
.settings-header { .settings-header {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start;
gap: var(--space-1); gap: var(--space-1);
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
padding-right: 40px; /* Space for close button */
}
.settings-header .settings-search-wrapper {
margin-left: auto;
}
/* Search Input Styles */
.settings-search-wrapper {
position: relative;
display: flex;
align-items: center;
width: 240px;
}
.settings-search-icon {
position: absolute;
left: 10px;
color: var(--text-color);
opacity: 0.5;
font-size: 0.9em;
pointer-events: none;
}
.settings-search-input {
width: 100%;
padding: 6px 28px 6px 32px;
height: 32px;
border-radius: var(--border-radius-xs);
border: 1px solid var(--border-color);
background-color: var(--lora-surface);
color: var(--text-color);
font-size: 0.9em;
transition: all 0.2s ease;
}
.settings-search-input:focus {
border-color: var(--lora-accent);
outline: none;
box-shadow: 0 0 0 2px rgba(var(--lora-accent-rgb, 79, 70, 229), 0.1);
}
.settings-search-input::placeholder {
color: var(--text-color);
opacity: 0.5;
}
.settings-search-clear {
position: absolute;
right: 6px;
width: 20px;
height: 20px;
border: none;
background: rgba(var(--border-color-rgb, 148, 163, 184), 0.3);
color: var(--text-color);
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7em;
opacity: 0.6;
transition: all 0.2s ease;
}
.settings-search-clear:hover {
opacity: 1;
background: rgba(var(--border-color-rgb, 148, 163, 184), 0.5);
}
/* Search Highlight Styles */
.settings-search-highlight {
background-color: rgba(var(--lora-accent-rgb, 79, 70, 229), 0.3);
color: var(--lora-accent);
padding: 0 2px;
border-radius: 2px;
font-weight: 500;
}
/* Section visibility during search */
.settings-section.search-match,
.setting-item.search-match {
display: block !important;
}
.settings-section.search-hidden,
.setting-item.search-hidden {
display: none !important;
}
/* Empty search results state */
.settings-search-empty {
text-align: center;
padding: var(--space-4);
color: var(--text-color);
opacity: 0.7;
}
.settings-search-empty i {
font-size: 2em;
margin-bottom: var(--space-2);
opacity: 0.5;
}
.settings-search-empty p {
margin: 0;
font-size: 0.95em;
} }
.settings-header h2 { .settings-header h2 {
@@ -461,13 +567,66 @@
padding-top: var(--space-2); padding-top: var(--space-2);
} }
.settings-section h3 { .settings-section-header {
font-size: 1.1em; display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
user-select: none;
padding: var(--space-1) 0;
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
transition: opacity 0.2s ease;
}
.settings-section-header:hover {
opacity: 0.8;
}
.settings-section-header h3 {
font-size: 1.1em;
margin: 0;
color: var(--text-color); color: var(--text-color);
opacity: 0.9; opacity: 0.9;
} }
.settings-section-toggle {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border: none;
background: transparent;
color: var(--text-color);
cursor: pointer;
transition: transform 0.3s ease;
opacity: 0.6;
}
.settings-section-toggle:hover {
opacity: 1;
}
.settings-section-toggle .chevron {
transition: transform 0.3s ease;
}
.settings-section.collapsed .settings-section-toggle .chevron {
transform: rotate(-90deg);
}
.settings-section-content {
overflow: hidden;
transition: max-height 0.3s ease, opacity 0.3s ease;
max-height: 2000px;
opacity: 1;
}
.settings-section.collapsed .settings-section-content {
max-height: 0;
opacity: 0;
}
.setting-item { .setting-item {
display: flex; display: flex;
flex-direction: column; /* Changed to column for help text placement */ flex-direction: column; /* Changed to column for help text placement */
@@ -806,6 +965,17 @@ input:checked + .toggle-slider:before {
flex-direction: column; flex-direction: column;
} }
.settings-header {
flex-direction: column;
align-items: flex-start;
gap: var(--space-2);
}
.settings-header .settings-search-wrapper {
margin-left: 0;
width: 100%;
}
.settings-nav { .settings-nav {
width: 100%; width: 100%;
max-height: 200px; max-height: 200px;

View File

@@ -365,6 +365,7 @@ export class SettingsManager {
this.setupPriorityTagInputs(); this.setupPriorityTagInputs();
this.initializeNavigation(); this.initializeNavigation();
this.initializeSearch();
this.initialized = true; this.initialized = true;
} }
@@ -435,10 +436,266 @@ export class SettingsManager {
} }
}); });
// Initialize section collapse/expand
this.initializeSectionCollapse();
// Initial update // Initial update
updateActiveNav(); updateActiveNav();
} }
initializeSectionCollapse() {
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
const STORAGE_KEY = 'settingsModal_collapsedSections';
// Load collapsed state from localStorage
let collapsedSections = {};
try {
const stored = localStorage.getItem(STORAGE_KEY);
if (stored) {
collapsedSections = JSON.parse(stored);
}
} catch (e) {
console.warn('Failed to load collapsed sections state:', e);
}
sections.forEach(section => {
const sectionId = section.getAttribute('data-section') || section.id;
const header = section.querySelector('.settings-section-header');
const toggleBtn = section.querySelector('.settings-section-toggle');
if (!header || !toggleBtn) return;
// Apply initial collapsed state
if (collapsedSections[sectionId]) {
section.classList.add('collapsed');
}
// Handle toggle click
const toggleSection = () => {
const isCollapsed = section.classList.toggle('collapsed');
// Save state to localStorage
collapsedSections[sectionId] = isCollapsed;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(collapsedSections));
} catch (e) {
console.warn('Failed to save collapsed sections state:', e);
}
};
// Click on header or toggle button
header.addEventListener('click', (e) => {
// Don't toggle if clicking on interactive elements within header
if (e.target.closest('a, button:not(.settings-section-toggle), input, select')) {
return;
}
toggleSection();
});
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleSection();
});
});
}
initializeSearch() {
const searchInput = document.getElementById('settingsSearchInput');
const searchClear = document.getElementById('settingsSearchClear');
if (!searchInput) return;
// Debounced search handler
let searchTimeout;
const debouncedSearch = (query) => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
this.performSearch(query);
}, 150);
};
// Handle input changes
searchInput.addEventListener('input', (e) => {
const query = e.target.value.trim();
// Show/hide clear button
if (searchClear) {
searchClear.style.display = query ? 'flex' : 'none';
}
debouncedSearch(query);
});
// Handle clear button click
if (searchClear) {
searchClear.addEventListener('click', () => {
searchInput.value = '';
searchClear.style.display = 'none';
searchInput.focus();
this.performSearch('');
});
}
// Handle Escape key to clear search
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
if (searchInput.value) {
searchInput.value = '';
if (searchClear) searchClear.style.display = 'none';
this.performSearch('');
}
}
});
}
performSearch(query) {
const sections = document.querySelectorAll('.settings-section, .setting-item[id^="section-"]');
const settingsForm = document.querySelector('.settings-form');
// Remove existing empty state
const existingEmptyState = settingsForm?.querySelector('.settings-search-empty');
if (existingEmptyState) {
existingEmptyState.remove();
}
if (!query) {
// Reset all sections to visible and remove highlights
sections.forEach(section => {
section.classList.remove('search-hidden', 'search-match');
this.removeSearchHighlights(section);
});
return;
}
const lowerQuery = query.toLowerCase();
let matchCount = 0;
sections.forEach(section => {
const sectionText = this.getSectionSearchableText(section);
const hasMatch = sectionText.includes(lowerQuery);
if (hasMatch) {
section.classList.remove('search-hidden');
section.classList.add('search-match');
this.highlightSearchMatches(section, lowerQuery);
matchCount++;
// Expand section if it has matches
section.classList.remove('collapsed');
} else {
section.classList.add('search-hidden');
section.classList.remove('search-match');
this.removeSearchHighlights(section);
}
});
// Show empty state if no matches found
if (matchCount === 0 && settingsForm) {
const emptyState = document.createElement('div');
emptyState.className = 'settings-search-empty';
emptyState.innerHTML = `
<i class="fas fa-search"></i>
<p>${translate('settings.search.noResults', { query }, `No settings found matching "${query}"`)}</p>
`;
settingsForm.appendChild(emptyState);
}
}
getSectionSearchableText(section) {
// Get all text content from labels, help text, and headers
const labels = section.querySelectorAll('label');
const helpTexts = section.querySelectorAll('.input-help');
const headers = section.querySelectorAll('h3');
let text = '';
labels.forEach(el => text += ' ' + el.textContent);
helpTexts.forEach(el => text += ' ' + el.textContent);
headers.forEach(el => text += ' ' + el.textContent);
return text.toLowerCase();
}
highlightSearchMatches(section, query) {
// Remove existing highlights first
this.removeSearchHighlights(section);
if (!query) return;
// Highlight in labels and help text
const textElements = section.querySelectorAll('label, .input-help, h3');
textElements.forEach(element => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
null,
false
);
const textNodes = [];
let node;
while (node = walker.nextNode()) {
if (node.textContent.toLowerCase().includes(query)) {
textNodes.push(node);
}
}
textNodes.forEach(textNode => {
const parent = textNode.parentElement;
const text = textNode.textContent;
const lowerText = text.toLowerCase();
// Split text by query and wrap matches in highlight spans
const parts = [];
let lastIndex = 0;
let index;
while ((index = lowerText.indexOf(query, lastIndex)) !== -1) {
// Add text before match
if (index > lastIndex) {
parts.push(document.createTextNode(text.substring(lastIndex, index)));
}
// Add highlighted match
const highlight = document.createElement('span');
highlight.className = 'settings-search-highlight';
highlight.textContent = text.substring(index, index + query.length);
parts.push(highlight);
lastIndex = index + query.length;
}
// Add remaining text
if (lastIndex < text.length) {
parts.push(document.createTextNode(text.substring(lastIndex)));
}
// Replace original text node with highlighted version
if (parts.length > 1) {
parts.forEach(part => parent.insertBefore(part, textNode));
parent.removeChild(textNode);
}
});
});
}
removeSearchHighlights(section) {
const highlights = section.querySelectorAll('.settings-search-highlight');
highlights.forEach(highlight => {
const parent = highlight.parentElement;
if (parent) {
// Replace highlight with its text content
parent.insertBefore(document.createTextNode(highlight.textContent), highlight);
parent.removeChild(highlight);
// Normalize to merge adjacent text nodes
parent.normalize();
}
});
}
async openSettingsFileLocation() { async openSettingsFileLocation() {
try { try {
const response = await fetch('/api/lm/settings/open-location', { const response = await fetch('/api/lm/settings/open-location', {

View File

@@ -11,6 +11,22 @@
title="{{ t('settings.openSettingsFileLocation.tooltip') }}"> title="{{ t('settings.openSettingsFileLocation.tooltip') }}">
<i class="fas fa-external-link-alt" aria-hidden="true"></i> <i class="fas fa-external-link-alt" aria-hidden="true"></i>
</button> </button>
<div class="settings-search-wrapper">
<i class="fas fa-search settings-search-icon" aria-hidden="true"></i>
<input type="text"
id="settingsSearchInput"
class="settings-search-input"
placeholder="{{ t('settings.search.placeholder') }}"
aria-label="{{ t('settings.search.placeholder') }}"
autocomplete="off">
<button type="button"
id="settingsSearchClear"
class="settings-search-clear"
aria-label="{{ t('settings.search.clear') }}"
style="display: none;">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<!-- Navigation Sidebar --> <!-- Navigation Sidebar -->
@@ -39,6 +55,7 @@
<div class="settings-nav-group-title">{{ t('settings.nav.advanced') }}</div> <div class="settings-nav-group-title">{{ t('settings.nav.advanced') }}</div>
<button type="button" class="settings-nav-item" data-target="section-priority-tags">{{ t('settings.priorityTags.title') }}</button> <button type="button" class="settings-nav-item" data-target="section-priority-tags">{{ t('settings.priorityTags.title') }}</button>
<button type="button" class="settings-nav-item" data-target="section-exclusions">{{ t('settings.autoOrganizeExclusions.label') }}</button> <button type="button" class="settings-nav-item" data-target="section-exclusions">{{ t('settings.autoOrganizeExclusions.label') }}</button>
<button type="button" class="settings-nav-item" data-target="section-metadata-skip-paths">{{ t('settings.metadataRefreshSkipPaths.label') }}</button>
<button type="button" class="settings-nav-item" data-target="section-metadata-archive">{{ t('settings.sections.metadataArchive') }}</button> <button type="button" class="settings-nav-item" data-target="section-metadata-archive">{{ t('settings.sections.metadataArchive') }}</button>
<button type="button" class="settings-nav-item" data-target="section-proxy">{{ t('settings.sections.proxySettings') }}</button> <button type="button" class="settings-nav-item" data-target="section-proxy">{{ t('settings.sections.proxySettings') }}</button>
<button type="button" class="settings-nav-item" data-target="section-misc">{{ t('settings.sections.misc') }}</button> <button type="button" class="settings-nav-item" data-target="section-misc">{{ t('settings.sections.misc') }}</button>
@@ -49,7 +66,15 @@
<!-- Content Area --> <!-- Content Area -->
<div class="settings-content"> <div class="settings-content">
<div class="settings-form"> <div class="settings-form">
<div id="section-api-key" class="setting-item api-key-item"> <!-- 1. API Key Section (General) -->
<div id="section-api-key" class="setting-item api-key-item" data-section="api-key">
<div class="settings-section-header">
<h3>{{ t('settings.civitaiApiKey') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
<label for="civitaiApiKey">{{ t('settings.civitaiApiKey') }}:</label> <label for="civitaiApiKey">{{ t('settings.civitaiApiKey') }}:</label>
@@ -72,9 +97,17 @@
{{ t('settings.civitaiApiKeyHelp') }} {{ t('settings.civitaiApiKeyHelp') }}
</div> </div>
</div> </div>
</div>
<div id="section-storage" class="settings-section"> <!-- 2. Storage Location Section (General) -->
<div id="section-storage" class="settings-section" data-section="storage">
<div class="settings-section-header">
<h3>{{ t('settings.sections.storageLocation') }}</h3> <h3>{{ t('settings.sections.storageLocation') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -93,10 +126,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div id="section-content-filtering" class="settings-section"> <!-- 3. Content Filtering Section (Interface) -->
<div id="section-content-filtering" class="settings-section" data-section="content-filtering">
<div class="settings-section-header">
<h3>{{ t('settings.sections.contentFiltering') }}</h3> <h3>{{ t('settings.sections.contentFiltering') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -133,11 +173,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Add Video Settings Section --> <!-- 4. Video Settings Section (Interface) -->
<div id="section-video" class="settings-section"> <div id="section-video" class="settings-section" data-section="video">
<div class="settings-section-header">
<h3>{{ t('settings.sections.videoSettings') }}</h3> <h3>{{ t('settings.sections.videoSettings') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -156,11 +202,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Add Layout Settings Section --> <!-- 5. Layout Settings Section (Interface) -->
<div id="section-layout" class="settings-section"> <div id="section-layout" class="settings-section" data-section="layout">
<div class="settings-section-header">
<h3>{{ t('settings.sections.layoutSettings') }}</h3> <h3>{{ t('settings.sections.layoutSettings') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -203,7 +255,6 @@
</div> </div>
</div> </div>
<!-- Add Model Name Display setting -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -221,7 +272,6 @@
</div> </div>
</div> </div>
<!-- Add Card Info Display setting -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -256,7 +306,6 @@
</div> </div>
</div> </div>
<!-- Language Selection -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -282,11 +331,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Add Folder Settings Section --> <!-- 6. Folder Settings Section (Download) -->
<div id="section-folder" class="settings-section"> <div id="section-folder" class="settings-section" data-section="folder">
<div class="settings-section-header">
<h3>{{ t('settings.sections.folderSettings') }}</h3> <h3>{{ t('settings.sections.folderSettings') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -311,7 +366,6 @@
<div class="setting-control select-control"> <div class="setting-control select-control">
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')"> <select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')">
<option value="">{{ t('settings.folderSettings.noDefault') }}</option> <option value="">{{ t('settings.folderSettings.noDefault') }}</option>
<!-- Options will be loaded dynamically -->
</select> </select>
</div> </div>
</div> </div>
@@ -328,7 +382,6 @@
<div class="setting-control select-control"> <div class="setting-control select-control">
<select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')"> <select id="defaultCheckpointRoot" onchange="settingsManager.saveSelectSetting('defaultCheckpointRoot', 'default_checkpoint_root')">
<option value="">{{ t('settings.folderSettings.noDefault') }}</option> <option value="">{{ t('settings.folderSettings.noDefault') }}</option>
<!-- Options will be loaded dynamically -->
</select> </select>
</div> </div>
</div> </div>
@@ -345,7 +398,6 @@
<div class="setting-control select-control"> <div class="setting-control select-control">
<select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')"> <select id="defaultUnetRoot" onchange="settingsManager.saveSelectSetting('defaultUnetRoot', 'default_unet_root')">
<option value="">{{ t('settings.folderSettings.noDefault') }}</option> <option value="">{{ t('settings.folderSettings.noDefault') }}</option>
<!-- Options will be loaded dynamically -->
</select> </select>
</div> </div>
</div> </div>
@@ -362,7 +414,6 @@
<div class="setting-control select-control"> <div class="setting-control select-control">
<select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')"> <select id="defaultEmbeddingRoot" onchange="settingsManager.saveSelectSetting('defaultEmbeddingRoot', 'default_embedding_root')">
<option value="">{{ t('settings.folderSettings.noDefault') }}</option> <option value="">{{ t('settings.folderSettings.noDefault') }}</option>
<!-- Options will be loaded dynamically -->
</select> </select>
</div> </div>
</div> </div>
@@ -371,10 +422,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Update Flag Strategy Section --> <!-- 7. Update Flag Strategy Section (Download) -->
<div id="section-update-flags" class="settings-section"> <div id="section-update-flags" class="settings-section" data-section="update-flags">
<div class="settings-section-header">
<h3>{{ t('settings.sections.updateFlags') }}</h3> <h3>{{ t('settings.sections.updateFlags') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -409,11 +467,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Default Path Customization Section --> <!-- 8. Path Templates Section (Download) -->
<div id="section-path-templates" class="settings-section"> <div id="section-path-templates" class="settings-section" data-section="path-templates">
<div class="settings-section-header">
<h3>{{ t('settings.downloadPathTemplates.title') }}</h3> <h3>{{ t('settings.downloadPathTemplates.title') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="input-help"> <div class="input-help">
{{ t('settings.downloadPathTemplates.help') }} {{ t('settings.downloadPathTemplates.help') }}
@@ -428,7 +492,6 @@
</div> </div>
</div> </div>
<!-- LoRA Template Configuration -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -455,7 +518,6 @@
<div class="template-preview" id="loraPreview"></div> <div class="template-preview" id="loraPreview"></div>
</div> </div>
<!-- Checkpoint Template Configuration -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -482,7 +544,6 @@
<div class="template-preview" id="checkpointPreview"></div> <div class="template-preview" id="checkpointPreview"></div>
</div> </div>
<!-- Embedding Template Configuration -->
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -508,7 +569,6 @@
</div> </div>
<div class="template-preview" id="embeddingPreview"></div> <div class="template-preview" id="embeddingPreview"></div>
</div> </div>
</div>
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
@@ -527,91 +587,21 @@
</div> </div>
<div class="mappings-container"> <div class="mappings-container">
<div id="baseModelMappingsContainer"> <div id="baseModelMappingsContainer">
<!-- Mapping rows will be added dynamically -->
</div> </div>
</div> </div>
</div> </div>
<div id="section-priority-tags" class="setting-item priority-tags-item">
<div class="setting-row priority-tags-header">
<div class="setting-info priority-tags-info">
<label>{{ t('settings.priorityTags.title') }}</label>
<a class="settings-action-link priority-tags-help-link" href="https://github.com/willmiao/ComfyUI-Lora-Manager/wiki/Priority-Tags-Configuration-Guide" target="_blank" rel="noopener" aria-label="{{ t('settings.priorityTags.helpLinkLabel') }}" title="{{ t('settings.priorityTags.helpLinkLabel') }}">
<i class="fas fa-question-circle" aria-hidden="true"></i>
</a>
</div>
</div>
<div class="input-help">{{ t('settings.priorityTags.description') }}</div>
<div class="priority-tags-tabs">
<input type="radio" id="priority-tags-tab-lora" name="priority-tags-tab" class="priority-tags-tab-input" checked>
<input type="radio" id="priority-tags-tab-checkpoint" name="priority-tags-tab" class="priority-tags-tab-input">
<input type="radio" id="priority-tags-tab-embedding" name="priority-tags-tab" class="priority-tags-tab-input">
<div class="priority-tags-tablist">
<label class="priority-tags-tab-label" for="priority-tags-tab-lora" id="priority-tags-tab-lora-label">{{ t('settings.priorityTags.modelTypes.lora') }}</label>
<label class="priority-tags-tab-label" for="priority-tags-tab-checkpoint" id="priority-tags-tab-checkpoint-label">{{ t('settings.priorityTags.modelTypes.checkpoint') }}</label>
<label class="priority-tags-tab-label" for="priority-tags-tab-embedding" id="priority-tags-tab-embedding-label">{{ t('settings.priorityTags.modelTypes.embedding') }}</label>
</div>
<div class="priority-tags-panels">
<div class="priority-tags-panel" id="priority-tags-panel-lora" aria-labelledby="priority-tags-tab-lora-label">
<textarea id="loraPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="loraPriorityTagsError"></div>
</div>
<div class="priority-tags-panel" id="priority-tags-panel-checkpoint" aria-labelledby="priority-tags-tab-checkpoint-label">
<textarea id="checkpointPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="checkpointPriorityTagsError"></div>
</div>
<div class="priority-tags-panel" id="priority-tags-panel-embedding" aria-labelledby="priority-tags-tab-embedding-label">
<textarea id="embeddingPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="embeddingPriorityTagsError"></div>
</div>
</div>
</div> </div>
</div> </div>
<div id="section-exclusions" class="setting-item priority-tags-item auto-organize-exclusions-item"> <!-- 9. Example Images Settings Section (Download) -->
<div class="setting-row priority-tags-header"> <div id="section-example-images" class="settings-section" data-section="example-images">
<div class="setting-info priority-tags-info"> <div class="settings-section-header">
<label>{{ t('settings.autoOrganizeExclusions.label') }}</label>
</div>
</div>
<div class="input-help">
{{ t('settings.autoOrganizeExclusions.help') }}
</div>
<textarea
id="autoOrganizeExclusions"
class="priority-tags-input auto-organize-exclusions-input"
rows="3"
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
onblur="settingsManager.saveAutoOrganizeExclusions()"
></textarea>
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
</div>
<div class="setting-item priority-tags-item auto-organize-exclusions-item">
<div class="setting-row priority-tags-header">
<div class="setting-info priority-tags-info">
<label>{{ t('settings.metadataRefreshSkipPaths.label') }}</label>
</div>
</div>
<div class="input-help">
{{ t('settings.metadataRefreshSkipPaths.help') }}
</div>
<textarea
id="metadataRefreshSkipPaths"
class="priority-tags-input auto-organize-exclusions-input"
rows="3"
placeholder="{{ t('settings.metadataRefreshSkipPaths.placeholder') }}"
onblur="settingsManager.saveMetadataRefreshSkipPaths()"
></textarea>
<div class="settings-input-error-message" id="metadataRefreshSkipPathsError"></div>
</div>
<!-- Add Example Images Settings Section -->
<div id="section-example-images" class="settings-section">
<h3>{{ t('settings.sections.exampleImages') }}</h3> <h3>{{ t('settings.sections.exampleImages') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -665,11 +655,107 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Metadata Archive Section --> <!-- 10. Priority Tags Section (Advanced) -->
<div id="section-metadata-archive" class="settings-section"> <div id="section-priority-tags" class="setting-item priority-tags-item" data-section="priority-tags">
<div class="settings-section-header">
<h3>
{{ t('settings.priorityTags.title') }}
<a class="settings-action-link priority-tags-help-link" href="https://github.com/willmiao/ComfyUI-Lora-Manager/wiki/Priority-Tags-Configuration-Guide" target="_blank" rel="noopener" aria-label="{{ t('settings.priorityTags.helpLinkLabel') }}" title="{{ t('settings.priorityTags.helpLinkLabel') }}">
<i class="fas fa-question-circle" aria-hidden="true"></i>
</a>
</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="input-help">{{ t('settings.priorityTags.description') }}</div>
<div class="priority-tags-tabs">
<input type="radio" id="priority-tags-tab-lora" name="priority-tags-tab" class="priority-tags-tab-input" checked>
<input type="radio" id="priority-tags-tab-checkpoint" name="priority-tags-tab" class="priority-tags-tab-input">
<input type="radio" id="priority-tags-tab-embedding" name="priority-tags-tab" class="priority-tags-tab-input">
<div class="priority-tags-tablist">
<label class="priority-tags-tab-label" for="priority-tags-tab-lora" id="priority-tags-tab-lora-label">{{ t('settings.priorityTags.modelTypes.lora') }}</label>
<label class="priority-tags-tab-label" for="priority-tags-tab-checkpoint" id="priority-tags-tab-checkpoint-label">{{ t('settings.priorityTags.modelTypes.checkpoint') }}</label>
<label class="priority-tags-tab-label" for="priority-tags-tab-embedding" id="priority-tags-tab-embedding-label">{{ t('settings.priorityTags.modelTypes.embedding') }}</label>
</div>
<div class="priority-tags-panels">
<div class="priority-tags-panel" id="priority-tags-panel-lora" aria-labelledby="priority-tags-tab-lora-label">
<textarea id="loraPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="loraPriorityTagsError"></div>
</div>
<div class="priority-tags-panel" id="priority-tags-panel-checkpoint" aria-labelledby="priority-tags-tab-checkpoint-label">
<textarea id="checkpointPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="checkpointPriorityTagsError"></div>
</div>
<div class="priority-tags-panel" id="priority-tags-panel-embedding" aria-labelledby="priority-tags-tab-embedding-label">
<textarea id="embeddingPriorityTagsInput" class="priority-tags-input" rows="3" placeholder="{{ t('settings.priorityTags.placeholder') }}"></textarea>
<div class="settings-input-error-message" id="embeddingPriorityTagsError"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 11. Auto-organize Exclusions Section (Advanced) -->
<div id="section-exclusions" class="setting-item priority-tags-item auto-organize-exclusions-item" data-section="exclusions">
<div class="settings-section-header">
<h3>{{ t('settings.autoOrganizeExclusions.label') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="input-help">
{{ t('settings.autoOrganizeExclusions.help') }}
</div>
<textarea
id="autoOrganizeExclusions"
class="priority-tags-input auto-organize-exclusions-input"
rows="3"
placeholder="{{ t('settings.autoOrganizeExclusions.placeholder') }}"
onblur="settingsManager.saveAutoOrganizeExclusions()"
></textarea>
<div class="settings-input-error-message" id="autoOrganizeExclusionsError"></div>
</div>
</div>
<!-- 12. Metadata Refresh Skip Paths Section (Advanced) -->
<div id="section-metadata-skip-paths" class="setting-item priority-tags-item auto-organize-exclusions-item" data-section="metadata-skip-paths">
<div class="settings-section-header">
<h3>{{ t('settings.metadataRefreshSkipPaths.label') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="input-help">
{{ t('settings.metadataRefreshSkipPaths.help') }}
</div>
<textarea
id="metadataRefreshSkipPaths"
class="priority-tags-input auto-organize-exclusions-input"
rows="3"
placeholder="{{ t('settings.metadataRefreshSkipPaths.placeholder') }}"
onblur="settingsManager.saveMetadataRefreshSkipPaths()"
></textarea>
<div class="settings-input-error-message" id="metadataRefreshSkipPathsError"></div>
</div>
</div>
<!-- 13. Metadata Archive Section (Advanced) -->
<div id="section-metadata-archive" class="settings-section" data-section="metadata-archive">
<div class="settings-section-header">
<h3>{{ t('settings.sections.metadataArchive') }}</h3> <h3>{{ t('settings.sections.metadataArchive') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -689,7 +775,6 @@
<div class="setting-item"> <div class="setting-item">
<div class="metadata-archive-status" id="metadataArchiveStatus"> <div class="metadata-archive-status" id="metadataArchiveStatus">
<!-- Status will be populated by JavaScript -->
</div> </div>
</div> </div>
@@ -712,11 +797,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Proxy Settings Section --> <!-- 14. Proxy Settings Section (Advanced) -->
<div id="section-proxy" class="settings-section"> <div id="section-proxy" class="settings-section" data-section="proxy">
<div class="settings-section-header">
<h3>{{ t('settings.sections.proxySettings') }}</h3> <h3>{{ t('settings.sections.proxySettings') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -835,10 +926,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<!-- Misc. Section --> <!-- 15. Misc Section (Advanced) -->
<div id="section-misc" class="settings-section"> <div id="section-misc" class="settings-section" data-section="misc">
<div class="settings-section-header">
<h3>{{ t('settings.sections.misc') }}</h3> <h3>{{ t('settings.sections.misc') }}</h3>
<button type="button" class="settings-section-toggle" aria-label="Toggle section">
<i class="fas fa-chevron-down chevron"></i>
</button>
</div>
<div class="settings-section-content">
<div class="setting-item"> <div class="setting-item">
<div class="setting-row"> <div class="setting-row">
<div class="setting-info"> <div class="setting-info">
@@ -857,6 +955,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>