mirror of https://github.com/keeweb/keeweb.git
global shortcuts
This commit is contained in:
parent
48c4f28d5d
commit
b572876099
|
@ -31,10 +31,9 @@ const AllGlobalShortcuts = {
|
|||
restoreApp: undefined
|
||||
};
|
||||
|
||||
const GlobalShortcutAppSettingsFields: Record<
|
||||
keyof typeof AllGlobalShortcuts,
|
||||
AppSettingsFieldName
|
||||
> = {
|
||||
export type GlobalShortcutType = keyof typeof AllGlobalShortcuts;
|
||||
|
||||
const GlobalShortcutAppSettingsFields: Record<GlobalShortcutType, AppSettingsFieldName> = {
|
||||
copyPassword: 'globalShortcutCopyPassword',
|
||||
copyUser: 'globalShortcutCopyUser',
|
||||
copyUrl: 'globalShortcutCopyUrl',
|
||||
|
@ -108,11 +107,11 @@ const Shortcuts = {
|
|||
return formatting ? `${shortcut} + ` : `${shortcut}+`;
|
||||
},
|
||||
|
||||
globalShortcutText(type: keyof typeof AllGlobalShortcuts, formatting?: boolean): string {
|
||||
globalShortcutText(type: GlobalShortcutType, formatting?: boolean): string {
|
||||
return this.presentShortcut(this.globalShortcut(type), formatting);
|
||||
},
|
||||
|
||||
globalShortcut(type: keyof typeof AllGlobalShortcuts): string | undefined {
|
||||
globalShortcut(type: GlobalShortcutType): string | undefined {
|
||||
const setting = GlobalShortcutAppSettingsFields[type];
|
||||
const appSettingsShortcut = AppSettings[setting];
|
||||
if (typeof appSettingsShortcut === 'string') {
|
||||
|
@ -128,7 +127,7 @@ const Shortcuts = {
|
|||
return undefined;
|
||||
},
|
||||
|
||||
setGlobalShortcut(type: keyof typeof AllGlobalShortcuts, value: string): void {
|
||||
setGlobalShortcut(type: GlobalShortcutType, value: string | null): void {
|
||||
if (!AllGlobalShortcuts[type]) {
|
||||
throw new Error('Bad shortcut: ' + type);
|
||||
}
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
import { View } from 'framework/views/view';
|
||||
import { Shortcuts } from 'comp/app/shortcuts';
|
||||
import { Launcher } from 'comp/launcher';
|
||||
import { Keys } from 'const/keys';
|
||||
import { Features } from 'util/features';
|
||||
import { Locale } from 'util/locale';
|
||||
import template from 'templates/settings/settings-shortcuts.hbs';
|
||||
|
||||
class SettingsShortcutsView extends View {
|
||||
template = template;
|
||||
|
||||
systemShortcuts = [
|
||||
'Meta+A',
|
||||
'Alt+A',
|
||||
'Alt+C',
|
||||
'Alt+D',
|
||||
'Meta+F',
|
||||
'Meta+C',
|
||||
'Meta+B',
|
||||
'Meta+U',
|
||||
'Meta+T',
|
||||
'Alt+N',
|
||||
'Meta+O',
|
||||
'Meta+S',
|
||||
'Meta+G',
|
||||
'Meta+,',
|
||||
'Meta+L'
|
||||
];
|
||||
|
||||
events = {
|
||||
'click button.shortcut': 'shortcutClick'
|
||||
};
|
||||
|
||||
render() {
|
||||
super.render({
|
||||
cmd: Shortcuts.actionShortcutSymbol(true),
|
||||
alt: Shortcuts.altShortcutSymbol(true),
|
||||
globalIsLarge: !Features.isMac,
|
||||
autoTypeSupported: !!Launcher,
|
||||
globalShortcuts: Launcher
|
||||
? {
|
||||
autoType: Shortcuts.globalShortcutText('autoType', true),
|
||||
copyPassword: Shortcuts.globalShortcutText('copyPassword', true),
|
||||
copyUser: Shortcuts.globalShortcutText('copyUser', true),
|
||||
copyUrl: Shortcuts.globalShortcutText('copyUrl', true),
|
||||
copyOtp: Shortcuts.globalShortcutText('copyOtp', true),
|
||||
restoreApp: Shortcuts.globalShortcutText('restoreApp', true)
|
||||
}
|
||||
: undefined
|
||||
});
|
||||
}
|
||||
|
||||
shortcutClick(e) {
|
||||
const globalShortcutType = e.target.dataset.shortcut;
|
||||
|
||||
const existing = $(`.shortcut__editor[data-shortcut=${globalShortcutType}]`);
|
||||
if (existing.length) {
|
||||
existing.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcutEditor = $('<div/>')
|
||||
.addClass('shortcut__editor')
|
||||
.attr('data-shortcut', globalShortcutType);
|
||||
$('<div/>').text(Locale.setShEdit).appendTo(shortcutEditor);
|
||||
const shortcutEditorInput = $('<input/>')
|
||||
.addClass('shortcut__editor-input')
|
||||
.val(Shortcuts.globalShortcutText(globalShortcutType))
|
||||
.appendTo(shortcutEditor);
|
||||
if (!Features.isMac) {
|
||||
shortcutEditorInput.addClass('shortcut__editor-input--large');
|
||||
}
|
||||
|
||||
shortcutEditor.insertAfter($(e.target).parent());
|
||||
shortcutEditorInput.focus();
|
||||
shortcutEditorInput.on('keypress', (e) => e.preventDefault());
|
||||
shortcutEditorInput.on('keydown', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (e.which === Keys.DOM_VK_DELETE || e.which === Keys.DOM_VK_BACK_SPACE) {
|
||||
Shortcuts.setGlobalShortcut(globalShortcutType, undefined);
|
||||
this.render();
|
||||
return;
|
||||
}
|
||||
if (e.which === Keys.DOM_VK_ESCAPE) {
|
||||
shortcutEditorInput.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = Shortcuts.keyEventToShortcut(e);
|
||||
const presentableShortcutText = Shortcuts.presentShortcut(shortcut.value);
|
||||
|
||||
shortcutEditorInput.val(presentableShortcutText);
|
||||
|
||||
const exists = this.systemShortcuts.includes(shortcut.text);
|
||||
shortcutEditorInput.toggleClass('input--error', exists);
|
||||
|
||||
const isValid = shortcut.valid && !exists;
|
||||
if (isValid) {
|
||||
Shortcuts.setGlobalShortcut(globalShortcutType, shortcut.value);
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { SettingsShortcutsView };
|
|
@ -0,0 +1,76 @@
|
|||
import { FunctionComponent, h } from 'preact';
|
||||
import { GlobalShortcutDef, SettingsShortcutsView } from 'views/settings/settings-shortcuts-view';
|
||||
import { Features } from 'util/features';
|
||||
import { Launcher } from 'comp/launcher';
|
||||
import { GlobalShortcutType, Shortcuts } from 'comp/browser/shortcuts';
|
||||
import { Locale } from 'util/locale';
|
||||
import { useModelWatcher } from 'util/ui/hooks';
|
||||
import { AppSettings } from 'models/app-settings';
|
||||
import { useState } from 'preact/hooks';
|
||||
|
||||
function getGlobalShortcuts(): GlobalShortcutDef[] {
|
||||
return [
|
||||
{
|
||||
id: 'autoType',
|
||||
title: Locale.setShAutoTypeGlobal,
|
||||
value: Shortcuts.globalShortcutText('autoType', true)
|
||||
},
|
||||
{
|
||||
id: 'copyPassword',
|
||||
title: Locale.setShCopyPassOnly,
|
||||
value: Shortcuts.globalShortcutText('copyPassword', true)
|
||||
},
|
||||
{
|
||||
id: 'copyUser',
|
||||
title: Locale.setShCopyUser,
|
||||
value: Shortcuts.globalShortcutText('copyUser', true)
|
||||
},
|
||||
{
|
||||
id: 'copyUrl',
|
||||
title: Locale.setShCopyUrl,
|
||||
value: Shortcuts.globalShortcutText('copyUrl', true)
|
||||
},
|
||||
{
|
||||
id: 'copyOtp',
|
||||
title: Locale.setShCopyOtp,
|
||||
value: Shortcuts.globalShortcutText('copyOtp', true)
|
||||
},
|
||||
{
|
||||
id: 'restoreApp',
|
||||
title: Locale.setShRestoreApp.with('KeeWeb'),
|
||||
value: Shortcuts.globalShortcutText('restoreApp', true)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
export const SettingsShortcuts: FunctionComponent = () => {
|
||||
useModelWatcher(AppSettings);
|
||||
const [editedShortcut, setEditedShortcut] = useState('');
|
||||
|
||||
const shortcutClicked = (id: GlobalShortcutType) => {
|
||||
setEditedShortcut(editedShortcut === id ? '' : id);
|
||||
};
|
||||
|
||||
const shortcutChanged = (id: GlobalShortcutType, shortcut: string) => {
|
||||
Shortcuts.setGlobalShortcut(id, shortcut);
|
||||
setEditedShortcut('');
|
||||
};
|
||||
|
||||
const shortcutReset = (id: GlobalShortcutType) => {
|
||||
Shortcuts.setGlobalShortcut(id, null);
|
||||
setEditedShortcut('');
|
||||
};
|
||||
|
||||
return h(SettingsShortcutsView, {
|
||||
cmd: Shortcuts.actionShortcutSymbol(true),
|
||||
alt: Shortcuts.altShortcutSymbol(true),
|
||||
globalIsLarge: !Features.isMac,
|
||||
autoTypeSupported: !!Launcher,
|
||||
globalShortcuts: Launcher ? getGlobalShortcuts() : undefined,
|
||||
editedShortcut,
|
||||
|
||||
shortcutClicked,
|
||||
shortcutChanged,
|
||||
shortcutReset
|
||||
});
|
||||
};
|
|
@ -0,0 +1,84 @@
|
|||
import { FunctionComponent } from 'preact';
|
||||
import { Locale } from 'util/locale';
|
||||
import { classes } from 'util/ui/classes';
|
||||
import { useLayoutEffect, useRef, useState } from 'preact/hooks';
|
||||
import { Keys } from 'const/keys';
|
||||
import { Shortcuts } from 'comp/browser/shortcuts';
|
||||
|
||||
const SystemShortcuts = [
|
||||
'Meta+A',
|
||||
'Alt+A',
|
||||
'Alt+C',
|
||||
'Alt+D',
|
||||
'Meta+F',
|
||||
'Meta+C',
|
||||
'Meta+B',
|
||||
'Meta+U',
|
||||
'Meta+T',
|
||||
'Alt+N',
|
||||
'Meta+O',
|
||||
'Meta+S',
|
||||
'Meta+G',
|
||||
'Meta+,',
|
||||
'Meta+L'
|
||||
];
|
||||
|
||||
export const ShortcutEditor: FunctionComponent<{
|
||||
shortcut: string;
|
||||
large: boolean;
|
||||
|
||||
onSet: (shortcut: string) => void;
|
||||
onClear: () => void;
|
||||
}> = ({ shortcut, large, onSet, onClear }) => {
|
||||
const shortcutInput = useRef<HTMLInputElement>();
|
||||
|
||||
const [shortcutExists, setShortcutExists] = useState(false);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
shortcutInput.current.focus();
|
||||
}, []);
|
||||
|
||||
const inputKeyDown = (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
|
||||
if (e.which === Keys.DOM_VK_DELETE || e.which === Keys.DOM_VK_BACK_SPACE) {
|
||||
onClear();
|
||||
return;
|
||||
}
|
||||
if (e.which === Keys.DOM_VK_ESCAPE) {
|
||||
shortcutInput.current.blur();
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = Shortcuts.keyEventToShortcut(e);
|
||||
const presentableShortcutText = Shortcuts.presentShortcut(shortcut.value);
|
||||
|
||||
shortcutInput.current.value = presentableShortcutText;
|
||||
|
||||
const exists = SystemShortcuts.includes(shortcut.value);
|
||||
setShortcutExists(exists);
|
||||
|
||||
const isValid = shortcut.valid && !exists;
|
||||
if (isValid) {
|
||||
onSet(shortcut.value);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="shortcut__editor">
|
||||
<div>{Locale.setShEdit}</div>
|
||||
<input
|
||||
ref={shortcutInput}
|
||||
class={classes({
|
||||
'shortcut__editor-input': true,
|
||||
'shortcut__editor-input--large': large,
|
||||
'input--error': shortcutExists
|
||||
})}
|
||||
value={shortcut}
|
||||
onKeyPress={(e) => e.preventDefault()}
|
||||
onKeyDown={inputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,129 @@
|
|||
import { FunctionComponent } from 'preact';
|
||||
import { Locale } from 'util/locale';
|
||||
import { classes } from 'util/ui/classes';
|
||||
import { ShortcutEditor } from 'views/components/shortcut-editor';
|
||||
import { GlobalShortcutType } from 'comp/browser/shortcuts';
|
||||
|
||||
export interface GlobalShortcutDef {
|
||||
id: GlobalShortcutType;
|
||||
value: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const SettingsShortcutsView: FunctionComponent<{
|
||||
cmd: string;
|
||||
alt: string;
|
||||
autoTypeSupported: boolean;
|
||||
globalShortcuts?: GlobalShortcutDef[];
|
||||
globalIsLarge: boolean;
|
||||
editedShortcut?: string;
|
||||
|
||||
shortcutClicked: (id: GlobalShortcutType) => void;
|
||||
shortcutChanged: (id: GlobalShortcutType, shortcut: string) => void;
|
||||
shortcutReset: (id: GlobalShortcutType) => void;
|
||||
}> = ({
|
||||
cmd,
|
||||
alt,
|
||||
autoTypeSupported,
|
||||
globalShortcuts,
|
||||
globalIsLarge,
|
||||
editedShortcut,
|
||||
|
||||
shortcutClicked,
|
||||
shortcutChanged,
|
||||
shortcutReset
|
||||
}) => {
|
||||
return (
|
||||
<div class="settings__content">
|
||||
<h1>
|
||||
<i class="fa fa-keyboard settings__head-icon" /> {Locale.setShTitle}
|
||||
</h1>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}A</span> {Locale.or}{' '}
|
||||
<span class="shortcut">{alt}A</span> {Locale.setShShowAll}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{alt}C</span> {Locale.setShColors}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{alt}D</span> {Locale.setShTrash}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}F</span> {Locale.setShFind}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">esc</span> {Locale.setShClearSearch}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}C</span> {Locale.setShCopyPass}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}B</span> {Locale.setShCopyUser}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}U</span> {Locale.setShCopyUrl}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{alt}2</span> {Locale.setShCopyOtp}
|
||||
</div>
|
||||
{autoTypeSupported ? (
|
||||
<div>
|
||||
<span class="shortcut">{cmd}T</span> {Locale.setShAutoType}
|
||||
</div>
|
||||
) : null}
|
||||
<div>
|
||||
<span class="shortcut">↑</span> {Locale.setShPrev}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">↓</span> {Locale.setShNext}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{alt}N</span> {Locale.setShCreateEntry}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}O</span> {Locale.setShOpen}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}S</span> {Locale.setShSave}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}G</span> {Locale.setShGen}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd},</span> {Locale.setShSet}
|
||||
</div>
|
||||
<div>
|
||||
<span class="shortcut">{cmd}L</span> {Locale.setShLock}
|
||||
</div>
|
||||
|
||||
{globalShortcuts ? (
|
||||
<>
|
||||
<p>{Locale.setShGlobal}</p>
|
||||
{globalShortcuts.map((sh) => (
|
||||
<div key={sh.id}>
|
||||
<button
|
||||
class={classes({
|
||||
'shortcut': true,
|
||||
'btn-silent': true,
|
||||
'shortcut-large': globalIsLarge
|
||||
})}
|
||||
onClick={() => shortcutClicked(sh.id)}
|
||||
>
|
||||
{sh.value}
|
||||
</button>{' '}
|
||||
{sh.title}
|
||||
{sh.id === editedShortcut ? (
|
||||
<ShortcutEditor
|
||||
shortcut={sh.value}
|
||||
large={globalIsLarge}
|
||||
onSet={(shortcut) => shortcutChanged(sh.id, shortcut)}
|
||||
onClear={() => shortcutReset(sh.id)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -3,6 +3,7 @@ import { SettingsPage } from 'models/workspace';
|
|||
import { Scrollable } from 'views/components/scrollable';
|
||||
import { Locale } from 'util/locale';
|
||||
import { SettingsGeneral } from 'ui/settings/settings-general';
|
||||
import { SettingsShortcuts } from 'ui/settings/settings-shortcuts';
|
||||
import { SettingsAbout } from 'ui/settings/settings-about';
|
||||
import { SettingsHelp } from 'ui/settings/settings-help';
|
||||
|
||||
|
@ -19,6 +20,7 @@ export const SettingsView: FunctionComponent<{
|
|||
</div>
|
||||
<Scrollable>
|
||||
{page === 'general' ? <SettingsGeneral /> : null}
|
||||
{page === 'shortcuts' ? <SettingsShortcuts /> : null}
|
||||
{page === 'about' ? <SettingsAbout /> : null}
|
||||
{page === 'help' ? <SettingsHelp /> : null}
|
||||
</Scrollable>
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<div class="settings__content">
|
||||
<h1><i class="fa fa-keyboard settings__head-icon"></i> {{res 'setShTitle'}}</h1>
|
||||
<div><span class="shortcut">{{cmd}}A</span> {{res 'or'}} <span class="shortcut">{{alt}}A</span> {{res 'setShShowAll'}}</div>
|
||||
<div><span class="shortcut">{{alt}}C</span> {{res 'setShColors'}}</div>
|
||||
<div><span class="shortcut">{{alt}}D</span> {{res 'setShTrash'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}F</span> {{res 'setShFind'}}</div>
|
||||
<div><span class="shortcut">esc</span> {{res 'setShClearSearch'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}C</span> {{res 'setShCopyPass'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}B</span> {{res 'setShCopyUser'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}U</span> {{res 'setShCopyUrl'}}</div>
|
||||
<div><span class="shortcut">{{alt}}2</span> {{res 'setShCopyOtp'}}</div>
|
||||
{{#if autoTypeSupported}}
|
||||
<div><span class="shortcut">{{cmd}}T</span> {{res 'setShAutoType'}}</div>
|
||||
{{/if}}
|
||||
<div><span class="shortcut">↑</span> {{res 'setShPrev'}}</div>
|
||||
<div><span class="shortcut">↓</span> {{res 'setShNext'}}</div>
|
||||
<div><span class="shortcut">{{alt}}N</span> {{res 'setShCreateEntry'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}O</span> {{res 'setShOpen'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}S</span> {{res 'setShSave'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}G</span> {{res 'setShGen'}}</div>
|
||||
<div><span class="shortcut">{{cmd}},</span> {{res 'setShSet'}}</div>
|
||||
<div><span class="shortcut">{{cmd}}L</span> {{res 'setShLock'}}</div>
|
||||
{{#if globalShortcuts}}
|
||||
<p>{{res 'setShGlobal'}}</p>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="autoType">{{globalShortcuts.autoType}}</button> {{res 'setShAutoTypeGlobal'}}</div>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="copyPassword">{{globalShortcuts.copyPassword}}</button> {{res 'setShCopyPassOnly'}}</div>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="copyUser">{{globalShortcuts.copyUser}}</button> {{res 'setShCopyUser'}}</div>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="copyUrl">{{globalShortcuts.copyUrl}}</button> {{res 'setShCopyUrl'}}</div>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="copyOtp">{{globalShortcuts.copyOtp}}</button> {{res 'setShCopyOtp'}}</div>
|
||||
<div><button class="shortcut btn-silent {{#if globalIsLarge}}shortcut-large{{/if}}"
|
||||
data-shortcut="restoreApp">{{globalShortcuts.restoreApp}}</button> {{#res 'setShRestoreApp'}}KeeWeb{{/res}}</div>
|
||||
{{/if}}
|
||||
</div>
|
Loading…
Reference in New Issue