feat: keyboard shortcuts for common actions (closes #2)

This commit is contained in:
ae 2025-05-05 12:42:09 +03:00
parent 99b515d1d1
commit 6b554cf90b
Signed by: ae
GPG Key ID: 995EFD5C1B532B3E
6 changed files with 65 additions and 24 deletions

View File

@ -498,12 +498,12 @@
@apply font-copernicus mb-4 text-center text-4xl;
}
.greeting-bottom-link {
@apply cursor-pointer text-[var(--light-accent)]/70 transition-colors duration-200 hover:underline;
.greeting-bottom-highlight {
@apply mx-1 cursor-default rounded-lg border-1 border-[var(--light-text)]/10 p-0.5 text-[var(--light-accent)]/70 transition-colors duration-200 hover:text-[var(--light-highlight-text)];
}
.dark .greeting-bottom-link {
@apply text-[var(--dark-accent)];
.dark .greeting-bottom-highlight {
@apply border-[var(--dark-text)]/10 text-[var(--dark-accent)] hover:text-[var(--light-highlight-text)];
}
/* * * * * * * * * * * * * */
@ -570,6 +570,14 @@
@apply border-[var(--dark-text)]/10 text-[var(--dark-text)] hover:bg-[var(--dark-foreground)]/80;
}
.button-keyboard-shortcut-hint {
@apply ml-2 rounded-lg border-1 border-[var(--light-text)]/10 px-1 text-[var(--light-text)]/50;
}
.dark .button-keyboard-shortcut-hint {
@apply border-[var(--dark-text)]/10 text-[var(--dark-text)]/50;
}
.note-action-icon-button {
@apply flex h-9 w-9 items-center justify-center rounded-lg border border-[var(--light-text)]/10 bg-transparent p-0 text-[var(--light-text)] hover:bg-[var(--light-foreground)]/80;
}
@ -700,7 +708,7 @@
@apply list-decimal;
}
/* Checkboxes ('todo items') */
/* Checkboxes */
.markdown-preview input[type="checkbox"] {
@apply mr-1 h-4 w-4 appearance-none rounded-md border-[var(--light-text)]/30 bg-[var(--light-foreground)] p-0 align-text-bottom;

View File

@ -18,8 +18,7 @@
let passwordError = ""
let isFormValid = false
// TODO (optional feature):
// during `onMount`, check whether user is *already authenticated* (redirect straight to `/` if yes)
// TODO (optional): route already authenticated users straight to "/"
// clear any errors when swapping between login/signup views
onMount(() => ($cError = null))

View File

@ -191,19 +191,20 @@
</h1>
{/if}
<!-- TODO: add pagination support for versions dropdown -->
<!-- TODO: handle versions pagination support later if it becomes an issue (`util/itemPagination.ts`) -->
<!-- Note action buttons -->
<div class="note-action-container">
<!-- Editor mode toggle button -->
{#if isLatestVersion(note)}
<button on:click={toggleEditMode} class="note-action-button w-28">
<button on:click={toggleEditMode} class="note-action-button w-28 sm:w-auto">
{#if isEditing}
<EditPen classString="mr-3 h-4 w-4" />
{:else}
<ViewEye classString="mr-3 h-4 w-4" />
{/if}
{isEditing ? "Editing" : "Viewing"}
<span class="button-keyboard-shortcut-hint hidden sm:inline">⌥ + a</span>
</button>
{:else}
<button disabled class="note-action-button w-28 cursor-not-allowed opacity-40"

View File

@ -33,6 +33,7 @@
let username = "friend"
let greetMessage = "What's up?"
let currentContentHash = ""
const keysPressed = new Map()
onMount(async (): Promise<any> => {
// the following fetch attempts to refresh any expired tokens automatically
@ -67,10 +68,42 @@
isComponentReady = true
return () => {
// listener destructors on unmount
window.removeEventListener("resize", handleResize)
}
})
const handleKeydown = (event: KeyboardEvent) => {
keysPressed.set(event.code, true)
if (keysPressed.has("AltLeft") && keysPressed.has("KeyE")) {
// toggle sidebar with alt/option + e
event.preventDefault()
isSidebarOpen = !isSidebarOpen
} else if (keysPressed.has("AltLeft") && keysPressed.has("KeyN")) {
// create new note with alt/option + n
event.preventDefault()
createNewNote()
} else if (isSidebarOpen && keysPressed.has("AltLeft") && keysPressed.has("KeyK")) {
// focus to searchbar with alt/option + k
event.preventDefault()
document.getElementById("sidebar-search-bar")?.focus()
} else if ($currentFullNote !== null && keysPressed.has("AltLeft") && keysPressed.has("KeyA")) {
// toggle note editor mode with alt/option + e
event.preventDefault()
isEditing = !isEditing
}
}
const handleKeyUp = (_event: KeyboardEvent) => {
keysPressed.clear()
}
const handleBlur = () => {
// blur = window losing focus
keysPressed.clear()
}
const loadNotes = async () => {
const notes = await apiClient.listNotes()
@ -249,6 +282,9 @@
})
</script>
<!-- Keyboard shortcuts -->
<svelte:window on:keydown={handleKeydown} on:keyup={handleKeyUp} on:blur={handleBlur} />
{#if isComponentReady}
<div class="main-layout-container">
<!-- Error notification -->
@ -309,18 +345,11 @@
<div class="greeting-container">
<p class="greeting-message">{greetMessage}</p>
<p class="greeting-message text-base">
Want to create a new note?<span class="hidden sm:inline"> </span>
<span class="block whitespace-nowrap sm:inline"
>Click
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<span
class="greeting-bottom-link"
on:click={createNewNote}
aria-roledescription="create-note-text-link">here</span
></span
>
<!-- TODO: italic & grayed out text new note creation keyboard shortcut -->
<span class="block whitespace-nowrap sm:inline">
Press
<span class="greeting-bottom-highlight">⌥ + n</span>
to create a new note
</span>
</p>
</div>
{/if}

View File

@ -156,7 +156,13 @@
</div>
<div class="relative flex flex-col px-2 pb-3">
<input type="text" placeholder="Search" bind:value={searchQuery} class="sidebar-search-bar" />
<input
type="text"
placeholder="⌥ + k"
bind:value={searchQuery}
id="sidebar-search-bar"
class="sidebar-search-bar"
/>
<Search classString="sidebar-search-bar-icon" />
</div>

View File

@ -2,8 +2,6 @@
export let classString = "h-6 w-6"
</script>
<!-- TODO: this needs to be fixed :) -->
<svg
viewBox="0 -4 20 20"
class={classString}