feat: keyboard shortcuts for primary actions (#2030)

This commit is contained in:
Mo
2022-11-18 09:01:48 -06:00
committed by GitHub
parent 0309912f98
commit f49ba6bd4d
67 changed files with 1296 additions and 555 deletions

View File

@@ -9,7 +9,7 @@ import { ItemListController } from '@/Controllers/ItemList/ItemListController'
import { FilesController } from '@/Controllers/FilesController'
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { NotesController } from '@/Controllers/NotesController'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { ElementIds } from '@/Constants/ElementIDs'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { ContentType, SNTag } from '@standardnotes/snjs'

View File

@@ -1,4 +1,12 @@
import { KeyboardKey, KeyboardModifier } from '@standardnotes/ui-services'
import {
CANCEL_SEARCH_COMMAND,
CREATE_NEW_NOTE_KEYBOARD_COMMAND,
keyboardStringForShortcut,
NEXT_LIST_ITEM_KEYBOARD_COMMAND,
PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
SEARCH_KEYBOARD_COMMAND,
SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
} from '@standardnotes/ui-services'
import { WebApplication } from '@/Application/Application'
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
import { FileItem, PrefKey } from '@standardnotes/snjs'
@@ -12,7 +20,7 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
import { FilesController } from '@/Controllers/FilesController'
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
import { NotesController } from '@/Controllers/NotesController'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
import { ElementIds } from '@/Constants/ElementIDs'
import ContentListHeader from './Header/ContentListHeader'
@@ -110,7 +118,6 @@ const ContentListView: FunctionComponent<Props> = ({
panelWidth,
renderedItems,
items,
searchBarElement,
isCurrentNoteTemplate,
} = itemListController
@@ -142,80 +149,69 @@ const ContentListView: FunctionComponent<Props> = ({
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
useEffect(() => {
const searchBarElement = document.getElementById(ElementIds.SearchBar)
/**
* In the browser we're not allowed to override cmd/ctrl + n, so we have to
* use Control modifier as well. These rules don't apply to desktop, but
* probably better to be consistent.
*/
const disposeNewNoteKeyObserver = application.io.addKeyObserver({
key: 'n',
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl],
onKeyDown: (event) => {
event.preventDefault()
void addNewItem()
return application.keyboardService.addCommandHandlers([
{
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
void addNewItem()
},
},
})
const disposeNextNoteKeyObserver = application.io.addKeyObserver({
key: KeyboardKey.Down,
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
onKeyDown: () => {
if (searchBarElement === document.activeElement) {
searchBarElement?.blur()
}
selectNextItem()
{
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
onKeyDown: () => {
if (searchBarElement === document.activeElement) {
searchBarElement?.blur()
}
selectNextItem()
},
},
})
const disposePreviousNoteKeyObserver = application.io.addKeyObserver({
key: KeyboardKey.Up,
element: document.body,
onKeyDown: () => {
selectPreviousItem()
{
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
element: document.body,
onKeyDown: () => {
selectPreviousItem()
},
},
})
const disposeSearchKeyObserver = application.io.addKeyObserver({
key: 'f',
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift],
onKeyDown: () => {
if (searchBarElement) {
searchBarElement.focus()
}
{
command: SEARCH_KEYBOARD_COMMAND,
onKeyDown: (event) => {
if (searchBarElement) {
event.preventDefault()
searchBarElement.focus()
}
},
},
})
const disposeSelectAllKeyObserver = application.io.addKeyObserver({
key: 'a',
modifiers: [KeyboardModifier.Ctrl],
onKeyDown: (event) => {
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
if (!isTargetInsideContentList) {
return
}
event.preventDefault()
selectionController.selectAll()
{
command: CANCEL_SEARCH_COMMAND,
onKeyDown: () => {
if (searchBarElement) {
searchBarElement.blur()
}
},
},
})
{
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
onKeyDown: (event) => {
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
return () => {
disposeNewNoteKeyObserver()
disposeNextNoteKeyObserver()
disposePreviousNoteKeyObserver()
disposeSearchKeyObserver()
disposeSelectAllKeyObserver()
}
}, [
addNewItem,
application.io,
createNewNote,
searchBarElement,
selectNextItem,
selectPreviousItem,
selectionController,
])
if (!isTargetInsideContentList) {
return
}
event.preventDefault()
selectionController.selectAll()
},
},
])
}, [addNewItem, application.keyboardService, createNewNote, selectNextItem, selectPreviousItem, selectionController])
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
@@ -229,11 +225,17 @@ const ContentListView: FunctionComponent<Props> = ({
[application, selectedAsTag, navigationController],
)
const addButtonLabel = useMemo(
() => (isFilesSmartView ? 'Upload file' : 'Create a new note in the selected tag'),
[isFilesSmartView],
const shortcutForCreate = useMemo(
() => application.keyboardService.keyboardShortcutForCommand(CREATE_NEW_NOTE_KEYBOARD_COMMAND),
[application],
)
const addButtonLabel = useMemo(() => {
return isFilesSmartView
? 'Upload file'
: `Create a new note in the selected tag (${shortcutForCreate && keyboardStringForShortcut(shortcutForCreate)})`
}, [isFilesSmartView, shortcutForCreate])
const matchesMediumBreakpoint = useMediaQuery(MediaQueryBreakpoints.md)
const matchesXLBreakpoint = useMediaQuery(MediaQueryBreakpoints.xl)
const isTabletScreenSize = matchesMediumBreakpoint && !matchesXLBreakpoint

View File

@@ -1,6 +1,6 @@
import { WebApplication } from '@/Application/Application'
import { FilesController } from '@/Controllers/FilesController'
import { NotesController } from '@/Controllers/NotesController'
import { NotesController } from '@/Controllers/NotesController/NotesController'
import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
import { ListableContentItem } from './ListableContentItem'