feat: singlecharacter shortcuts
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
:title="LEFT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
|
:title="LEFT_SIDEBAR ? $t('hide.sidebar') : $t('show.sidebar')"
|
||||||
icon="menu_open"
|
icon="menu_open"
|
||||||
:class="{ 'transform rotate-180': !LEFT_SIDEBAR }"
|
:class="{ 'transform rotate-180': !LEFT_SIDEBAR }"
|
||||||
@click.native="toggleSetting('LEFT_SIDEBAR')"
|
@click.native="LEFT_SIDEBAR = !LEFT_SIDEBAR"
|
||||||
/>
|
/>
|
||||||
<ButtonSecondary
|
<ButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
:class="{
|
:class="{
|
||||||
'!text-accent focus:text-accent hover:text-accent': ZEN_MODE,
|
'!text-accent focus:text-accent hover:text-accent': ZEN_MODE,
|
||||||
}"
|
}"
|
||||||
@click.native="toggleSetting('ZEN_MODE')"
|
@click.native="ZEN_MODE = !ZEN_MODE"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
icon="menu_open"
|
icon="menu_open"
|
||||||
class="transform rotate-180"
|
class="transform rotate-180"
|
||||||
:class="{ 'rotate-0': !RIGHT_SIDEBAR }"
|
:class="{ 'rotate-0': !RIGHT_SIDEBAR }"
|
||||||
@click.native="toggleSetting('RIGHT_SIDEBAR')"
|
@click.native="RIGHT_SIDEBAR = !RIGHT_SIDEBAR"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,47 +107,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "@nuxtjs/composition-api"
|
import { defineComponent, ref } from "@nuxtjs/composition-api"
|
||||||
import {
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
defaultSettings,
|
import { useSetting } from "~/newstore/settings"
|
||||||
getSettingSubject,
|
|
||||||
applySetting,
|
|
||||||
toggleSetting,
|
|
||||||
} from "~/newstore/settings"
|
|
||||||
import type { KeysMatching } from "~/types/ts-utils"
|
|
||||||
|
|
||||||
type SettingsType = typeof defaultSettings
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
setup() {
|
||||||
|
const showShortcuts = ref(false)
|
||||||
|
|
||||||
|
defineActionHandler("flyouts.keybinds.toggle", () => {
|
||||||
|
showShortcuts.value = !showShortcuts.value
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
LEFT_SIDEBAR: null,
|
LEFT_SIDEBAR: useSetting("LEFT_SIDEBAR"),
|
||||||
RIGHT_SIDEBAR: null,
|
RIGHT_SIDEBAR: useSetting("RIGHT_SIDEBAR"),
|
||||||
ZEN_MODE: null,
|
ZEN_MODE: useSetting("ZEN_MODE"),
|
||||||
showShortcuts: false,
|
|
||||||
navigatorShare: navigator.share,
|
navigatorShare: !!navigator.share,
|
||||||
}
|
|
||||||
},
|
showShortcuts,
|
||||||
subscriptions() {
|
|
||||||
return {
|
|
||||||
LEFT_SIDEBAR: getSettingSubject("LEFT_SIDEBAR"),
|
|
||||||
RIGHT_SIDEBAR: getSettingSubject("RIGHT_SIDEBAR"),
|
|
||||||
ZEN_MODE: getSettingSubject("ZEN_MODE"),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
ZEN_MODE(ZEN_MODE) {
|
ZEN_MODE() {
|
||||||
this.applySetting("LEFT_SIDEBAR", !ZEN_MODE)
|
this.LEFT_SIDEBAR = !this.ZEN_MODE
|
||||||
// this.applySetting("RIGHT_SIDEBAR", !ZEN_MODE)
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleSetting<K extends KeysMatching<SettingsType, boolean>>(key: K) {
|
|
||||||
toggleSetting(key)
|
|
||||||
},
|
|
||||||
applySetting<K extends keyof SettingsType>(key: K, value: SettingsType[K]) {
|
|
||||||
applySetting(key, value)
|
|
||||||
},
|
|
||||||
nativeShare() {
|
nativeShare() {
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
navigator
|
navigator
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type HoppAction =
|
|||||||
| "request.method.post" // Select POST Method
|
| "request.method.post" // Select POST Method
|
||||||
| "request.method.put" // Select PUT Method
|
| "request.method.put" // Select PUT Method
|
||||||
| "request.method.delete" // Select DELETE Method
|
| "request.method.delete" // Select DELETE Method
|
||||||
|
| "flyouts.keybinds.toggle" // Shows the keybinds flyout
|
||||||
|
|
||||||
type BoundActionList = {
|
type BoundActionList = {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
|
import { onBeforeUnmount, onMounted } from "@nuxtjs/composition-api"
|
||||||
import { HoppAction, invokeAction } from "./actions"
|
import { HoppAction, invokeAction } from "./actions"
|
||||||
import { isAppleDevice } from "./platformutils"
|
import { isAppleDevice } from "./platformutils"
|
||||||
|
import { isDOMElement, isTypableElement } from "./utils/dom"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This variable keeps track whether keybindings are being accepted
|
* This variable keeps track whether keybindings are being accepted
|
||||||
@@ -9,6 +10,13 @@ import { isAppleDevice } from "./platformutils"
|
|||||||
*/
|
*/
|
||||||
let keybindingsEnabled = true
|
let keybindingsEnabled = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This variable keeps track whether the currently focused element on the document
|
||||||
|
* is something that accepts a keyboard input
|
||||||
|
* (this is to prevent single character shortcuts from firing while typing)
|
||||||
|
*/
|
||||||
|
let focusNotTypable = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alt is also regarded as macOS OPTION (⌥) key
|
* Alt is also regarded as macOS OPTION (⌥) key
|
||||||
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
||||||
@@ -19,10 +27,14 @@ type ModifierKeys = "ctrl" | "alt"
|
|||||||
type Key = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k'
|
type Key = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k'
|
||||||
| 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x'
|
| 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x'
|
||||||
| 'y' | 'z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
| 'y' | 'z' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
||||||
| "up" | "down" | "left" | "right"
|
| "up" | "down" | "left" | "right" | "?"
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
type ShortcutKey = `${ModifierKeys}-${Key}`
|
type ModifierBasedShortcutKey = `${ModifierKeys}-${Key}`
|
||||||
|
// Singular keybindings (these will be disabled when an input-ish area has been focused)
|
||||||
|
type SingleCharacterShortcutKey = `${Key}`
|
||||||
|
|
||||||
|
type ShortcutKey = ModifierBasedShortcutKey | SingleCharacterShortcutKey
|
||||||
|
|
||||||
export const bindings: {
|
export const bindings: {
|
||||||
// eslint-disable-next-line no-unused-vars
|
// eslint-disable-next-line no-unused-vars
|
||||||
@@ -39,6 +51,7 @@ export const bindings: {
|
|||||||
"alt-p": "request.method.post",
|
"alt-p": "request.method.post",
|
||||||
"alt-u": "request.method.put",
|
"alt-u": "request.method.put",
|
||||||
"alt-x": "request.method.delete",
|
"alt-x": "request.method.delete",
|
||||||
|
"?": "flyouts.keybinds.toggle",
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,13 +62,35 @@ export const bindings: {
|
|||||||
export function hookKeybindingsListener() {
|
export function hookKeybindingsListener() {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener("keydown", handleKeyDown)
|
document.addEventListener("keydown", handleKeyDown)
|
||||||
|
document.addEventListener("focusin", handleFocusUpdate)
|
||||||
|
document.addEventListener("focusout", handleFocusUpdate)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener("keydown", handleKeyDown)
|
document.removeEventListener("keydown", handleKeyDown)
|
||||||
|
document.removeEventListener("focusin", handleFocusUpdate)
|
||||||
|
document.removeEventListener("focusout", handleFocusUpdate)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFocusUpdate(ev: FocusEvent) {
|
||||||
|
const target = ev.target
|
||||||
|
|
||||||
|
if (isDOMElement(target) && isTypableElement(target)) {
|
||||||
|
if (focusNotTypable) {
|
||||||
|
console.log(
|
||||||
|
"Single Char keybindings are disabled because typable element is having focus"
|
||||||
|
)
|
||||||
|
focusNotTypable = false
|
||||||
|
}
|
||||||
|
} else if (!focusNotTypable) {
|
||||||
|
console.log(
|
||||||
|
"Single Char keybindings are restored because typable element is no longer focused"
|
||||||
|
)
|
||||||
|
focusNotTypable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleKeyDown(ev: KeyboardEvent) {
|
function handleKeyDown(ev: KeyboardEvent) {
|
||||||
// Do not check keybinds if the mode is disabled
|
// Do not check keybinds if the mode is disabled
|
||||||
if (!keybindingsEnabled) return
|
if (!keybindingsEnabled) return
|
||||||
@@ -73,7 +108,18 @@ function handleKeyDown(ev: KeyboardEvent) {
|
|||||||
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
function generateKeybindingString(ev: KeyboardEvent): ShortcutKey | null {
|
||||||
// All our keybinds need to have one modifier pressed atleast
|
// All our keybinds need to have one modifier pressed atleast
|
||||||
const modifierKey = getActiveModifier(ev)
|
const modifierKey = getActiveModifier(ev)
|
||||||
if (!modifierKey) return null
|
|
||||||
|
const target = ev.target
|
||||||
|
console.log(target)
|
||||||
|
|
||||||
|
debugger
|
||||||
|
if (!modifierKey && !(isDOMElement(target) && isTypableElement(target))) {
|
||||||
|
// Check if we are having singulars instead
|
||||||
|
const key = getPressedKey(ev)
|
||||||
|
|
||||||
|
if (!key) return null
|
||||||
|
else return `${key}` as ShortcutKey
|
||||||
|
}
|
||||||
|
|
||||||
const key = getPressedKey(ev)
|
const key = getPressedKey(ev)
|
||||||
if (!key) return null
|
if (!key) return null
|
||||||
@@ -97,6 +143,9 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
|
|||||||
// Check if number keys
|
// Check if number keys
|
||||||
if (val.length === 1 && !isNaN(val as any)) return val as Key
|
if (val.length === 1 && !isNaN(val as any)) return val as Key
|
||||||
|
|
||||||
|
// Check if question mark
|
||||||
|
if (val === "?") return "?"
|
||||||
|
|
||||||
// If no other cases match, this is not a valid key
|
// If no other cases match, this is not a valid key
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
19
helpers/utils/dom.ts
Normal file
19
helpers/utils/dom.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
export function isDOMElement(el: any): el is HTMLElement {
|
||||||
|
return !!el && (el instanceof Element || el instanceof HTMLElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTypableElement(el: HTMLElement): boolean {
|
||||||
|
// If content editable, then it is editable
|
||||||
|
if (el.isContentEditable) return true
|
||||||
|
|
||||||
|
// If element is an input and the input is enabled, then it is typable
|
||||||
|
if (el.tagName === "INPUT") {
|
||||||
|
return !(el as HTMLInputElement).disabled
|
||||||
|
}
|
||||||
|
// If element is a textarea and the input is enabled, then it is typable
|
||||||
|
if (el.tagName === "TEXTAREA") {
|
||||||
|
return !(el as HTMLTextAreaElement).disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user