feat: keyboard shortcuts for primary actions (#2030)
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user