Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding Feature for Customizable Shortcut Keys #604

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 146 additions & 5 deletions src/client/components/SettingsModal/Shortcut.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,157 @@
import React from 'react'
import React, { useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { v4 as uuid } from 'uuid'
import { RefreshCw } from 'react-feather'

import { updateShortcut } from '@/slices/settings'
import { ShortcutItem } from '@/types'

export interface ShortcutProps {
action: string
letter: string
shortcut: string
id: number
originalKey: string
shortcuts: ShortcutItem[]
}

export const Shortcut: React.FC<ShortcutProps> = ({ action, letter }) => {
export const Shortcut: React.FC<ShortcutProps> = ({
action,
shortcut,
id,
originalKey,
shortcuts,
}) => {
const [newShortcut, setNewShortcut] = React.useState<string>('')
const [currentShortcut, setCurrentShortcut] = React.useState<string>(shortcut)
const [editShortcut, setEditShortcut] = React.useState<boolean>(false)

// ===========================================================================
// Dispatch
// ===========================================================================

const dispatch = useDispatch()

const _updateShortcut = (newShortcut: string) =>
dispatch(
updateShortcut({
action,
key: shortcut,
newShortcut: newShortcut,
id: id,
originalKey: originalKey,
})
)

const _resetShortcut = () =>
dispatch(
updateShortcut({
action,
key: shortcut,
newShortcut: originalKey,
id: id,
originalKey: originalKey,
})
)

// ===========================================================================
// Hooks
// ===========================================================================

useEffect(() => {
if (newShortcut !== '') {
_updateShortcut(newShortcut)
setCurrentShortcut(newShortcut)
}
if (!editShortcut) {
return document.removeEventListener('keydown', handleKeyDown)
}

return () => document.removeEventListener('keydown', handleKeyDown)
}, [editShortcut])

useEffect(() => {
const shortcutItem = shortcuts.find((item) => item.id === id)
if (shortcutItem) {
setCurrentShortcut(shortcutItem.key)
}
}, [shortcuts])

function handleKeyDown(e): void {
e.preventDefault() // Prevent default browser behavior for the shortcut key

const key = e.key.replace('Control', 'ctrl')
if (key === 'Backspace') {
return setNewShortcut((prevShortcut) => prevShortcut.split('+').slice(0, -1).join('+'))
}

if (key === 'Enter') {
if (newShortcut === '') {
setEditShortcut(false)

return setNewShortcut(shortcut)
}
setCurrentShortcut(newShortcut)
setEditShortcut(false)

return document.removeEventListener('keydown', handleKeyDown)
}
setNewShortcut((prevShortcut) => {
if (prevShortcut.split('+').length < 3) {
return prevShortcut === '' ? key : `${prevShortcut}+${key}`
}

return prevShortcut
})
}

return (
<div className="settings-shortcut">
<div>{action}</div>
<div className="keys">
<kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>{letter}</kbd>
<div className="keys" onDoubleClick={() => setEditShortcut(!editShortcut)}>
{!editShortcut ? (
<div>
{currentShortcut.split('+').map((key, index) => {
return (
<React.Fragment key={uuid()}>
<kbd>{key.toUpperCase()}</kbd>
{index !== currentShortcut.split('+').length - 1 && <span> + </span>}
</React.Fragment>
)
})}
</div>
) : (
<div>
<kbd>
<input
onChange={() => {}}
value={newShortcut.split('+').join(' + ')}
className="shortcut-edit-input"
onBlur={() => {
if (editShortcut) {
setNewShortcut('')
setCurrentShortcut(shortcut)
setEditShortcut(false)

return document.removeEventListener('keydown', handleKeyDown)
}
}}
onFocus={() => {
setNewShortcut('')
document.addEventListener('keydown', handleKeyDown)
}}
/>
</kbd>
</div>
)}

<RefreshCw
aria-hidden="true"
size={12}
onClick={() => {
setCurrentShortcut(originalKey)
_resetShortcut()
}}
/>
</div>
</div>
)
Expand Down
11 changes: 9 additions & 2 deletions src/client/components/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@ import { Icon } from 'react-feather'
export interface TabProps {
label: string
activeTab: string
testId?: string
onClick: (label: string) => void
icon: Icon
}

export const Tab: React.FC<TabProps> = ({ activeTab, label, icon: IconCmp, onClick }) => {
export const Tab: React.FC<TabProps> = ({ activeTab, label, testId, icon: IconCmp, onClick }) => {
const className = activeTab === label ? 'tab active' : 'tab'

return (
<div role="button" key={label} className={className} onClick={() => onClick(label)}>
<div
role="button"
key={label}
data-testid={testId}
className={className}
onClick={() => onClick(label)}
>
<IconCmp size={18} className="mr-1" aria-hidden="true" focusable="false" /> {label}
</div>
)
Expand Down
1 change: 1 addition & 0 deletions src/client/components/Tabs/TabPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Icon } from 'react-feather'
export interface TabPanelProps {
label: string
icon: Icon
testId?: string
children: JSX.Element[] | JSX.Element
}

Expand Down
3 changes: 2 additions & 1 deletion src/client/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const Tabs: React.FC<TabsProps> = ({ children }) => {
<div className="tabs">
<nav className="tab-list">
{children.map((child) => {
const { label, icon } = child.props
const { label, icon, testId } = child.props

return (
<Tab
Expand All @@ -22,6 +22,7 @@ export const Tabs: React.FC<TabsProps> = ({ children }) => {
key={label}
label={label}
onClick={setActiveTab}
testId={testId}
/>
)
})}
Expand Down
36 changes: 22 additions & 14 deletions src/client/containers/KeyboardShortcuts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react'
import React, { useEffect, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import prettier from 'prettier/standalone'
import parserMarkdown from 'prettier/parser-markdown'
Expand All @@ -19,17 +19,17 @@ import { sync } from '@/slices/sync'
import { getCategories, getNotes, getSettings } from '@/selectors'
import { CategoryItem, NoteItem } from '@/types'
import { toggleDarkTheme, togglePreviewMarkdown, updateCodeMirrorOption } from '@/slices/settings'
import { shortcutMap } from '@/utils/constants'

export const KeyboardShortcuts: React.FC = () => {
// ===========================================================================
// Selectors
// ===========================================================================

const { categories } = useSelector(getCategories)
const { activeCategoryId, activeFolder, activeNoteId, notes, selectedNotesIds } = useSelector(
getNotes
)
const { darkTheme, previewMarkdown } = useSelector(getSettings)
const { activeCategoryId, activeFolder, activeNoteId, notes, selectedNotesIds } =
useSelector(getNotes)
const { darkTheme, previewMarkdown, shortcuts } = useSelector(getSettings)

const activeNote = getActiveNote(notes, activeNoteId)

Expand All @@ -47,7 +47,13 @@ export const KeyboardShortcuts: React.FC = () => {
const _swapFolder = (folder: Folder) => dispatch(swapFolder({ folder }))
const _toggleTrashNotes = (noteId: string) => dispatch(toggleTrashNotes(noteId))
const _sync = (notes: NoteItem[], categories: CategoryItem[]) =>
dispatch(sync({ notes, categories }))
dispatch(
sync({
notes,
categories,
shortcuts,
})
)
const _togglePreviewMarkdown = () => dispatch(togglePreviewMarkdown())
const _toggleDarkTheme = () => dispatch(toggleDarkTheme())
const _updateCodeMirrorOption = (key: string, value: string) =>
Expand Down Expand Up @@ -113,14 +119,16 @@ export const KeyboardShortcuts: React.FC = () => {
// Hooks
// ===========================================================================

useKey(Shortcuts.NEW_NOTE, () => newNoteHandler())
useKey(Shortcuts.NEW_CATEGORY, () => newTempCategoryHandler())
useKey(Shortcuts.DELETE_NOTE, () => trashNoteHandler())
useKey(Shortcuts.SYNC_NOTES, () => syncNotesHandler())
useKey(Shortcuts.DOWNLOAD_NOTES, () => downloadNotesHandler())
useKey(Shortcuts.PREVIEW, () => togglePreviewMarkdownHandler())
useKey(Shortcuts.TOGGLE_THEME, () => toggleDarkThemeHandler())
useKey(Shortcuts.PRETTIFY, () => prettifyNoteHandler())
const sortedShortcuts = [...shortcuts].sort((a, b) => a.id - b.id)

useKey(sortedShortcuts[0].key, () => newNoteHandler())
useKey(sortedShortcuts[1].key, () => trashNoteHandler())
useKey(sortedShortcuts[2].key, () => newTempCategoryHandler())
useKey(sortedShortcuts[3].key, () => downloadNotesHandler())
useKey(sortedShortcuts[4].key, () => syncNotesHandler())
useKey(sortedShortcuts[6].key, () => togglePreviewMarkdownHandler())
useKey(sortedShortcuts[7].key, () => toggleDarkThemeHandler())
useKey(sortedShortcuts[9].key, () => prettifyNoteHandler())

return null
}
10 changes: 5 additions & 5 deletions src/client/containers/NoteMenuBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {

import { TestID } from '@resources/TestID'
import { LastSyncedNotification } from '@/components/LastSyncedNotification'
import { NoteItem, CategoryItem } from '@/types'
import { NoteItem, CategoryItem, ShortcutItem } from '@/types'
import {
toggleSettingsModal,
togglePreviewMarkdown,
Expand All @@ -36,7 +36,7 @@ export const NoteMenuBar = () => {
const { notes, activeNoteId } = useSelector(getNotes)
const { categories } = useSelector(getCategories)
const { syncing, lastSynced, pendingSync } = useSelector(getSync)
const { darkTheme } = useSelector(getSettings)
const { darkTheme, shortcuts } = useSelector(getSettings)

// ===========================================================================
// Other
Expand Down Expand Up @@ -77,8 +77,8 @@ export const NoteMenuBar = () => {
const _togglePreviewMarkdown = () => dispatch(togglePreviewMarkdown())
const _toggleTrashNotes = (noteId: string) => dispatch(toggleTrashNotes(noteId))
const _toggleFavoriteNotes = (noteId: string) => dispatch(toggleFavoriteNotes(noteId))
const _sync = (notes: NoteItem[], categories: CategoryItem[]) =>
dispatch(sync({ notes, categories }))
const _sync = (notes: NoteItem[], categories: CategoryItem[], shortcuts: ShortcutItem[]) =>
dispatch(sync({ notes, categories, shortcuts }))
const _toggleSettingsModal = () => dispatch(toggleSettingsModal())
const _toggleDarkTheme = () => dispatch(toggleDarkTheme())
const _updateCodeMirrorOption = (key: string, value: any) =>
Expand All @@ -91,7 +91,7 @@ export const NoteMenuBar = () => {
const downloadNotesHandler = () => downloadNotes([activeNote], categories)
const favoriteNoteHandler = () => _toggleFavoriteNotes(activeNoteId)
const trashNoteHandler = () => _toggleTrashNotes(activeNoteId)
const syncNotesHandler = () => _sync(notes, categories)
const syncNotesHandler = () => _sync(notes, categories, shortcuts)
const settingsHandler = () => _toggleSettingsModal()
const toggleDarkThemeHandler = () => {
_toggleDarkTheme()
Expand Down
Loading