diff --git a/locales/de.json b/locales/de.json index 64907f87..e7a3a719 100644 --- a/locales/de.json +++ b/locales/de.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/en.json b/locales/en.json index d775ad37..57edd93a 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1977,6 +1977,7 @@ "title": "Keyboard & Mouse Shortcuts", "groups": { "general": "General", + "actions": "Actions", "selection": "Selection & Bulk Mode", "navigation": "Navigation", "modelModal": "Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "Focus search", "closeModal": "Close modal / panel", "openShortcuts": "Open this shortcuts panel", + "refresh": "Refresh model list", + "fetchMetadata": "Fetch metadata from CivitAI (model pages only)", + "downloadModel": "Download a model (model pages only)", "toggleBulkMode": "Toggle bulk mode", "selectAll": "Select all visible models", "rangeSelect": "Range select", diff --git a/locales/es.json b/locales/es.json index 0582e714..bde3d681 100644 --- a/locales/es.json +++ b/locales/es.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/fr.json b/locales/fr.json index 5fb4f173..8ec0611e 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/he.json b/locales/he.json index f1c73328..e5989b59 100644 --- a/locales/he.json +++ b/locales/he.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/ja.json b/locales/ja.json index 9ef133fc..bf31ac58 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/ko.json b/locales/ko.json index e77d279f..355f0aa6 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/ru.json b/locales/ru.json index dff99a88..537b55cf 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index d6419e9d..fb0dbe54 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 882744a1..62b1c9f0 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -1977,6 +1977,7 @@ "title": "[TODO: Translate] Keyboard & Mouse Shortcuts", "groups": { "general": "[TODO: Translate] General", + "actions": "[TODO: Translate] Actions", "selection": "[TODO: Translate] Selection & Bulk Mode", "navigation": "[TODO: Translate] Navigation", "modelModal": "[TODO: Translate] Model / Recipe Modal", @@ -1993,6 +1994,9 @@ "focusSearch": "[TODO: Translate] Focus search", "closeModal": "[TODO: Translate] Close modal / panel", "openShortcuts": "[TODO: Translate] Open this shortcuts panel", + "refresh": "[TODO: Translate] Refresh model list", + "fetchMetadata": "[TODO: Translate] Fetch metadata from CivitAI (model pages only)", + "downloadModel": "[TODO: Translate] Download a model (model pages only)", "toggleBulkMode": "[TODO: Translate] Toggle bulk mode", "selectAll": "[TODO: Translate] Select all visible models", "rangeSelect": "[TODO: Translate] Range select", diff --git a/static/css/base.css b/static/css/base.css index 3656b7fd..245d1821 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -31,9 +31,14 @@ body { --header-height: 48px; --scrollbar-width: 8px; - --shortcut-bg: var(--color-accent-subtle); - --shortcut-border: var(--color-accent-border); - --shortcut-text: var(--text-primary); + /* Neutral "keycap" style for keyboard shortcut hints (GitHub/Linear-like). + Derived from --text-muted so it adapts to every theme/preset. */ + --shortcut-bg: color-mix(in oklch, var(--text-muted) 10%, transparent); + --shortcut-bg-hover: color-mix(in oklch, var(--text-muted) 16%, transparent); + --shortcut-border: color-mix(in oklch, var(--text-muted) 30%, transparent); + --shortcut-border-hover: color-mix(in oklch, var(--text-muted) 45%, transparent); + --shortcut-text: var(--text-muted); + --shortcut-shadow: 0 1.5px 0 color-mix(in oklch, var(--text-muted) 30%, transparent); --lora-accent-transparent: var(--color-accent-transparent); diff --git a/static/css/components/header.css b/static/css/components/header.css index fa35eb97..7d2b5441 100644 --- a/static/css/components/header.css +++ b/static/css/components/header.css @@ -249,10 +249,10 @@ font-family: inherit; font-size: 0.68rem; font-weight: 500; - color: var(--text-muted); - /* Subtle tint derived from text color so it adapts to both light & dark themes */ - background: color-mix(in oklch, var(--text-muted) 12%, transparent); - border: 1px solid color-mix(in oklch, var(--text-muted) 25%, transparent); + color: var(--shortcut-text); + background: var(--shortcut-bg); + border: 1px solid var(--shortcut-border); + box-shadow: var(--shortcut-shadow); border-radius: var(--border-radius-xs, 3px); line-height: 1; } diff --git a/static/css/components/modal/help-modal.css b/static/css/components/modal/help-modal.css index 40c26f9a..9c6d91f8 100644 --- a/static/css/components/modal/help-modal.css +++ b/static/css/components/modal/help-modal.css @@ -376,10 +376,10 @@ font-family: inherit; font-size: 0.68rem; font-weight: 500; - color: var(--text-muted); - /* Subtle tint derived from text color so it adapts to both light & dark themes */ - background: color-mix(in oklch, var(--text-muted) 12%, transparent); - border: 1px solid color-mix(in oklch, var(--text-muted) 25%, transparent); + color: var(--shortcut-text); + background: var(--shortcut-bg); + border: 1px solid var(--shortcut-border); + box-shadow: var(--shortcut-shadow); border-radius: var(--border-radius-xs, 3px); line-height: 1; } diff --git a/static/css/layout.css b/static/css/layout.css index 6ab1766d..dec8e473 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -202,15 +202,17 @@ align-items: center; justify-content: center; margin-left: 6px; - min-width: 16px; - height: 16px; - padding: 0 3px; + min-width: 18px; + height: 18px; + padding: 0 5px; font-size: 11px; font-weight: 600; line-height: 1; + text-transform: uppercase; border-radius: var(--border-radius-xs); background-color: var(--shortcut-bg); border: 1px solid var(--shortcut-border); + box-shadow: var(--shortcut-shadow); color: var(--shortcut-text); vertical-align: middle; opacity: 0.8; @@ -219,12 +221,8 @@ .control-group button:hover .shortcut-key { opacity: 1; - background-color: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.2); -} - -[data-theme="dark"] .shortcut-key { - --shortcut-bg: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.15); - --shortcut-border: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.3); + background-color: var(--shortcut-bg-hover); + border-color: var(--shortcut-border-hover); } /* Ensure correct vertical alignment for text+shortcut */ diff --git a/static/css/onboarding.css b/static/css/onboarding.css index 592200aa..4ad20212 100644 --- a/static/css/onboarding.css +++ b/static/css/onboarding.css @@ -205,6 +205,7 @@ display: inline-block; background: var(--shortcut-bg); border: 1px solid var(--shortcut-border); + box-shadow: var(--shortcut-shadow); border-radius: var(--border-radius-xs); padding: 2px 6px; font-size: 0.8em; diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js index 42534640..294651dd 100644 --- a/static/js/components/controls/PageControls.js +++ b/static/js/components/controls/PageControls.js @@ -1,7 +1,8 @@ // PageControls.js - Manages controls for both LoRAs and Checkpoints pages import { state, getCurrentPageState, setCurrentPageType } from '../../state/index.js'; import { getStorageItem, setStorageItem, removeStorageItem, getSessionItem, setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js'; -import { showToast, openCivitaiByMetadata } from '../../utils/uiHelpers.js'; +import { showToast, openCivitaiByMetadata, isTypingContext } from '../../utils/uiHelpers.js'; +import { eventManager } from '../../utils/EventManager.js'; import { performModelUpdateCheck } from '../../utils/updateCheckHelpers.js'; import { sidebarManager } from '../SidebarManager.js'; import { initSortDropdown, applySortToSelect, randomizeSortValue } from './SortDropdown.js'; @@ -146,6 +147,62 @@ export class PageControls { // Page-specific event listeners this.initPageSpecificListeners(); + + // Keyboard shortcuts for the actions toolbar (R / F / D) + this.registerKeyboardShortcuts(); + } + + /** + * Register keyboard shortcuts for the actions toolbar buttons + * (R = refresh, F = fetch metadata, D = download) + */ + registerKeyboardShortcuts() { + eventManager.addHandler('keydown', 'pageControls-actions', (e) => { + return this.handleActionShortcut(e); + }, { + priority: 90, + skipWhenModalOpen: true + }); + } + + /** + * Handle a keydown event for the actions toolbar shortcuts + * @param {KeyboardEvent} e + * @returns {boolean} True when the event was handled and propagation should stop + */ + handleActionShortcut(e) { + // Plain letters only — leave modified combos (Ctrl/Cmd/Alt) alone + if (e.ctrlKey || e.metaKey || e.altKey) { + return false; + } + + // Don't hijack keys while typing in a text entry context + if (isTypingContext(e.target)) { + return false; + } + + const actionByKey = { + r: 'refresh', + f: 'fetch', + d: 'download' + }; + const action = actionByKey[e.key.toLowerCase()]; + if (!action) { + return false; + } + + // The button may not exist on this page (e.g. recipes has no + // fetch/download) — let other handlers run in that case + const button = document.querySelector(`[data-action="${action}"]`); + if (!button) { + return false; + } + + e.preventDefault(); + // Native disabled buttons ignore .click(), so an in-progress + // refresh is safe + button.click(); + return true; } initExcludedViewControls() { diff --git a/static/js/utils/uiHelpers.js b/static/js/utils/uiHelpers.js index deb4b37f..ce654c57 100644 --- a/static/js/utils/uiHelpers.js +++ b/static/js/utils/uiHelpers.js @@ -311,6 +311,20 @@ export function showActionToast(key, params = {}, type = 'info', options = {}) { toast.append(closeBtn); } +/** + * Check whether the event target is a text-entry context (input, textarea, + * select, or contenteditable) where single-letter shortcuts should be treated + * as literal input. + * @param {EventTarget|null} target - The DOM event target + * @returns {boolean} + */ +export function isTypingContext(target) { + if (!(target instanceof Element)) return false; + + const tagName = target.tagName?.toLowerCase(); + return target.isContentEditable || tagName === 'input' || tagName === 'textarea' || tagName === 'select'; +} + export function restoreFolderFilter() { const activeFolder = getStorageItem('activeFolder'); const folderTag = activeFolder && document.querySelector(`.tag[data-folder="${activeFolder}"]`); diff --git a/templates/components/controls.html b/templates/components/controls.html index 1aafdad1..15aa2871 100644 --- a/templates/components/controls.html +++ b/templates/components/controls.html @@ -65,7 +65,7 @@