mirror of
https://github.com/aleksey-hoffman/sigma-file-manager.git
synced 2026-10-19 01:31:05 -05:00
Improve: shortcuts
- Unassign default shortcuts from "Navigate to previous / next page" action to avoid confusion; - Allow to unassign shortcuts; - Allow to assign multiple shortcuts to one action; - Add context menu to the shortcuts list; - Improve design;
This commit is contained in:
1 parent
7214a940b3
commit
e017cb141e
20 files changed
+1015
-328
No files matched your search
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "即使应用程序未获得焦点,全局快捷键也会触发操作。",
|
||||
"changeShortcut": "更改快捷键",
|
||||
"resetShortcut": "重置快捷键",
|
||||
"unassignBinding": "取消绑定",
|
||||
"addAlternateBinding": "添加备用快捷键绑定",
|
||||
"removeAlternateBinding": "移除备用快捷键绑定",
|
||||
"localShortcutsAppScope": "本地快捷键(应用范围)",
|
||||
"whenConditionTooltip": "必须满足的条件才能触发此快捷键",
|
||||
"unknownMainKey": "未知按键",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "打开全局搜索",
|
||||
"openTheAppWindowCreateANewNote": "打开应用程序窗口并创建一个新笔记",
|
||||
"displayShortcutList": "显示快捷键列表",
|
||||
"closeOpenedDialogOverlay": "关闭打开的对话框/覆盖;关闭准备复制或移动的项目,取消选择项目",
|
||||
"openCloseSelectedFileInQuickView": "在快速查看窗口中打开/关闭所选文件。支持:图像、视频、音频、PDF、纯文本",
|
||||
"closeOpenedDialogOverlay": "关闭对话框 / 浮层 / 菜单 / 选择",
|
||||
"openCloseSelectedFileInQuickView": "在快速查看窗口中打开 / 关闭所选文件",
|
||||
"toggleFullScreen": "切换全屏",
|
||||
"uiZoomIncrease": "用户界面缩放增加",
|
||||
"uiZoomDecrease": "用户界面缩放减少",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Globale Verknüpfungen lösen Aktionen aus, auch wenn die App nicht fokussiert ist.",
|
||||
"changeShortcut": "Verknüpfung ändern",
|
||||
"resetShortcut": "Verknüpfung zurücksetzen",
|
||||
"unassignBinding": "Zuweisung aufheben",
|
||||
"addAlternateBinding": "Alternative Tastenkombination hinzufügen",
|
||||
"removeAlternateBinding": "Alternative Tastenkombination entfernen",
|
||||
"localShortcutsAppScope": "Lokale Verknüpfungen (Anwendungsbereich)",
|
||||
"whenConditionTooltip": "Bedingungen, die erfüllt sein müssen, damit diese Verknüpfung ausgelöst wird",
|
||||
"unknownMainKey": "Unbekannte Taste",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Globale Suche öffnen",
|
||||
"openTheAppWindowCreateANewNote": "Öffne das App-Fenster und erstelle eine neue Notiz",
|
||||
"displayShortcutList": "Kurzwahlliste anzeigen",
|
||||
"closeOpenedDialogOverlay": "Geöffneten Dialog / Overlay schließen; zum Kopieren oder Verschieben vorbereitete Elemente verwerfen, Elemente abwählen",
|
||||
"openCloseSelectedFileInQuickView": "Ausgewählte Datei im Schnellansichtsfenster öffnen / schließen. Unterstützt: Bilder, Videos, Audio, PDF, einfacher Text",
|
||||
"closeOpenedDialogOverlay": "Dialog / Overlay / Menü / Auswahl schließen",
|
||||
"openCloseSelectedFileInQuickView": "Ausgewählte Datei im Schnellansichtsfenster öffnen / schließen",
|
||||
"toggleFullScreen": "Vollbild umschalten",
|
||||
"uiZoomIncrease": "UI-Zoom erhöhen",
|
||||
"uiZoomDecrease": "UI-Zoom verringern",
|
||||
|
||||
@@ -808,6 +808,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Global shortcuts will trigger actions even when the app is not focused.",
|
||||
"changeShortcut": "Change shortcut",
|
||||
"resetShortcut": "Reset shortcut",
|
||||
"unassignBinding": "Unassign binding",
|
||||
"addAlternateBinding": "Add alternate binding",
|
||||
"removeAlternateBinding": "Remove alternate binding",
|
||||
"localShortcutsAppScope": "Local shortcuts (app scope)",
|
||||
"whenConditionTooltip": "Conditions that must be met for this shortcut to trigger",
|
||||
"unknownMainKey": "Unknown key",
|
||||
@@ -827,8 +830,8 @@
|
||||
"openGlobalSearch": "Open global search",
|
||||
"openTheAppWindowCreateANewNote": "Open the app window & create a new note",
|
||||
"displayShortcutList": "Display shortcut list",
|
||||
"closeOpenedDialogOverlay": "Close opened dialog / overlay; dismiss items prepared for copying or moving, deselect items",
|
||||
"openCloseSelectedFileInQuickView": "Open / close selected file in Quick View window. Supported: images, videos, audio, PDF, plain text",
|
||||
"closeOpenedDialogOverlay": "Dismiss dialog / overlay / menu / selection",
|
||||
"openCloseSelectedFileInQuickView": "Open / close selected file in Quick View window",
|
||||
"toggleFullScreen": "Toggle full screen",
|
||||
"uiZoomIncrease": "UI zoom increase",
|
||||
"uiZoomDecrease": "UI zoom decrease",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Los accesos directos globales activarán acciones incluso cuando la aplicación no esté enfocada",
|
||||
"changeShortcut": "Cambiar acceso directo",
|
||||
"resetShortcut": "Restablecer acceso directo",
|
||||
"unassignBinding": "Desasignar atajo",
|
||||
"addAlternateBinding": "Añadir acceso directo alternativo",
|
||||
"removeAlternateBinding": "Quitar acceso directo alternativo",
|
||||
"localShortcutsAppScope": "Accesos directos locales (alcance de la aplicación)",
|
||||
"whenConditionTooltip": "Condiciones que deben cumplirse para que este acceso directo se active",
|
||||
"unknownMainKey": "Tecla desconocida",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Abrir búsqueda global",
|
||||
"openTheAppWindowCreateANewNote": "Abre la ventana de la aplicación y crea una nueva nota",
|
||||
"displayShortcutList": "Mostrar lista de accesos directos",
|
||||
"closeOpenedDialogOverlay": "Cerrar cuadro de diálogo abierto/superposición; descartar elementos preparados para copiar o mover, anular la selección de elementos",
|
||||
"openCloseSelectedFileInQuickView": "Abrir/cerrar el archivo seleccionado en la ventana Vista rápida. Compatible: imágenes, videos, audio, PDF, texto sin formato",
|
||||
"closeOpenedDialogOverlay": "Cerrar diálogo / superposición / menú / selección",
|
||||
"openCloseSelectedFileInQuickView": "Abrir / cerrar archivo seleccionado en ventana Vista rápida",
|
||||
"toggleFullScreen": "Alternar pantalla completa",
|
||||
"uiZoomIncrease": "Aumento del zoom de la interfaz de usuario",
|
||||
"uiZoomDecrease": "Disminución del zoom de la interfaz de usuario",
|
||||
|
||||
@@ -768,6 +768,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "میانبرهای جهانی حتی زمانی که برنامه متمرکز نشده باشد، عملکردها را فعال می کند.",
|
||||
"changeShortcut": "تغییر میانبر",
|
||||
"resetShortcut": "بازنشانی میانبر",
|
||||
"unassignBinding": "لغو انتساب",
|
||||
"addAlternateBinding": "افزودن میانبر جایگزین",
|
||||
"removeAlternateBinding": "حذف میانبر جایگزین",
|
||||
"localShortcutsAppScope": "میانبرهای محلی (حوزه برنامه)",
|
||||
"whenConditionTooltip": "شرایطی که باید برای فعال شدن این میانبر رعایت شوند",
|
||||
"unknownMainKey": "کلید ناشناخته",
|
||||
@@ -787,8 +790,8 @@
|
||||
"openGlobalSearch": "باز کردن جستجوی جهانی",
|
||||
"openTheAppWindowCreateANewNote": "پنجره برنامه را باز کنید و یک یادداشت جدید ایجاد کنید",
|
||||
"displayShortcutList": "نمایش لیست میانبر",
|
||||
"closeOpenedDialogOverlay": "بستن گفتگو / همپوشانی باز شده؛ موارد آماده شده برای کپی یا جابجایی را رد کنید، موارد را لغو انتخاب کنید",
|
||||
"openCloseSelectedFileInQuickView": "باز کردن / بستن فایل انتخاب شده در پنجره نمایش سریع. پشتیبانی شده: تصاویر، فیلم ها، صدا، PDF، متن ساده",
|
||||
"closeOpenedDialogOverlay": "بستن گفتگو / همپوشانی / منو / انتخاب",
|
||||
"openCloseSelectedFileInQuickView": "باز کردن / بستن فایل انتخابشده در پنجره نمایش سریع",
|
||||
"toggleFullScreen": "تغییر تمام صفحه",
|
||||
"uiZoomIncrease": "افزایش بزرگنمایی UI",
|
||||
"uiZoomDecrease": "کاهش زوم UI",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Les raccourcis globaux déclencheront des actions même lorsque l'application n'est pas ciblée.",
|
||||
"changeShortcut": "Changer de raccourci",
|
||||
"resetShortcut": "Réinitialiser le raccourci",
|
||||
"unassignBinding": "Annuler l'attribution",
|
||||
"addAlternateBinding": "Ajouter un raccourci alternatif",
|
||||
"removeAlternateBinding": "Supprimer le raccourci alternatif",
|
||||
"localShortcutsAppScope": "Raccourcis locaux (portée de l'application)",
|
||||
"whenConditionTooltip": "Conditions qui doivent être remplies pour que ce raccourci se déclenche",
|
||||
"unknownMainKey": "Touche inconnue",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Ouvrir la recherche globale",
|
||||
"openTheAppWindowCreateANewNote": "Ouvrez la fenêtre de l'application et créez une nouvelle note",
|
||||
"displayShortcutList": "Afficher la liste des raccourcis",
|
||||
"closeOpenedDialogOverlay": "Fermer la boîte de dialogue / la superposition ouverte ; ignorer les éléments préparés pour la copie ou le déplacement, désélectionner les éléments",
|
||||
"openCloseSelectedFileInQuickView": "Ouvrir/fermer le fichier sélectionné dans la fenêtre Quick View. Pris en charge : images, vidéos, audio, PDF, texte brut",
|
||||
"closeOpenedDialogOverlay": "Fermer dialogue / superposition / menu / sélection",
|
||||
"openCloseSelectedFileInQuickView": "Ouvrir / fermer le fichier sélectionné dans la fenêtre Quick View",
|
||||
"toggleFullScreen": "Passer en plein écran",
|
||||
"uiZoomIncrease": "Augmentation du zoom de l'interface utilisateur",
|
||||
"uiZoomDecrease": "diminution du zoom de l'interface utilisateur",
|
||||
|
||||
@@ -808,6 +808,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "ऐप फोकस न होने पर भी वैश्विक शॉर्टकट कार्रवाइयों को ट्रिगर करेगा।",
|
||||
"changeShortcut": "शॉर्टकट बदलें",
|
||||
"resetShortcut": "शॉर्टकट रीसेट करें",
|
||||
"unassignBinding": "बाइंडिंग हटाएँ",
|
||||
"addAlternateBinding": "वैकल्पिक शॉर्टकट जोड़ें",
|
||||
"removeAlternateBinding": "वैकल्पिक शॉर्टकट हटाएँ",
|
||||
"localShortcutsAppScope": "स्थानीय शॉर्टकट (ऐप स्कोप)",
|
||||
"whenConditionTooltip": "इस शॉर्टकट को ट्रिगर करने के लिए शर्तें पूरी होनी चाहिए",
|
||||
"unknownMainKey": "अज्ञात कुंजी",
|
||||
@@ -827,8 +830,8 @@
|
||||
"openGlobalSearch": "वैश्विक खोज खोलें",
|
||||
"openTheAppWindowCreateANewNote": "ऐप विंडो खोलें और एक नया नोट बनाएं",
|
||||
"displayShortcutList": "शॉर्टकट सूची प्रदर्शित करें",
|
||||
"closeOpenedDialogOverlay": "खोले गए संवाद/ओवरले को बंद करें; कॉपी करने या स्थानांतरित करने के लिए तैयार किए गए आइटम को ख़ारिज करें, आइटम का चयन रद्द करें",
|
||||
"openCloseSelectedFileInQuickView": "त्वरित दृश्य विंडो में चयनित फ़ाइल खोलें/बंद करें। समर्थित: चित्र, वीडियो, ऑडियो, पीडीएफ, सादा पाठ",
|
||||
"closeOpenedDialogOverlay": "संवाद / ओवरले / मेनू / चयन बंद करें",
|
||||
"openCloseSelectedFileInQuickView": "त्वरित दृश्य विंडो में चयनित फ़ाइल खोलें / बंद करें",
|
||||
"toggleFullScreen": "संपूर्ण स्क्रीन टॉगल करें",
|
||||
"uiZoomIncrease": "यूआई ज़ूम वृद्धि",
|
||||
"uiZoomDecrease": "यूआई ज़ूम में कमी",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Le scorciatoie globali attivano azioni anche quando l'app non è focalizzata.",
|
||||
"changeShortcut": "Cambia scorciatoia",
|
||||
"resetShortcut": "Reimposta scorciatoia",
|
||||
"unassignBinding": "Rimuovi assegnazione",
|
||||
"addAlternateBinding": "Aggiungi scorciatoia alternativa",
|
||||
"removeAlternateBinding": "Rimuovi scorciatoia alternativa",
|
||||
"localShortcutsAppScope": "Scorciatoie locali (sistema)",
|
||||
"whenConditionTooltip": "Condizioni che devono essere soddisfatte affinché questa scorciatoia si attivi",
|
||||
"unknownMainKey": "Tasto sconosciuto",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Apri la ricerca globale",
|
||||
"openTheAppWindowCreateANewNote": "Apri la finestra dell'app e crea una nuova nota",
|
||||
"displayShortcutList": "Mostra la lista delle scorciatoie",
|
||||
"closeOpenedDialogOverlay": "Chiudi finestra di dialogo aperta / sovrimpressione; elimina gli elementi preparati per la copia o lo spostamento, deseleziona gli elementi",
|
||||
"openCloseSelectedFileInQuickView": "Apri / chiudi il file selezionato nella finestra Visualizzazione Rapida. File supportati: immagini, video, audio, PDF, testo normale",
|
||||
"closeOpenedDialogOverlay": "Chiudi dialogo / sovrapposizione / menu / selezione",
|
||||
"openCloseSelectedFileInQuickView": "Apri / chiudi il file selezionato nella finestra Quick View",
|
||||
"toggleFullScreen": "Attiva lo schermo intero",
|
||||
"uiZoomIncrease": "Aumento zoom della UI",
|
||||
"uiZoomDecrease": "Diminuzione zoom della UI",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "グローバル ショートカットは、アプリがフォーカスされていなくてもアクションをトリガーします。",
|
||||
"changeShortcut": "ショートカットを変更",
|
||||
"resetShortcut": "ショートカットをリセット",
|
||||
"unassignBinding": "割り当てを解除",
|
||||
"addAlternateBinding": "代替キーバインドを追加",
|
||||
"removeAlternateBinding": "代替キーバインドを削除",
|
||||
"localShortcutsAppScope": "ローカル ショートカット (アプリ スコープ)",
|
||||
"whenConditionTooltip": "このショートカットをトリガーするために満たすべき条件",
|
||||
"unknownMainKey": "不明なキー",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "グローバル検索を開く",
|
||||
"openTheAppWindowCreateANewNote": "アプリ ウィンドウを開いて新しいメモを作成",
|
||||
"displayShortcutList": "ショートカット一覧を表示",
|
||||
"closeOpenedDialogOverlay": "開いているダイアログ/オーバーレイを閉じます。コピーまたは移動の準備ができているアイテムを閉じ、アイテムの選択を解除します",
|
||||
"openCloseSelectedFileInQuickView": "クイック ビュー ウィンドウで選択したファイルを開く/閉じる。サポート対象: 画像、動画、音声、PDF、プレーン テキスト",
|
||||
"closeOpenedDialogOverlay": "ダイアログ / オーバーレイ / メニュー / 選択を閉じる",
|
||||
"openCloseSelectedFileInQuickView": "クイック ビュー ウィンドウで選択したファイルを開く / 閉じる",
|
||||
"toggleFullScreen": "フルスクリーン切り替え",
|
||||
"uiZoomIncrease": "UI ズーム増加",
|
||||
"uiZoomDecrease": "UIズーム減少",
|
||||
|
||||
@@ -771,6 +771,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Atalhos globais acionarão ações mesmo quando o aplicativo não estiver em foco",
|
||||
"changeShortcut": "Alterar atalho",
|
||||
"resetShortcut": "Redefinir atalho",
|
||||
"unassignBinding": "Desatribuir atalho",
|
||||
"addAlternateBinding": "Adicionar atalho alternativo",
|
||||
"removeAlternateBinding": "Remover atalho alternativo",
|
||||
"localShortcutsAppScope": "Atalhos locais (escopo do aplicativo)",
|
||||
"whenConditionTooltip": "Condições que devem ser atendidas para que este atalho seja acionado",
|
||||
"unknownMainKey": "Tecla desconhecida",
|
||||
@@ -790,8 +793,8 @@
|
||||
"openGlobalSearch": "Abrir pesquisa global",
|
||||
"openTheAppWindowCreateANewNote": "Abra a janela do aplicativo e crie uma nova nota",
|
||||
"displayShortcutList": "Exibir lista de atalhos",
|
||||
"closeOpenedDialogOverlay": "Fechar caixa de diálogo/sobreposição aberta; descartar itens preparados para copiar ou mover, desmarcar itens",
|
||||
"openCloseSelectedFileInQuickView": "Abrir/fechar o arquivo selecionado na janela Quick View. Suportado: imagens, vídeos, áudio, PDF, texto simples",
|
||||
"closeOpenedDialogOverlay": "Fechar diálogo / sobreposição / menu / seleção",
|
||||
"openCloseSelectedFileInQuickView": "Abrir / fechar arquivo selecionado na janela Quick View",
|
||||
"toggleFullScreen": "Alternar tela inteira",
|
||||
"uiZoomIncrease": "Aumento de zoom da interface do usuário",
|
||||
"uiZoomDecrease": "Diminuir zoom da interface do usuário",
|
||||
|
||||
@@ -808,6 +808,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Глобальные шорткаты будут запускать действия, даже если приложение не сфокусировано.",
|
||||
"changeShortcut": "Изменить шорткат",
|
||||
"resetShortcut": "Сбросить шорткат",
|
||||
"unassignBinding": "Снять назначение",
|
||||
"addAlternateBinding": "Добавить дополнительную привязку",
|
||||
"removeAlternateBinding": "Удалить дополнительную привязку",
|
||||
"localShortcutsAppScope": "Локальные шорткаты (в приложении)",
|
||||
"whenConditionTooltip": "Условия, которые должны быть выполнены для срабатывания этого шортката",
|
||||
"unknownMainKey": "Неизвестная клавиша",
|
||||
@@ -827,8 +830,8 @@
|
||||
"openGlobalSearch": "Открыть глобальный поиск",
|
||||
"openTheAppWindowCreateANewNote": "Открыть окно приложения и создать новую заметку",
|
||||
"displayShortcutList": "Показать список шорткатов",
|
||||
"closeOpenedDialogOverlay": "Закрыть открытый диалог/оверлей; закрыть элементы, подготовленные для копирования или перемещения, отменить выбор элементов",
|
||||
"openCloseSelectedFileInQuickView": "Открыть/закрыть выбранный файл в окне быстрого просмотра. Поддерживаются: изображения, видео, аудио, PDF, обычный текст",
|
||||
"closeOpenedDialogOverlay": "Закрыть диалог / оверлей / меню / выделение",
|
||||
"openCloseSelectedFileInQuickView": "Открыть / закрыть выбранный файл в окне быстрого просмотра",
|
||||
"toggleFullScreen": "Включить полноэкранный режим",
|
||||
"uiZoomIncrease": "Увеличение масштаба интерфейса",
|
||||
"uiZoomDecrease": "уменьшение масштаба интерфейса",
|
||||
|
||||
@@ -808,6 +808,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Globalne bližnjice bodo sprožile dejanja tudi, ko aplikacija ni v fokusu.",
|
||||
"changeShortcut": "Spremeni bližnjico",
|
||||
"resetShortcut": "Ponastavi bližnjico",
|
||||
"unassignBinding": "Odstrani dodelitev",
|
||||
"addAlternateBinding": "Dodaj nadomestno bližnjico",
|
||||
"removeAlternateBinding": "Odstrani nadomestno bližnjico",
|
||||
"localShortcutsAppScope": "Lokalne bližnjice (obseg aplikacije)",
|
||||
"whenConditionTooltip": "Pogoji, ki morajo biti izpolnjeni, da se bližnjica sproži",
|
||||
"unknownMainKey": "Neznana tipka",
|
||||
@@ -827,8 +830,8 @@
|
||||
"openGlobalSearch": "Odpri globalno iskanje",
|
||||
"openTheAppWindowCreateANewNote": "Odpri okno aplikacije & ustvari novo opombo",
|
||||
"displayShortcutList": "Prikaži seznam bližnjic",
|
||||
"closeOpenedDialogOverlay": "Zapri odprt dialog / prekrivanje; odkloni predmete pripravljen za kopiranje ali premik, odznači predmete",
|
||||
"openCloseSelectedFileInQuickView": "Odpri / zapri izbrano datoteko v oknu Quick View. Podprto: slike, videi, zvok, PDF, besedilo",
|
||||
"closeOpenedDialogOverlay": "Zapri dialog / prekrivanje / meni / izbiro",
|
||||
"openCloseSelectedFileInQuickView": "Odpri / zapri izbrano datoteko v oknu Quick View",
|
||||
"toggleFullScreen": "Preklopi celozaslonski način",
|
||||
"uiZoomIncrease": "Povečaj UI povečavo",
|
||||
"uiZoomDecrease": "Zmanjšaj UI povečavo",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Genel kısayollar, uygulama odaklanmadığında bile eylemleri tetikler.",
|
||||
"changeShortcut": "Kısayolu değiştir",
|
||||
"resetShortcut": "Kısayolu sıfırla",
|
||||
"unassignBinding": "Atamayı kaldır",
|
||||
"addAlternateBinding": "Alternatif kısayol ekle",
|
||||
"removeAlternateBinding": "Alternatif kısayolu kaldır",
|
||||
"localShortcutsAppScope": "Yerel kısayollar (uygulama kapsamı)",
|
||||
"whenConditionTooltip": "Bu kısayolun tetiklenmesi için karşılanması gereken koşullar",
|
||||
"unknownMainKey": "Bilinmeyen tuş",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Global aramayı aç",
|
||||
"openTheAppWindowCreateANewNote": "Uygulama penceresini aç ve yeni bir not oluştur",
|
||||
"displayShortcutList": "Kısayol listesini görüntüle",
|
||||
"closeOpenedDialogOverlay": "Açılan iletişim kutusunu / yer paylaşımını kapat; kopyalamak veya taşımak için hazırlanan öğeleri kapat, öğelerin seçimini kaldır",
|
||||
"openCloseSelectedFileInQuickView": "Hızlı Görünüm penceresinde seçilen dosyayı aç / kapat. Desteklenen: resimler, videolar, ses, PDF, düz metin",
|
||||
"closeOpenedDialogOverlay": "İletişim kutusu / yer paylaşımı / menü / seçimi kapat",
|
||||
"openCloseSelectedFileInQuickView": "Hızlı Görünüm penceresinde seçilen dosyayı aç / kapat",
|
||||
"toggleFullScreen": "Tam ekrana geç",
|
||||
"uiZoomIncrease": "UI yakınlaştırma artışı",
|
||||
"uiZoomDecrease": "UI yakınlaştırma azaltma",
|
||||
|
||||
@@ -808,6 +808,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "ایپ فوکس میں نہ ہو تو بھی عالمی شارٹ کٹس کام کرتے ہیں۔",
|
||||
"changeShortcut": "شارٹ کٹ بدلیں",
|
||||
"resetShortcut": "شارٹ کٹ ری سیٹ کریں",
|
||||
"unassignBinding": "تفویض ہٹائیں",
|
||||
"addAlternateBinding": "متبادل شارٹ کٹ شامل کریں",
|
||||
"removeAlternateBinding": "متبادل شارٹ کٹ ہٹائیں",
|
||||
"localShortcutsAppScope": "مقامی شارٹ کٹس (ایپ سطح)",
|
||||
"whenConditionTooltip": "اس شارٹ کٹ کے لیے پوری ہونے والی شرائط",
|
||||
"unknownMainKey": "نامعلوم کلید",
|
||||
@@ -827,8 +830,8 @@
|
||||
"openGlobalSearch": "عالمی تلاش کھولیں",
|
||||
"openTheAppWindowCreateANewNote": "ایپ کھولیں اور نیا نوٹ بنائیں",
|
||||
"displayShortcutList": "شارٹ کٹ فہرست دکھائیں",
|
||||
"closeOpenedDialogOverlay": "ڈائیلاگ/اوورلے بند کریں؛ کاپی/منتقل کے لیے تیار اشیاء منسوخ کریں، انتخاب ہٹائیں",
|
||||
"openCloseSelectedFileInQuickView": "فوری نظارے میں منتخب فائل کھولیں / بند کریں۔ معاون: تصویر، ویڈیو، آڈیو، PDF، سادہ متن",
|
||||
"closeOpenedDialogOverlay": "ڈائیلاگ / اوورلے / مینو / انتخاب برخاست کریں",
|
||||
"openCloseSelectedFileInQuickView": "فوری نظارے میں منتخب فائل کھولیں / بند کریں",
|
||||
"toggleFullScreen": "مکمل اسکرین ٹوگل کریں",
|
||||
"uiZoomIncrease": "UI زوم بڑھائیں",
|
||||
"uiZoomDecrease": "UI زوم کم کریں",
|
||||
|
||||
@@ -769,6 +769,9 @@
|
||||
"globalShortcutsTriggerActionsWhenNotFocused": "Các phím tắt chung sẽ kích hoạt hành động ngay cả khi ứng dụng không được đặt tiêu điểm.",
|
||||
"changeShortcut": "Thay đổi phím tắt",
|
||||
"resetShortcut": "Đặt lại phím tắt",
|
||||
"unassignBinding": "Hủy gán phím tắt",
|
||||
"addAlternateBinding": "Thêm phím tắt thay thế",
|
||||
"removeAlternateBinding": "Xóa phím tắt thay thế",
|
||||
"localShortcutsAppScope": "Phím tắt cục bộ (phạm vi ứng dụng)",
|
||||
"whenConditionTooltip": "Các điều kiện phải được đáp ứng để phím tắt này kích hoạt",
|
||||
"unknownMainKey": "Phím không xác định",
|
||||
@@ -788,8 +791,8 @@
|
||||
"openGlobalSearch": "Mở tìm kiếm toàn cầu",
|
||||
"openTheAppWindowCreateANewNote": "Mở cửa sổ ứng dụng và tạo ghi chú mới",
|
||||
"displayShortcutList": "Hiển thị danh sách phím tắt",
|
||||
"closeOpenedDialogOverlay": "Đóng hộp thoại/lớp phủ đã mở; loại bỏ các mục chuẩn bị sao chép hoặc di chuyển, bỏ chọn các mục",
|
||||
"openCloseSelectedFileInQuickView": "Mở/đóng tệp đã chọn trong cửa sổ Xem nhanh. Được hỗ trợ: hình ảnh, video, âm thanh, PDF, văn bản thuần túy",
|
||||
"closeOpenedDialogOverlay": "Đóng hộp thoại / lớp phủ / menu / lựa chọn",
|
||||
"openCloseSelectedFileInQuickView": "Mở / đóng tệp đã chọn trong cửa sổ Xem nhanh",
|
||||
"toggleFullScreen": "Bật chế độ toàn màn hình",
|
||||
"uiZoomIncrease": "Tăng thu phóng giao diện người dùng",
|
||||
"uiZoomDecrease": "Giảm thu phóng giao diện người dùng",
|
||||
|
||||
@@ -25,6 +25,12 @@ import {
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuTrigger,
|
||||
} from '@/components/ui/context-menu';
|
||||
import {
|
||||
KeyboardIcon,
|
||||
RotateCcwIcon,
|
||||
@@ -65,6 +71,7 @@ import {
|
||||
formatCaptureChordLabel,
|
||||
isModifierPhysicalKeyCode,
|
||||
resolveShortcutKeyFromKeyboardEvent,
|
||||
type ShortcutDefinition,
|
||||
type ShortcutId,
|
||||
type ShortcutKeys,
|
||||
} from '@/stores/runtime/shortcuts';
|
||||
@@ -134,7 +141,7 @@ const globalShortcutIcons: Record<GlobalShortcutId, Component> = {
|
||||
const extensionsStorageStore = useExtensionsStorageStore();
|
||||
|
||||
const isDialogOpen = ref(false);
|
||||
const editingShortcutId = ref<ShortcutId | null>(null);
|
||||
const editingAppShortcutDefinition = ref<ShortcutDefinition | null>(null);
|
||||
const editingGlobalShortcutId = ref<GlobalShortcutId | null>(null);
|
||||
const recordedKeys = ref<ShortcutKeys | null>(null);
|
||||
const isRecording = ref(false);
|
||||
@@ -408,8 +415,8 @@ const editingDefinition = computed(() => {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!editingShortcutId.value) return null;
|
||||
return shortcutsStore.getShortcutDefinition(editingShortcutId.value);
|
||||
if (!editingAppShortcutDefinition.value) return null;
|
||||
return editingAppShortcutDefinition.value;
|
||||
});
|
||||
|
||||
const currentEditingShortcutLabel = computed(() => {
|
||||
@@ -417,8 +424,8 @@ const currentEditingShortcutLabel = computed(() => {
|
||||
return globalShortcutsStore.getShortcutLabel(editingGlobalShortcutId.value);
|
||||
}
|
||||
|
||||
if (!editingShortcutId.value) return '';
|
||||
return shortcutsStore.getShortcutLabel(editingShortcutId.value);
|
||||
if (!editingAppShortcutDefinition.value) return '';
|
||||
return shortcutsStore.getShortcutBindingLabel(editingAppShortcutDefinition.value);
|
||||
});
|
||||
|
||||
const recordedKeysLabel = computed(() => {
|
||||
@@ -438,20 +445,57 @@ function createUnassignedShortcutKeys(): ShortcutKeys {
|
||||
return { key: '' };
|
||||
}
|
||||
|
||||
async function contextUnassignShortcutBinding(definition: ShortcutDefinition): Promise<void> {
|
||||
await shortcutsStore.setShortcut(
|
||||
definition.id,
|
||||
createUnassignedShortcutKeys(),
|
||||
definition.bindingSlot ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
async function contextAddAlternateShortcutBinding(definition: ShortcutDefinition): Promise<void> {
|
||||
await shortcutsStore.addUserAlternateChordBinding(definition.id, definition);
|
||||
}
|
||||
|
||||
async function contextRemoveUserAlternateBinding(definition: ShortcutDefinition): Promise<void> {
|
||||
await shortcutsStore.removeUserAlternateChordBindingRow(definition.id, definition.bindingSlot ?? 0);
|
||||
}
|
||||
|
||||
async function contextResetAppShortcutBinding(definition: ShortcutDefinition): Promise<void> {
|
||||
await shortcutsStore.resetShortcut(definition.id, definition.bindingSlot ?? 0);
|
||||
}
|
||||
|
||||
async function contextResetGlobalShortcut(globalShortcutId: GlobalShortcutId): Promise<void> {
|
||||
await globalShortcutsStore.resetShortcut(globalShortcutId);
|
||||
}
|
||||
|
||||
async function contextUnassignGlobalShortcut(globalShortcutId: GlobalShortcutId): Promise<void> {
|
||||
globalRegisterFailed.value = false;
|
||||
await globalShortcutsStore.unsetShortcut(globalShortcutId);
|
||||
}
|
||||
|
||||
function shortcutKeysConflict(firstKeys: ShortcutKeys, secondKeys: ShortcutKeys): boolean {
|
||||
return Boolean(firstKeys.key && secondKeys.key) && shortcutKeysMatch(firstKeys, secondKeys);
|
||||
}
|
||||
|
||||
function shortcutDefinitionsMatchSlot(
|
||||
firstDefinition: ShortcutDefinition,
|
||||
secondDefinition: ShortcutDefinition,
|
||||
): boolean {
|
||||
return firstDefinition.id === secondDefinition.id
|
||||
&& (firstDefinition.bindingSlot ?? 0) === (secondDefinition.bindingSlot ?? 0);
|
||||
}
|
||||
|
||||
function findConflictingAppShortcuts(
|
||||
keys: ShortcutKeys,
|
||||
excludeShortcutId?: ShortcutId,
|
||||
excludeDefinition?: ShortcutDefinition,
|
||||
) {
|
||||
return shortcutsStore.definitions.filter((definition) => {
|
||||
if (definition.id === excludeShortcutId) {
|
||||
if (excludeDefinition && shortcutDefinitionsMatchSlot(definition, excludeDefinition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return shortcutKeysConflict(shortcutsStore.getShortcutKeys(definition.id), keys);
|
||||
return shortcutKeysConflict(shortcutsStore.resolveShortcutBindingKeys(definition), keys);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -551,7 +595,7 @@ const conflictLabel = computed(() => {
|
||||
|
||||
const conflictingAppShortcut = findConflictingAppShortcuts(
|
||||
recordedKeys.value,
|
||||
editingShortcutId.value ?? undefined,
|
||||
editingAppShortcutDefinition.value ?? undefined,
|
||||
)[0] ?? null;
|
||||
|
||||
if (conflictingAppShortcut) {
|
||||
@@ -572,11 +616,10 @@ const conflictLabel = computed(() => {
|
||||
|
||||
const hasConflict = computed(() => conflictLabel.value !== null);
|
||||
|
||||
function openShortcutEditor(shortcutId: ShortcutId) {
|
||||
const definition = shortcutsStore.getShortcutDefinition(shortcutId);
|
||||
if (definition?.isReadOnly) return;
|
||||
function openShortcutEditor(definition: ShortcutDefinition) {
|
||||
if (definition.isReadOnly) return;
|
||||
|
||||
editingShortcutId.value = shortcutId;
|
||||
editingAppShortcutDefinition.value = definition;
|
||||
editingExtensionKeybinding.value = null;
|
||||
editingGlobalShortcutId.value = null;
|
||||
recordedKeys.value = null;
|
||||
@@ -592,7 +635,7 @@ function openShortcutEditor(shortcutId: ShortcutId) {
|
||||
function openGlobalShortcutEditor(globalShortcutId: GlobalShortcutId) {
|
||||
editingGlobalShortcutId.value = globalShortcutId;
|
||||
editingExtensionKeybinding.value = null;
|
||||
editingShortcutId.value = null;
|
||||
editingAppShortcutDefinition.value = null;
|
||||
recordedKeys.value = null;
|
||||
globalRegisterFailed.value = false;
|
||||
isRecording.value = true;
|
||||
@@ -688,53 +731,45 @@ async function saveShortcut(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!editingShortcutId.value) return false;
|
||||
await shortcutsStore.setShortcut(editingShortcutId.value, recordedKeys.value);
|
||||
if (!editingAppShortcutDefinition.value) return false;
|
||||
await shortcutsStore.setShortcut(
|
||||
editingAppShortcutDefinition.value.id,
|
||||
recordedKeys.value,
|
||||
editingAppShortcutDefinition.value.bindingSlot ?? 0,
|
||||
);
|
||||
isDialogOpen.value = false;
|
||||
editingShortcutId.value = null;
|
||||
editingAppShortcutDefinition.value = null;
|
||||
recordedKeys.value = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
async function resetShortcut() {
|
||||
if (editingGlobalShortcutId.value) {
|
||||
await globalShortcutsStore.resetShortcut(editingGlobalShortcutId.value);
|
||||
isDialogOpen.value = false;
|
||||
editingGlobalShortcutId.value = null;
|
||||
recordedKeys.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!editingShortcutId.value) return;
|
||||
await shortcutsStore.resetShortcut(editingShortcutId.value);
|
||||
isDialogOpen.value = false;
|
||||
editingShortcutId.value = null;
|
||||
recordedKeys.value = null;
|
||||
}
|
||||
|
||||
async function resetAllShortcuts() {
|
||||
await shortcutsStore.resetAllShortcuts();
|
||||
}
|
||||
|
||||
async function clearConflictingAppShortcuts(
|
||||
keys: ShortcutKeys,
|
||||
excludeShortcutId?: ShortcutId,
|
||||
excludeDefinition?: ShortcutDefinition,
|
||||
): Promise<ShortcutConflictRestore[]> {
|
||||
const conflictingShortcuts = findConflictingAppShortcuts(keys, excludeShortcutId);
|
||||
const conflictingShortcuts = findConflictingAppShortcuts(keys, excludeDefinition);
|
||||
const restoreActions: ShortcutConflictRestore[] = [];
|
||||
|
||||
for (const shortcut of conflictingShortcuts) {
|
||||
const previousKeys = shortcutsStore.getShortcutKeys(shortcut.id);
|
||||
const previousSource = shortcutsStore.getSource(shortcut.id);
|
||||
const previousKeys = shortcutsStore.resolveShortcutBindingKeys(shortcut);
|
||||
const previousSource = shortcutsStore.getShortcutBindingSource(shortcut);
|
||||
restoreActions.push(async () => {
|
||||
if (previousSource === 'system') {
|
||||
await shortcutsStore.resetShortcut(shortcut.id);
|
||||
await shortcutsStore.resetShortcut(shortcut.id, shortcut.bindingSlot ?? 0);
|
||||
return;
|
||||
}
|
||||
|
||||
await shortcutsStore.setShortcut(shortcut.id, previousKeys);
|
||||
await shortcutsStore.setShortcut(shortcut.id, previousKeys, shortcut.bindingSlot ?? 0);
|
||||
});
|
||||
await shortcutsStore.setShortcut(shortcut.id, createUnassignedShortcutKeys());
|
||||
await shortcutsStore.setShortcut(
|
||||
shortcut.id,
|
||||
createUnassignedShortcutKeys(),
|
||||
shortcut.bindingSlot ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
return restoreActions;
|
||||
@@ -861,7 +896,7 @@ async function clearConflictingShortcuts(keys: ShortcutKeys): Promise<ShortcutCo
|
||||
return restoreActions;
|
||||
}
|
||||
|
||||
restoreActions.push(...await clearConflictingAppShortcuts(keys, editingShortcutId.value ?? undefined));
|
||||
restoreActions.push(...await clearConflictingAppShortcuts(keys, editingAppShortcutDefinition.value ?? undefined));
|
||||
restoreActions.push(...await clearConflictingLocalExtensionKeybindings(keys, editingExtensionKeybinding.value?.commandId));
|
||||
return restoreActions;
|
||||
}
|
||||
@@ -886,26 +921,26 @@ async function replaceShortcut() {
|
||||
}
|
||||
}
|
||||
|
||||
function getConditionsLabel(shortcutId: ShortcutId): string {
|
||||
const definition = shortcutsStore.getShortcutDefinition(shortcutId);
|
||||
if (!definition) return '';
|
||||
function getConditionsLabelFromDefinition(definition: ShortcutDefinition): string {
|
||||
return formatConditionsLabel(definition.conditions);
|
||||
}
|
||||
|
||||
function getSourceLabel(shortcutId: ShortcutId): string {
|
||||
return shortcutsStore.getSource(shortcutId) === 'user' ? t('shortcutsUI.sourceUser') : t('shortcutsUI.sourceSystem');
|
||||
function getShortcutBindingSourceLabel(definition: ShortcutDefinition): string {
|
||||
return shortcutsStore.getShortcutBindingSource(definition) === 'user'
|
||||
? t('shortcutsUI.sourceUser')
|
||||
: t('shortcutsUI.sourceSystem');
|
||||
}
|
||||
|
||||
function getGlobalSourceLabel(globalShortcutId: GlobalShortcutId): string {
|
||||
return globalShortcutsStore.getSource(globalShortcutId) === 'user' ? t('shortcutsUI.sourceUser') : t('shortcutsUI.sourceSystem');
|
||||
}
|
||||
|
||||
function getSourceIcon(shortcutId: ShortcutId): Component {
|
||||
return shortcutsStore.getSource(shortcutId) === 'user' ? UserIcon : SettingsIcon;
|
||||
function getShortcutBindingSourceIcon(definition: ShortcutDefinition): Component {
|
||||
return shortcutsStore.getShortcutBindingSource(definition) === 'user' ? UserIcon : SettingsIcon;
|
||||
}
|
||||
|
||||
function getShortcutListLabel(label: string): string {
|
||||
return label || unassignedShortcutLabel;
|
||||
function isShortcutLabelAssigned(label: string): boolean {
|
||||
return label.trim().length > 0;
|
||||
}
|
||||
|
||||
function openExtensionKeybindingEditor(extensionId: string, commandId: string, commandTitle: string) {
|
||||
@@ -915,7 +950,7 @@ function openExtensionKeybindingEditor(extensionId: string, commandId: string, c
|
||||
commandTitle,
|
||||
scope: 'local',
|
||||
};
|
||||
editingShortcutId.value = null;
|
||||
editingAppShortcutDefinition.value = null;
|
||||
editingGlobalShortcutId.value = null;
|
||||
recordedKeys.value = null;
|
||||
globalRegisterFailed.value = false;
|
||||
@@ -934,7 +969,7 @@ function openExtensionGlobalShortcutEditor(extensionId: string, commandId: strin
|
||||
commandTitle,
|
||||
scope: 'global',
|
||||
};
|
||||
editingShortcutId.value = null;
|
||||
editingAppShortcutDefinition.value = null;
|
||||
editingGlobalShortcutId.value = null;
|
||||
recordedKeys.value = null;
|
||||
globalRegisterFailed.value = false;
|
||||
@@ -985,31 +1020,11 @@ async function saveExtensionKeybinding(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
async function resetExtensionKeybinding() {
|
||||
if (!editingExtensionKeybinding.value) return;
|
||||
|
||||
await extensionsStorageStore.removeKeybindingOverride(
|
||||
editingExtensionKeybinding.value.extensionId,
|
||||
editingExtensionKeybinding.value.commandId,
|
||||
editingExtensionKeybinding.value.scope,
|
||||
);
|
||||
|
||||
if (editingExtensionKeybinding.value.scope === 'local') {
|
||||
extensionsStore.resetKeybindingOverride(editingExtensionKeybinding.value.commandId);
|
||||
}
|
||||
|
||||
await globalShortcutsStore.syncExtensionShortcuts();
|
||||
|
||||
isDialogOpen.value = false;
|
||||
editingExtensionKeybinding.value = null;
|
||||
recordedKeys.value = null;
|
||||
}
|
||||
|
||||
watch(isDialogOpen, (open) => {
|
||||
if (!open) {
|
||||
isRecording.value = false;
|
||||
recordedKeys.value = null;
|
||||
editingShortcutId.value = null;
|
||||
editingAppShortcutDefinition.value = null;
|
||||
editingGlobalShortcutId.value = null;
|
||||
editingExtensionKeybinding.value = null;
|
||||
}
|
||||
@@ -1025,6 +1040,39 @@ watch(isDialogOpen, (open) => {
|
||||
|
||||
const extensionsStore = useExtensionsStore();
|
||||
|
||||
async function resetExtensionKeybindingAt(
|
||||
extensionId: string,
|
||||
commandId: string,
|
||||
scope: 'local' | 'global',
|
||||
): Promise<void> {
|
||||
await extensionsStorageStore.removeKeybindingOverride(extensionId, commandId, scope);
|
||||
|
||||
if (scope === 'local') {
|
||||
extensionsStore.resetKeybindingOverride(commandId);
|
||||
}
|
||||
|
||||
await globalShortcutsStore.syncExtensionShortcuts();
|
||||
}
|
||||
|
||||
async function unassignExtensionKeybindingAt(
|
||||
extensionId: string,
|
||||
commandId: string,
|
||||
scope: 'local' | 'global',
|
||||
): Promise<void> {
|
||||
const override: ExtensionKeybindingOverride = {
|
||||
commandId,
|
||||
scope,
|
||||
keys: createUnassignedShortcutKeys(),
|
||||
};
|
||||
await extensionsStorageStore.setKeybindingOverride(extensionId, override);
|
||||
|
||||
if (scope === 'local') {
|
||||
extensionsStore.applyKeybindingOverride(commandId, createUnassignedShortcutKeys());
|
||||
}
|
||||
|
||||
await globalShortcutsStore.syncExtensionShortcuts();
|
||||
}
|
||||
|
||||
const extensionKeybindings = computed(() => {
|
||||
return extensionsStore.keybindings.map((keybinding) => {
|
||||
const commandIdWithoutPrefix = keybinding.commandId.replace(`${keybinding.extensionId}.`, '');
|
||||
@@ -1114,88 +1162,136 @@ const globalExtensionShortcuts = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__body">
|
||||
<div
|
||||
<ContextMenu
|
||||
v-for="definition in globalShortcutsStore.definitions"
|
||||
:key="definition.id"
|
||||
class="shortcuts-table__row"
|
||||
@click="openGlobalShortcutEditor(definition.id)"
|
||||
>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<component
|
||||
:is="globalShortcutIcons[definition.id]"
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ t(definition.labelKey) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd class="shortcuts-table__kbd">
|
||||
{{ getShortcutListLabel(globalShortcutsStore.getShortcutLabel(definition.id)) }}
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when" />
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': globalShortcutsStore.getSource(definition.id) === 'user',
|
||||
'shortcuts-table__source--system': globalShortcutsStore.getSource(definition.id) === 'system',
|
||||
}"
|
||||
<ContextMenuTrigger as-child>
|
||||
<div
|
||||
class="shortcuts-table__row"
|
||||
@click="openGlobalShortcutEditor(definition.id)"
|
||||
>
|
||||
<component
|
||||
:is="globalShortcutsStore.getSource(definition.id) === 'user' ? UserIcon : SettingsIcon"
|
||||
:size="12"
|
||||
/>
|
||||
{{ getGlobalSourceLabel(definition.id) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<component
|
||||
:is="globalShortcutIcons[definition.id]"
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ t(definition.labelKey) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd
|
||||
v-if="isShortcutLabelAssigned(globalShortcutsStore.getShortcutLabel(definition.id))"
|
||||
class="shortcuts-table__kbd"
|
||||
>
|
||||
{{ globalShortcutsStore.getShortcutLabel(definition.id) }}
|
||||
</kbd>
|
||||
<span
|
||||
v-else
|
||||
class="shortcuts-keybinding-unassigned"
|
||||
>
|
||||
{{ unassignedShortcutLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when" />
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': globalShortcutsStore.getSource(definition.id) === 'user',
|
||||
'shortcuts-table__source--system': globalShortcutsStore.getSource(definition.id) === 'system',
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="globalShortcutsStore.getSource(definition.id) === 'user' ? UserIcon : SettingsIcon"
|
||||
:size="12"
|
||||
/>
|
||||
{{ getGlobalSourceLabel(definition.id) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent class="shortcuts-settings-context-menu">
|
||||
<ContextMenuItem @select="contextResetGlobalShortcut(definition.id)">
|
||||
{{ t('shortcutsUI.resetShortcut') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem @select="contextUnassignGlobalShortcut(definition.id)">
|
||||
{{ t('shortcutsUI.unassignBinding') }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
|
||||
<ContextMenu
|
||||
v-for="shortcut in globalExtensionShortcuts"
|
||||
:key="shortcut.commandId"
|
||||
class="shortcuts-table__row"
|
||||
@click="openExtensionGlobalShortcutEditor(shortcut.extensionId, shortcut.commandId, shortcut.commandTitle)"
|
||||
:key="`${shortcut.extensionId}-${shortcut.commandId}`"
|
||||
>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<BlocksIcon
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ shortcut.commandTitle }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd class="shortcuts-table__kbd">
|
||||
{{ getShortcutListLabel(shortcut.keybindingLabel) }}
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when" />
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': shortcut.source === 'user',
|
||||
'shortcuts-table__source--system': shortcut.source === 'system',
|
||||
}"
|
||||
<ContextMenuTrigger as-child>
|
||||
<div
|
||||
class="shortcuts-table__row"
|
||||
@click="openExtensionGlobalShortcutEditor(shortcut.extensionId, shortcut.commandId, shortcut.commandTitle)"
|
||||
>
|
||||
<component
|
||||
:is="shortcut.source === 'user' ? UserIcon : SettingsIcon"
|
||||
:size="12"
|
||||
/>
|
||||
{{ shortcut.extensionName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<BlocksIcon
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ shortcut.commandTitle }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd
|
||||
v-if="isShortcutLabelAssigned(shortcut.keybindingLabel)"
|
||||
class="shortcuts-table__kbd"
|
||||
>
|
||||
{{ shortcut.keybindingLabel }}
|
||||
</kbd>
|
||||
<span
|
||||
v-else
|
||||
class="shortcuts-keybinding-unassigned"
|
||||
>
|
||||
{{ unassignedShortcutLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when" />
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': shortcut.source === 'user',
|
||||
'shortcuts-table__source--system': shortcut.source === 'system',
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="shortcut.source === 'user' ? UserIcon : SettingsIcon"
|
||||
:size="12"
|
||||
/>
|
||||
{{ shortcut.extensionName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent class="shortcuts-settings-context-menu">
|
||||
<ContextMenuItem
|
||||
@select="resetExtensionKeybindingAt(shortcut.extensionId, shortcut.commandId, 'global')"
|
||||
>
|
||||
{{ t('shortcutsUI.resetShortcut') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
@select="unassignExtensionKeybindingAt(shortcut.extensionId, shortcut.commandId, 'global')"
|
||||
>
|
||||
{{ t('shortcutsUI.unassignBinding') }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1226,66 +1322,105 @@ const globalExtensionShortcuts = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__body">
|
||||
<div
|
||||
<ContextMenu
|
||||
v-for="definition in shortcutsStore.definitions"
|
||||
:key="definition.id"
|
||||
class="shortcuts-table__row"
|
||||
:class="{ 'shortcuts-table__row--readonly': definition.isReadOnly }"
|
||||
@click="openShortcutEditor(definition.id)"
|
||||
:key="`${definition.id}-${definition.bindingSlot ?? 0}`"
|
||||
>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<component
|
||||
:is="shortcutIcons[definition.id]"
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ t(definition.labelKey) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd class="shortcuts-table__kbd">
|
||||
{{ getShortcutListLabel(shortcutsStore.getShortcutLabel(definition.id)) }}
|
||||
</kbd>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when">
|
||||
<Tooltip v-if="getConditionsLabel(definition.id)">
|
||||
<TooltipTrigger as-child>
|
||||
<code class="shortcuts-table__when-code">
|
||||
{{ getConditionsLabel(definition.id) }}
|
||||
</code>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<code class="shortcuts-table__when-tooltip-code">
|
||||
{{ getConditionsLabel(definition.id) }}
|
||||
</code>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
<ContextMenuTrigger
|
||||
as-child
|
||||
:disabled="definition.isReadOnly"
|
||||
>
|
||||
<div
|
||||
class="shortcuts-table__row"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': shortcutsStore.getSource(definition.id) === 'user',
|
||||
'shortcuts-table__source--system': shortcutsStore.getSource(definition.id) === 'system',
|
||||
'shortcuts-table__row--readonly': definition.isReadOnly,
|
||||
'shortcuts-table__row--alternate-chord': definition.alternateChordRow,
|
||||
}"
|
||||
@click="openShortcutEditor(definition)"
|
||||
>
|
||||
<component
|
||||
:is="getSourceIcon(definition.id)"
|
||||
:size="12"
|
||||
/>
|
||||
{{ getSourceLabel(definition.id) }}
|
||||
</span>
|
||||
<LockIcon
|
||||
v-if="definition.isReadOnly"
|
||||
:size="10"
|
||||
class="shortcuts-table__lock-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<component
|
||||
:is="shortcutIcons[definition.id]"
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ t(definition.labelKey) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd
|
||||
v-if="isShortcutLabelAssigned(shortcutsStore.getShortcutBindingLabel(definition))"
|
||||
class="shortcuts-table__kbd"
|
||||
>
|
||||
{{ shortcutsStore.getShortcutBindingLabel(definition) }}
|
||||
</kbd>
|
||||
<span
|
||||
v-else
|
||||
class="shortcuts-keybinding-unassigned"
|
||||
>
|
||||
{{ unassignedShortcutLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when">
|
||||
<Tooltip v-if="getConditionsLabelFromDefinition(definition)">
|
||||
<TooltipTrigger as-child>
|
||||
<code class="shortcuts-table__when-code">
|
||||
{{ getConditionsLabelFromDefinition(definition) }}
|
||||
</code>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top">
|
||||
<code class="shortcuts-table__when-tooltip-code">
|
||||
{{ getConditionsLabelFromDefinition(definition) }}
|
||||
</code>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span
|
||||
class="shortcuts-table__source"
|
||||
:class="{
|
||||
'shortcuts-table__source--user': shortcutsStore.getShortcutBindingSource(definition) === 'user',
|
||||
'shortcuts-table__source--system': shortcutsStore.getShortcutBindingSource(definition) === 'system',
|
||||
}"
|
||||
>
|
||||
<component
|
||||
:is="getShortcutBindingSourceIcon(definition)"
|
||||
:size="12"
|
||||
/>
|
||||
{{ getShortcutBindingSourceLabel(definition) }}
|
||||
</span>
|
||||
<LockIcon
|
||||
v-if="definition.isReadOnly"
|
||||
:size="10"
|
||||
class="shortcuts-table__lock-icon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent
|
||||
v-if="!definition.isReadOnly"
|
||||
class="shortcuts-settings-context-menu"
|
||||
>
|
||||
<ContextMenuItem @select="contextResetAppShortcutBinding(definition)">
|
||||
{{ t('shortcutsUI.resetShortcut') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem @select="contextUnassignShortcutBinding(definition)">
|
||||
{{ t('shortcutsUI.unassignBinding') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem @select="contextAddAlternateShortcutBinding(definition)">
|
||||
{{ t('shortcutsUI.addAlternateBinding') }}
|
||||
</ContextMenuItem>
|
||||
<template v-if="definition.userDefinedAlternateChord">
|
||||
<ContextMenuItem @select="contextRemoveUserAlternateBinding(definition)">
|
||||
{{ t('shortcutsUI.removeAlternateBinding') }}
|
||||
</ContextMenuItem>
|
||||
</template>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1312,41 +1447,67 @@ const globalExtensionShortcuts = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__body">
|
||||
<div
|
||||
<ContextMenu
|
||||
v-for="keybinding in extensionKeybindings"
|
||||
:key="keybinding.commandId"
|
||||
class="shortcuts-table__row"
|
||||
@click="openExtensionKeybindingEditor(keybinding.extensionId, keybinding.commandId, keybinding.commandTitle)"
|
||||
:key="`${keybinding.extensionId}-${keybinding.commandId}`"
|
||||
>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<BlocksIcon
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ keybinding.commandTitle }}
|
||||
</span>
|
||||
</div>
|
||||
<ContextMenuTrigger as-child>
|
||||
<div
|
||||
class="shortcuts-table__row"
|
||||
@click="openExtensionKeybindingEditor(keybinding.extensionId, keybinding.commandId, keybinding.commandTitle)"
|
||||
>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--command">
|
||||
<BlocksIcon
|
||||
:size="14"
|
||||
class="shortcuts-table__cell-icon"
|
||||
/>
|
||||
<span class="shortcuts-table__cell-text">
|
||||
{{ keybinding.commandTitle }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd class="shortcuts-table__kbd">
|
||||
{{ getShortcutListLabel(keybinding.keybindingLabel) }}
|
||||
</kbd>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--keybinding">
|
||||
<kbd
|
||||
v-if="isShortcutLabelAssigned(keybinding.keybindingLabel)"
|
||||
class="shortcuts-table__kbd"
|
||||
>
|
||||
{{ keybinding.keybindingLabel }}
|
||||
</kbd>
|
||||
<span
|
||||
v-else
|
||||
class="shortcuts-keybinding-unassigned"
|
||||
>
|
||||
{{ unassignedShortcutLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when">
|
||||
<code class="shortcuts-table__when-code">
|
||||
{{ keybinding.whenLabel }}
|
||||
</code>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--when">
|
||||
<code class="shortcuts-table__when-code">
|
||||
{{ keybinding.whenLabel }}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span class="shortcuts-table__source shortcuts-table__source--extension">
|
||||
<BlocksIcon :size="12" />
|
||||
{{ keybinding.extensionName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="shortcuts-table__cell shortcuts-table__cell--source">
|
||||
<span class="shortcuts-table__source shortcuts-table__source--extension">
|
||||
<BlocksIcon :size="12" />
|
||||
{{ keybinding.extensionName }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ContextMenuTrigger>
|
||||
<ContextMenuContent class="shortcuts-settings-context-menu">
|
||||
<ContextMenuItem
|
||||
@select="resetExtensionKeybindingAt(keybinding.extensionId, keybinding.commandId, 'local')"
|
||||
>
|
||||
{{ t('shortcutsUI.resetShortcut') }}
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
@select="unassignExtensionKeybindingAt(keybinding.extensionId, keybinding.commandId, 'local')"
|
||||
>
|
||||
{{ t('shortcutsUI.unassignBinding') }}
|
||||
</ContextMenuItem>
|
||||
</ContextMenuContent>
|
||||
</ContextMenu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1373,20 +1534,29 @@ const globalExtensionShortcuts = computed(() => {
|
||||
<span class="shortcut-editor__label">
|
||||
{{ t('dialogs.shortcutEditorDialog.shortcut') }}:
|
||||
</span>
|
||||
<kbd class="shortcut-editor__kbd">
|
||||
<kbd
|
||||
v-if="isShortcutLabelAssigned(currentEditingShortcutLabel)"
|
||||
class="shortcut-editor__kbd"
|
||||
>
|
||||
{{ currentEditingShortcutLabel }}
|
||||
</kbd>
|
||||
<span
|
||||
v-else
|
||||
class="shortcuts-keybinding-unassigned"
|
||||
>
|
||||
{{ unassignedShortcutLabel }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!isEditingGlobalShortcut && editingShortcutId && getConditionsLabel(editingShortcutId)"
|
||||
v-if="!isEditingGlobalShortcut && editingAppShortcutDefinition && getConditionsLabelFromDefinition(editingAppShortcutDefinition)"
|
||||
class="shortcut-editor__info-row"
|
||||
>
|
||||
<span class="shortcut-editor__label">
|
||||
{{ t('when') }}:
|
||||
</span>
|
||||
<code class="shortcut-editor__when-code">
|
||||
{{ getConditionsLabel(editingShortcutId) }}
|
||||
{{ getConditionsLabelFromDefinition(editingAppShortcutDefinition) }}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -1461,30 +1631,22 @@ const globalExtensionShortcuts = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="shortcut-editor-dialog__footer">
|
||||
<ConfirmButton
|
||||
<Button
|
||||
v-if="hasConflict"
|
||||
variant="outline"
|
||||
class="shortcut-editor-dialog__footer-button"
|
||||
@click="isEditingExtension ? resetExtensionKeybinding() : resetShortcut()"
|
||||
class="shortcut-editor-dialog__replace-button"
|
||||
:disabled="!recordedKeys"
|
||||
@click="replaceShortcut"
|
||||
>
|
||||
<RotateCcwIcon :size="14" />
|
||||
{{ t('shortcutsUI.resetShortcut') }}
|
||||
</ConfirmButton>
|
||||
<div class="shortcut-editor-dialog__footer-right">
|
||||
<Button
|
||||
v-if="hasConflict"
|
||||
variant="secondary"
|
||||
:disabled="!recordedKeys"
|
||||
@click="replaceShortcut"
|
||||
>
|
||||
{{ t('conflictDialog.replace') }}
|
||||
</Button>
|
||||
<Button
|
||||
:disabled="!recordedKeys || hasConflict"
|
||||
@click="isEditingExtension ? saveExtensionKeybinding() : saveShortcut()"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</Button>
|
||||
</div>
|
||||
{{ t('conflictDialog.replace') }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!hasConflict"
|
||||
:disabled="!recordedKeys"
|
||||
@click="isEditingExtension ? saveExtensionKeybinding() : saveShortcut()"
|
||||
>
|
||||
{{ t('save') }}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
@@ -1595,7 +1757,7 @@ const globalExtensionShortcuts = computed(() => {
|
||||
background-color: hsl(var(--muted) / 30%);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
grid-template-columns: 1fr 140px 160px 100px;
|
||||
grid-template-columns: minmax(140px, 1fr) 140px 160px 100px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -1613,7 +1775,7 @@ const globalExtensionShortcuts = computed(() => {
|
||||
display: grid;
|
||||
border-bottom: 1px solid hsl(var(--border) / 50%);
|
||||
cursor: pointer;
|
||||
grid-template-columns: 1fr 140px 160px 100px;
|
||||
grid-template-columns: minmax(140px, 1fr) 140px 160px 100px;
|
||||
transition: background-color 0.1s ease;
|
||||
}
|
||||
|
||||
@@ -1626,6 +1788,10 @@ const globalExtensionShortcuts = computed(() => {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.shortcuts-table__row--alternate-chord {
|
||||
background-color: hsl(var(--muted) / 30%);
|
||||
}
|
||||
|
||||
.shortcuts-table__row--readonly:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
@@ -1639,19 +1805,32 @@ const globalExtensionShortcuts = computed(() => {
|
||||
|
||||
.shortcuts-table__cell--command {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.shortcuts-table__cell-icon {
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.shortcuts-table__cell-text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
-webkit-box-orient: vertical;
|
||||
color: hsl(var(--foreground));
|
||||
font-size: 0.875rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
line-height: 1.35;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.shortcuts-keybinding-unassigned {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.shortcuts-table__kbd {
|
||||
@@ -1870,19 +2049,20 @@ const globalExtensionShortcuts = computed(() => {
|
||||
.shortcut-editor-dialog__footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
margin-top: 0.5rem;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.shortcut-editor-dialog__footer-button {
|
||||
gap: 8px;
|
||||
.shortcut-editor-dialog__replace-button.sigma-ui-button {
|
||||
border-color: hsl(var(--destructive));
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.shortcut-editor-dialog__footer-right {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
.shortcut-editor-dialog__replace-button.sigma-ui-button:hover {
|
||||
background-color: hsl(var(--destructive) / 12%);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
</style>
|
||||
@@ -69,6 +69,7 @@ describe('shortcuts store', () => {
|
||||
extensionKeybindings.splice(0, extensionKeybindings.length);
|
||||
userSettingsStoreMock.userSettings = reactive({
|
||||
shortcuts: {},
|
||||
shortcutUserAlternateChordSlots: {},
|
||||
});
|
||||
userSettingsStoreMock.setUserSettingsStorage.mockReset();
|
||||
setAppKeybindingConflictCheckerMock.mockReset();
|
||||
@@ -154,20 +155,22 @@ describe('shortcuts store', () => {
|
||||
it('defines customizable mouse page and Alt+arrow pane navigation shortcuts', () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
|
||||
expect(shortcutsStore.getShortcutLabel('navigatePageBack')).toBe('Mouse Button 4');
|
||||
expect(shortcutsStore.getShortcutLabel('navigatePageForward')).toBe('Mouse Button 5');
|
||||
expect(shortcutsStore.getShortcutLabel('navigatePageBack')).toBe('');
|
||||
expect(shortcutsStore.getShortcutLabel('navigatePageForward')).toBe('');
|
||||
expect(shortcutsStore.getShortcutLabel('navigateHistoryBack')).toBe('Alt+←');
|
||||
expect(shortcutsStore.getShortcutLabel('navigateHistoryForward')).toBe('Alt+→');
|
||||
expect(shortcutsStore.getShortcutLabel('goUpDirectory')).toBe('Alt+↑');
|
||||
});
|
||||
|
||||
it('matches mouse shortcuts for page history navigation', async () => {
|
||||
it('matches mouse shortcuts for page history navigation when assigned', async () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
const navigatePageBackHandler = vi.fn();
|
||||
const navigatePageForwardHandler = vi.fn();
|
||||
|
||||
shortcutsStore.registerHandler('navigatePageBack', navigatePageBackHandler);
|
||||
shortcutsStore.registerHandler('navigatePageForward', navigatePageForwardHandler);
|
||||
await shortcutsStore.setShortcut('navigatePageBack', { key: 'MouseButton4' });
|
||||
await shortcutsStore.setShortcut('navigatePageForward', { key: 'MouseButton5' });
|
||||
|
||||
const backEvent = new MouseEvent('mousedown', {
|
||||
button: 3,
|
||||
@@ -251,6 +254,79 @@ describe('shortcuts store', () => {
|
||||
expect(upEvent.defaultPrevented).toBe(true);
|
||||
});
|
||||
|
||||
it('matches default mouse back and forward for directory history alongside Alt+arrow', async () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
const navigateHistoryBackHandler = vi.fn();
|
||||
const navigateHistoryForwardHandler = vi.fn();
|
||||
|
||||
shortcutsStore.registerHandler('navigateHistoryBack', navigateHistoryBackHandler);
|
||||
shortcutsStore.registerHandler('navigateHistoryForward', navigateHistoryForwardHandler);
|
||||
|
||||
const mouseBackEvent = new MouseEvent('mousedown', {
|
||||
button: 3,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
const mouseForwardEvent = new MouseEvent('mousedown', {
|
||||
button: 4,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
await expect(shortcutsStore.handleMouseDown(mouseBackEvent)).resolves.toBe(true);
|
||||
await expect(shortcutsStore.handleMouseDown(mouseForwardEvent)).resolves.toBe(true);
|
||||
expect(navigateHistoryBackHandler).toHaveBeenCalledTimes(1);
|
||||
expect(navigateHistoryForwardHandler).toHaveBeenCalledTimes(1);
|
||||
expect(mouseBackEvent.defaultPrevented).toBe(true);
|
||||
expect(mouseForwardEvent.defaultPrevented).toBe(true);
|
||||
});
|
||||
|
||||
it('applies setShortcut without binding slot only to binding slot 0 and leaves slot 1 defaults', async () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
|
||||
await shortcutsStore.setShortcut('navigateHistoryBack', {
|
||||
ctrl: true,
|
||||
key: 'h',
|
||||
});
|
||||
|
||||
expect(shortcutsStore.getShortcutLabel('navigateHistoryBack')).toBe('Ctrl+H');
|
||||
|
||||
const mouseBackDefinition = shortcutsStore.definitions.find(
|
||||
definition =>
|
||||
definition.id === 'navigateHistoryBack'
|
||||
&& (definition.bindingSlot ?? 0) === 1,
|
||||
);
|
||||
|
||||
expect(mouseBackDefinition).toBeDefined();
|
||||
expect(shortcutsStore.resolveShortcutBindingKeys(mouseBackDefinition!)).toEqual({
|
||||
key: 'MouseButton4',
|
||||
});
|
||||
});
|
||||
|
||||
it('applies setShortcut with binding slot 1 without changing slot 0 keys', async () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
|
||||
await shortcutsStore.setShortcut(
|
||||
'navigateHistoryBack',
|
||||
{ alt: true, key: 'h' },
|
||||
1,
|
||||
);
|
||||
|
||||
expect(shortcutsStore.getShortcutLabel('navigateHistoryBack')).toBe('Alt+←');
|
||||
|
||||
const mouseDefinition = shortcutsStore.definitions.find(
|
||||
definition =>
|
||||
definition.id === 'navigateHistoryBack'
|
||||
&& (definition.bindingSlot ?? 0) === 1,
|
||||
);
|
||||
|
||||
expect(mouseDefinition).toBeDefined();
|
||||
expect(shortcutsStore.resolveShortcutBindingKeys(mouseDefinition!)).toEqual({
|
||||
alt: true,
|
||||
key: 'h',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps zoom and fullscreen shortcuts active while a dialog is open', async () => {
|
||||
const shortcutsStore = useShortcutsStore();
|
||||
const zoomInHandler = vi.fn();
|
||||
|
||||
+407
-27
@@ -5,7 +5,12 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useUserSettingsStore } from '@/stores/storage/user-settings';
|
||||
import type { ShortcutId, ShortcutKeys, UserShortcuts } from '@/types/user-settings';
|
||||
import type {
|
||||
ShortcutId,
|
||||
ShortcutKeys,
|
||||
ShortcutUserAlternateChordSlots,
|
||||
UserShortcuts,
|
||||
} from '@/types/user-settings';
|
||||
import { setAppKeybindingConflictChecker } from '@/modules/extensions/api';
|
||||
import { getSelectedEntries } from '@/modules/extensions/context';
|
||||
import { useExtensionsStore } from '@/stores/runtime/extensions';
|
||||
@@ -16,6 +21,7 @@ import {
|
||||
import { isDialogOpened, isInputFieldActive } from '@/utils/dom-interaction-state';
|
||||
|
||||
export type { ShortcutId, ShortcutKeys, UserShortcuts };
|
||||
export type { UserShortcutStoredValue, ShortcutUserAlternateChordSlots } from '@/types/user-settings';
|
||||
|
||||
export type ShortcutConditions = {
|
||||
inputFieldIsActive?: boolean;
|
||||
@@ -25,6 +31,9 @@ export type ShortcutConditions = {
|
||||
|
||||
export type ShortcutDefinition = {
|
||||
id: ShortcutId;
|
||||
bindingSlot?: number;
|
||||
alternateChordRow?: boolean;
|
||||
userDefinedAlternateChord?: boolean;
|
||||
labelKey: string;
|
||||
defaultKeys: ShortcutKeys;
|
||||
scope: 'global' | 'navigator' | 'settings';
|
||||
@@ -169,7 +178,7 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
id: 'navigatePageBack',
|
||||
labelKey: 'shortcuts.navigatePageBack',
|
||||
defaultKeys: {
|
||||
key: 'MouseButton4',
|
||||
key: '',
|
||||
},
|
||||
scope: 'global',
|
||||
conditions: {
|
||||
@@ -181,7 +190,7 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
id: 'navigatePageForward',
|
||||
labelKey: 'shortcuts.navigatePageForward',
|
||||
defaultKeys: {
|
||||
key: 'MouseButton5',
|
||||
key: '',
|
||||
},
|
||||
scope: 'global',
|
||||
conditions: {
|
||||
@@ -479,6 +488,7 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
},
|
||||
{
|
||||
id: 'navigateHistoryBack',
|
||||
bindingSlot: 0,
|
||||
labelKey: 'shortcuts.navigateHistoryBack',
|
||||
defaultKeys: {
|
||||
alt: true,
|
||||
@@ -491,8 +501,24 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
},
|
||||
isReadOnly: false,
|
||||
},
|
||||
{
|
||||
id: 'navigateHistoryBack',
|
||||
bindingSlot: 1,
|
||||
alternateChordRow: true,
|
||||
labelKey: 'shortcuts.navigateHistoryBack',
|
||||
defaultKeys: {
|
||||
key: 'MouseButton4',
|
||||
},
|
||||
scope: 'navigator',
|
||||
conditions: {
|
||||
inputFieldIsActive: false,
|
||||
dialogIsOpened: false,
|
||||
},
|
||||
isReadOnly: false,
|
||||
},
|
||||
{
|
||||
id: 'navigateHistoryForward',
|
||||
bindingSlot: 0,
|
||||
labelKey: 'shortcuts.navigateHistoryForward',
|
||||
defaultKeys: {
|
||||
alt: true,
|
||||
@@ -505,6 +531,21 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
},
|
||||
isReadOnly: false,
|
||||
},
|
||||
{
|
||||
id: 'navigateHistoryForward',
|
||||
bindingSlot: 1,
|
||||
alternateChordRow: true,
|
||||
labelKey: 'shortcuts.navigateHistoryForward',
|
||||
defaultKeys: {
|
||||
key: 'MouseButton5',
|
||||
},
|
||||
scope: 'navigator',
|
||||
conditions: {
|
||||
inputFieldIsActive: false,
|
||||
dialogIsOpened: false,
|
||||
},
|
||||
isReadOnly: false,
|
||||
},
|
||||
{
|
||||
id: 'goUpDirectory',
|
||||
labelKey: 'shortcuts.goUpDirectory',
|
||||
@@ -581,6 +622,53 @@ const DEFAULT_SHORTCUTS: ShortcutDefinition[] = [
|
||||
},
|
||||
];
|
||||
|
||||
function mergeShortcutDefinitionsWithUserAlternateRows(
|
||||
baseDefinitions: ShortcutDefinition[],
|
||||
userAlternateChordSlots: ShortcutUserAlternateChordSlots,
|
||||
): ShortcutDefinition[] {
|
||||
const merged: ShortcutDefinition[] = [];
|
||||
let baseWalkIndex = 0;
|
||||
|
||||
while (baseWalkIndex < baseDefinitions.length) {
|
||||
const currentShortcutId = baseDefinitions[baseWalkIndex].id;
|
||||
|
||||
while (
|
||||
baseWalkIndex < baseDefinitions.length
|
||||
&& baseDefinitions[baseWalkIndex].id === currentShortcutId
|
||||
) {
|
||||
merged.push(baseDefinitions[baseWalkIndex]);
|
||||
baseWalkIndex += 1;
|
||||
}
|
||||
|
||||
const extraSlots = userAlternateChordSlots[currentShortcutId];
|
||||
if (!extraSlots?.length) continue;
|
||||
|
||||
const primaryTemplate = baseDefinitions.find(
|
||||
definition =>
|
||||
definition.id === currentShortcutId
|
||||
&& (definition.bindingSlot ?? 0) === 0,
|
||||
);
|
||||
|
||||
if (!primaryTemplate) continue;
|
||||
|
||||
for (const bindingSlot of extraSlots) {
|
||||
merged.push({
|
||||
id: primaryTemplate.id,
|
||||
bindingSlot,
|
||||
labelKey: primaryTemplate.labelKey,
|
||||
defaultKeys: { key: '' },
|
||||
scope: primaryTemplate.scope,
|
||||
conditions: { ...primaryTemplate.conditions },
|
||||
isReadOnly: primaryTemplate.isReadOnly,
|
||||
alternateChordRow: true,
|
||||
userDefinedAlternateChord: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
const MODIFIER_ONLY_KEYBOARD_CODES = new Set([
|
||||
'MetaLeft',
|
||||
'MetaRight',
|
||||
@@ -1014,7 +1102,12 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
const userSettingsStore = useUserSettingsStore();
|
||||
const extensionsStore = useExtensionsStore();
|
||||
|
||||
const definitions = ref<ShortcutDefinition[]>(DEFAULT_SHORTCUTS);
|
||||
const definitions = computed(() =>
|
||||
mergeShortcutDefinitionsWithUserAlternateRows(
|
||||
DEFAULT_SHORTCUTS,
|
||||
userSettingsStore.userSettings.shortcutUserAlternateChordSlots ?? {},
|
||||
),
|
||||
);
|
||||
const handlers = ref<Map<ShortcutId, HandlerRegistration>>(new Map());
|
||||
const isInitialized = ref(false);
|
||||
const isListenerActive = ref(false);
|
||||
@@ -1028,13 +1121,94 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
},
|
||||
});
|
||||
|
||||
async function persistShortcutUserAlternateChordSlots(next: ShortcutUserAlternateChordSlots): Promise<void> {
|
||||
const cleaned: ShortcutUserAlternateChordSlots = {};
|
||||
|
||||
for (const shortcutId of Object.keys(next) as ShortcutId[]) {
|
||||
const slots = next[shortcutId];
|
||||
if (slots?.length) {
|
||||
cleaned[shortcutId] = slots;
|
||||
}
|
||||
}
|
||||
|
||||
userSettingsStore.userSettings.shortcutUserAlternateChordSlots = cleaned;
|
||||
await userSettingsStore.setUserSettingsStorage('shortcutUserAlternateChordSlots', cleaned);
|
||||
}
|
||||
|
||||
function getMaxBindingSlotIndex(shortcutId: ShortcutId): number {
|
||||
let maxSlot = 0;
|
||||
|
||||
for (const definition of definitions.value) {
|
||||
if (definition.id !== shortcutId) continue;
|
||||
|
||||
const slotIndex = definition.bindingSlot ?? 0;
|
||||
|
||||
if (slotIndex > maxSlot) {
|
||||
maxSlot = slotIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return maxSlot;
|
||||
}
|
||||
|
||||
function resolveShortcutBindingKeys(definition: ShortcutDefinition): ShortcutKeys {
|
||||
const bindingSlot = definition.bindingSlot ?? 0;
|
||||
const shortcutId = definition.id;
|
||||
const entry = userShortcuts.value[shortcutId];
|
||||
const defaultKeys = definition.defaultKeys;
|
||||
|
||||
if (entry === undefined) {
|
||||
return defaultKeys;
|
||||
}
|
||||
|
||||
if (!Array.isArray(entry)) {
|
||||
return bindingSlot === 0 ? entry : defaultKeys;
|
||||
}
|
||||
|
||||
const slotValue = entry[bindingSlot];
|
||||
|
||||
return slotValue != null ? slotValue : defaultKeys;
|
||||
}
|
||||
|
||||
function getShortcutBindingLabel(definition: ShortcutDefinition): string {
|
||||
return formatShortcutKeys(resolveShortcutBindingKeys(definition));
|
||||
}
|
||||
|
||||
function isShortcutBindingCustomized(definition: ShortcutDefinition): boolean {
|
||||
const bindingSlot = definition.bindingSlot ?? 0;
|
||||
const entry = userShortcuts.value[definition.id];
|
||||
|
||||
if (entry === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Array.isArray(entry)) {
|
||||
return bindingSlot === 0;
|
||||
}
|
||||
|
||||
return entry[bindingSlot] != null;
|
||||
}
|
||||
|
||||
function getShortcutBindingSource(definition: ShortcutDefinition): 'system' | 'user' {
|
||||
return isShortcutBindingCustomized(definition) ? 'user' : 'system';
|
||||
}
|
||||
|
||||
function getPrimaryShortcutDefinition(shortcutId: ShortcutId): ShortcutDefinition | undefined {
|
||||
return definitions.value.find(
|
||||
definitionItem =>
|
||||
definitionItem.id === shortcutId
|
||||
&& (definitionItem.bindingSlot ?? 0) === 0,
|
||||
);
|
||||
}
|
||||
|
||||
function getShortcutKeys(shortcutId: ShortcutId): ShortcutKeys {
|
||||
const userKeys = userShortcuts.value[shortcutId];
|
||||
if (userKeys) return userKeys;
|
||||
const definition = getPrimaryShortcutDefinition(shortcutId);
|
||||
|
||||
const definition = definitions.value.find(definitionItem => definitionItem.id === shortcutId);
|
||||
if (!definition) {
|
||||
return { key: '' };
|
||||
}
|
||||
|
||||
return definition?.defaultKeys ?? { key: '' };
|
||||
return resolveShortcutBindingKeys(definition);
|
||||
}
|
||||
|
||||
function getShortcutLabel(shortcutId: ShortcutId): string {
|
||||
@@ -1042,7 +1216,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
}
|
||||
|
||||
function getShortcutDefinition(shortcutId: ShortcutId): ShortcutDefinition | undefined {
|
||||
return definitions.value.find(definitionItem => definitionItem.id === shortcutId);
|
||||
return getPrimaryShortcutDefinition(shortcutId);
|
||||
}
|
||||
|
||||
function isCustomized(shortcutId: ShortcutId): boolean {
|
||||
@@ -1053,22 +1227,211 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
return isCustomized(shortcutId) ? 'user' : 'system';
|
||||
}
|
||||
|
||||
async function setShortcut(shortcutId: ShortcutId, keys: ShortcutKeys): Promise<void> {
|
||||
const newShortcuts = {
|
||||
async function setShortcut(
|
||||
shortcutId: ShortcutId,
|
||||
keys: ShortcutKeys,
|
||||
bindingSlot = 0,
|
||||
): Promise<void> {
|
||||
const arrayLength = getMaxBindingSlotIndex(shortcutId) + 1;
|
||||
|
||||
if (arrayLength <= 1) {
|
||||
const newShortcuts = {
|
||||
...userShortcuts.value,
|
||||
[shortcutId]: keys,
|
||||
};
|
||||
userShortcuts.value = newShortcuts;
|
||||
return;
|
||||
}
|
||||
|
||||
const current = userShortcuts.value[shortcutId];
|
||||
const nextArray: Array<ShortcutKeys | null> = Array.from({ length: arrayLength }, () => null);
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
for (let slotIndex = 0; slotIndex < arrayLength; slotIndex += 1) {
|
||||
nextArray[slotIndex] = current[slotIndex] ?? null;
|
||||
}
|
||||
}
|
||||
else if (current !== undefined) {
|
||||
nextArray[0] = current;
|
||||
}
|
||||
|
||||
nextArray[bindingSlot] = keys;
|
||||
|
||||
if (nextArray.every(slotValue => slotValue == null)) {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
return;
|
||||
}
|
||||
|
||||
userShortcuts.value = {
|
||||
...userShortcuts.value,
|
||||
[shortcutId]: keys,
|
||||
[shortcutId]: nextArray,
|
||||
};
|
||||
userShortcuts.value = newShortcuts;
|
||||
}
|
||||
|
||||
async function resetShortcut(shortcutId: ShortcutId): Promise<void> {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
async function resetShortcut(shortcutId: ShortcutId, bindingSlot?: number): Promise<void> {
|
||||
const arrayLength = getMaxBindingSlotIndex(shortcutId) + 1;
|
||||
|
||||
if (bindingSlot === undefined) {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
|
||||
const clearedExtras: ShortcutUserAlternateChordSlots = {
|
||||
...(userSettingsStore.userSettings.shortcutUserAlternateChordSlots ?? {}),
|
||||
};
|
||||
delete clearedExtras[shortcutId];
|
||||
await persistShortcutUserAlternateChordSlots(clearedExtras);
|
||||
return;
|
||||
}
|
||||
|
||||
if (arrayLength <= 1) {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
return;
|
||||
}
|
||||
|
||||
const current = userShortcuts.value[shortcutId];
|
||||
const nextArray: Array<ShortcutKeys | null> = Array.from({ length: arrayLength }, () => null);
|
||||
|
||||
if (Array.isArray(current)) {
|
||||
for (let slotIndex = 0; slotIndex < arrayLength; slotIndex += 1) {
|
||||
nextArray[slotIndex] = current[slotIndex] ?? null;
|
||||
}
|
||||
}
|
||||
else if (current !== undefined) {
|
||||
nextArray[0] = current;
|
||||
}
|
||||
|
||||
nextArray[bindingSlot] = null;
|
||||
|
||||
if (nextArray.every(slotValue => slotValue == null)) {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
return;
|
||||
}
|
||||
|
||||
userShortcuts.value = {
|
||||
...userShortcuts.value,
|
||||
[shortcutId]: nextArray,
|
||||
};
|
||||
}
|
||||
|
||||
function trimUserShortcutEntryToMaxSlot(shortcutId: ShortcutId): void {
|
||||
const maxSlotIndex = getMaxBindingSlotIndex(shortcutId);
|
||||
const arrayLength = maxSlotIndex + 1;
|
||||
const currentEntry = userShortcuts.value[shortcutId];
|
||||
|
||||
if (!Array.isArray(currentEntry)) return;
|
||||
|
||||
const trimmed = currentEntry.slice(0, arrayLength);
|
||||
|
||||
if (trimmed.every(slotValue => slotValue == null)) {
|
||||
const newShortcuts = { ...userShortcuts.value };
|
||||
delete newShortcuts[shortcutId];
|
||||
userShortcuts.value = newShortcuts;
|
||||
return;
|
||||
}
|
||||
|
||||
userShortcuts.value = {
|
||||
...userShortcuts.value,
|
||||
[shortcutId]: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
async function addUserAlternateChordBinding(
|
||||
shortcutId: ShortcutId,
|
||||
originDefinition: ShortcutDefinition,
|
||||
): Promise<void> {
|
||||
const primaryTemplate = DEFAULT_SHORTCUTS.find(
|
||||
definition =>
|
||||
definition.id === shortcutId
|
||||
&& (definition.bindingSlot ?? 0) === 0,
|
||||
);
|
||||
|
||||
if (!primaryTemplate || primaryTemplate.isReadOnly) return;
|
||||
|
||||
const slotsBefore = userSettingsStore.userSettings.shortcutUserAlternateChordSlots ?? {};
|
||||
const mergedBefore = mergeShortcutDefinitionsWithUserAlternateRows(DEFAULT_SHORTCUTS, slotsBefore);
|
||||
|
||||
const originBindingSlot = originDefinition.bindingSlot ?? 0;
|
||||
|
||||
const originIdx = mergedBefore.findIndex(
|
||||
definition =>
|
||||
definition.id === shortcutId
|
||||
&& (definition.bindingSlot ?? 0) === originBindingSlot,
|
||||
);
|
||||
|
||||
if (originIdx < 0) return;
|
||||
|
||||
let endIdx = originIdx;
|
||||
|
||||
while (
|
||||
endIdx + 1 < mergedBefore.length
|
||||
&& mergedBefore[endIdx + 1].id === shortcutId
|
||||
) {
|
||||
endIdx += 1;
|
||||
}
|
||||
|
||||
const insertMergedIndex = endIdx + 1;
|
||||
|
||||
const builtinCountForId = DEFAULT_SHORTCUTS.filter(definition => definition.id === shortcutId).length;
|
||||
const groupStart = mergedBefore.findIndex(definition => definition.id === shortcutId);
|
||||
|
||||
if (groupStart < 0) return;
|
||||
|
||||
const userVisualStart = groupStart + builtinCountForId;
|
||||
const rawInsertPosition = insertMergedIndex - userVisualStart;
|
||||
const current: ShortcutUserAlternateChordSlots = { ...slotsBefore };
|
||||
const userSlots = [...(current[shortcutId] ?? [])];
|
||||
|
||||
const insertIntoUserArray = Math.max(0, Math.min(rawInsertPosition, userSlots.length));
|
||||
|
||||
const maxBuiltinSlot = Math.max(
|
||||
0,
|
||||
...DEFAULT_SHORTCUTS
|
||||
.filter(definition => definition.id === shortcutId)
|
||||
.map(definition => definition.bindingSlot ?? 0),
|
||||
);
|
||||
|
||||
const maxFromUserSlots = userSlots.length ? Math.max(...userSlots) : -1;
|
||||
const maxSlotOverall = Math.max(maxBuiltinSlot, maxFromUserSlots);
|
||||
const newSlot = maxSlotOverall + 1;
|
||||
|
||||
userSlots.splice(insertIntoUserArray, 0, newSlot);
|
||||
current[shortcutId] = userSlots;
|
||||
|
||||
await persistShortcutUserAlternateChordSlots(current);
|
||||
}
|
||||
|
||||
async function removeUserAlternateChordBindingRow(shortcutId: ShortcutId, bindingSlot: number): Promise<void> {
|
||||
await resetShortcut(shortcutId, bindingSlot);
|
||||
|
||||
const current: ShortcutUserAlternateChordSlots = {
|
||||
...(userSettingsStore.userSettings.shortcutUserAlternateChordSlots ?? {}),
|
||||
};
|
||||
const userSlots = [...(current[shortcutId] ?? [])];
|
||||
const filtered = userSlots.filter(slot => slot !== bindingSlot);
|
||||
|
||||
if (filtered.length === userSlots.length) return;
|
||||
|
||||
if (filtered.length) {
|
||||
current[shortcutId] = filtered;
|
||||
}
|
||||
else {
|
||||
delete current[shortcutId];
|
||||
}
|
||||
|
||||
await persistShortcutUserAlternateChordSlots(current);
|
||||
trimUserShortcutEntryToMaxSlot(shortcutId);
|
||||
}
|
||||
|
||||
async function resetAllShortcuts(): Promise<void> {
|
||||
userShortcuts.value = {};
|
||||
await persistShortcutUserAlternateChordSlots({});
|
||||
}
|
||||
|
||||
function checkConditions(definition: ShortcutDefinition, registration?: HandlerRegistration): boolean {
|
||||
@@ -1142,7 +1505,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
|
||||
function findMatchingShortcut(event: KeyboardEvent): ShortcutId | null {
|
||||
for (const definition of definitions.value) {
|
||||
const keys = getShortcutKeys(definition.id);
|
||||
const keys = resolveShortcutBindingKeys(definition);
|
||||
|
||||
if (matchesShortcut(event, keys)) {
|
||||
return definition.id;
|
||||
@@ -1156,7 +1519,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
const matchingShortcuts: ShortcutId[] = [];
|
||||
|
||||
for (const definition of definitions.value) {
|
||||
const keys = getShortcutKeys(definition.id);
|
||||
const keys = resolveShortcutBindingKeys(definition);
|
||||
|
||||
if (matchesShortcut(event, keys)) {
|
||||
matchingShortcuts.push(definition.id);
|
||||
@@ -1170,7 +1533,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
const matchingShortcuts: ShortcutId[] = [];
|
||||
|
||||
for (const definition of definitions.value) {
|
||||
const keys = getShortcutKeys(definition.id);
|
||||
const keys = resolveShortcutBindingKeys(definition);
|
||||
|
||||
if (matchesMouseShortcut(event, keys)) {
|
||||
matchingShortcuts.push(definition.id);
|
||||
@@ -1180,13 +1543,22 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
return matchingShortcuts;
|
||||
}
|
||||
|
||||
function findConflictingShortcut(keys: ShortcutKeys, excludeShortcutId?: ShortcutId): ShortcutDefinition | null {
|
||||
function findConflictingShortcut(
|
||||
keys: ShortcutKeys,
|
||||
excludeDefinition?: ShortcutDefinition,
|
||||
): ShortcutDefinition | null {
|
||||
const keysLabel = formatShortcutKeys(keys);
|
||||
|
||||
for (const definition of definitions.value) {
|
||||
if (definition.id === excludeShortcutId) continue;
|
||||
if (
|
||||
excludeDefinition
|
||||
&& definition.id === excludeDefinition.id
|
||||
&& (definition.bindingSlot ?? 0) === (excludeDefinition.bindingSlot ?? 0)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existingLabel = getShortcutLabel(definition.id);
|
||||
const existingLabel = formatShortcutKeys(resolveShortcutBindingKeys(definition));
|
||||
|
||||
if (existingLabel === keysLabel) {
|
||||
return definition;
|
||||
@@ -1311,9 +1683,6 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
const matchingShortcutIds = findAllMatchingMouseShortcuts(event);
|
||||
if (matchingShortcutIds.length === 0) return false;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
for (const shortcutId of matchingShortcutIds) {
|
||||
const result = callShortcutHandler(shortcutId);
|
||||
if (result === null) continue;
|
||||
@@ -1322,6 +1691,11 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
? (await result) !== false
|
||||
: result !== false;
|
||||
|
||||
if (isHandled) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
return isHandled;
|
||||
}
|
||||
|
||||
@@ -1352,7 +1726,7 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
|
||||
function hasConflictWithAppShortcut(keys: ShortcutKeys): boolean {
|
||||
for (const definition of definitions.value) {
|
||||
const shortcutKeys = getShortcutKeys(definition.id);
|
||||
const shortcutKeys = resolveShortcutBindingKeys(definition);
|
||||
|
||||
if (
|
||||
shortcutKeys.key.toLowerCase() === keys.key.toLowerCase()
|
||||
@@ -1389,11 +1763,17 @@ export const useShortcutsStore = defineStore('shortcuts', () => {
|
||||
getShortcutKeys,
|
||||
getShortcutLabel,
|
||||
getShortcutDefinition,
|
||||
resolveShortcutBindingKeys,
|
||||
getShortcutBindingLabel,
|
||||
getShortcutBindingSource,
|
||||
isShortcutBindingCustomized,
|
||||
isCustomized,
|
||||
getSource,
|
||||
setShortcut,
|
||||
resetShortcut,
|
||||
resetAllShortcuts,
|
||||
addUserAlternateChordBinding,
|
||||
removeUserAlternateChordBindingRow,
|
||||
registerHandler,
|
||||
unregisterHandler,
|
||||
setShortcutCaptureActive,
|
||||
|
||||
@@ -151,6 +151,7 @@ export const useUserSettingsStore = defineStore('userSettings', () => {
|
||||
visualFilters: createDefaultVisualFiltersSettings(),
|
||||
settingsCurrentTab: 'general',
|
||||
shortcuts: {},
|
||||
shortcutUserAlternateChordSlots: {},
|
||||
globalShortcuts: {},
|
||||
focusWindowOnDriveConnected: true,
|
||||
preventDropdownCloseFocusReturn: false,
|
||||
|
||||
@@ -88,6 +88,8 @@ export type ShortcutKeys = {
|
||||
key: string;
|
||||
};
|
||||
|
||||
export type UserShortcutStoredValue = ShortcutKeys | Array<ShortcutKeys | null>;
|
||||
|
||||
export type ShortcutId
|
||||
= 'toggleGlobalSearch'
|
||||
| 'switchToHomePage'
|
||||
@@ -131,7 +133,9 @@ export type ShortcutId
|
||||
| 'uiZoomDecrease'
|
||||
| 'toggleFullscreen';
|
||||
|
||||
export type UserShortcuts = Partial<Record<ShortcutId, ShortcutKeys>>;
|
||||
export type UserShortcuts = Partial<Record<ShortcutId, UserShortcutStoredValue>>;
|
||||
|
||||
export type ShortcutUserAlternateChordSlots = Partial<Record<ShortcutId, number[]>>;
|
||||
|
||||
export type GlobalShortcutId = 'launchApp';
|
||||
|
||||
@@ -172,6 +176,7 @@ export type UserSettings = {
|
||||
visualFilters: VisualFiltersSettings;
|
||||
settingsCurrentTab: string;
|
||||
shortcuts?: UserShortcuts;
|
||||
shortcutUserAlternateChordSlots?: ShortcutUserAlternateChordSlots;
|
||||
globalShortcuts?: UserGlobalShortcuts;
|
||||
focusWindowOnDriveConnected: boolean;
|
||||
preventDropdownCloseFocusReturn: boolean;
|
||||
|
||||
Reference in new issue
Block a user