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

@@ -26,7 +26,7 @@ import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/t
import { action, makeObservable, observable, reaction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { NotesController } from './NotesController'
import { NotesController } from './NotesController/NotesController'
import { downloadOrShareBlobBasedOnPlatform } from '@/Utils/DownloadOrShareBasedOnPlatform'
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]

View File

@@ -4,7 +4,7 @@ import { InternalEventBus } from '@standardnotes/services'
import { WebApplication } from '@/Application/Application'
import { LinkingController } from '../LinkingController'
import { NavigationController } from '../Navigation/NavigationController'
import { NotesController } from '../NotesController'
import { NotesController } from '../NotesController/NotesController'
import { SearchOptionsController } from '../SearchOptionsController'
import { SelectedItemsController } from '../SelectedItemsController'
import { ItemListController } from './ItemListController'

View File

@@ -28,7 +28,7 @@ import { NavigationController } from '../Navigation/NavigationController'
import { CrossControllerEvent } from '../CrossControllerEvent'
import { SearchOptionsController } from '../SearchOptionsController'
import { SelectedItemsController } from '../SelectedItemsController'
import { NotesController } from '../NotesController'
import { NotesController } from '../NotesController/NotesController'
import { formatDateAndTimeForNote } from '@/Utils/DateUtils'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import dayjs from 'dayjs'
@@ -42,7 +42,6 @@ import { ItemsReloadSource } from './ItemsReloadSource'
const MinNoteCellHeight = 51.0
const DefaultListNumNotes = 20
const ElementIdSearchBar = 'search-bar'
const ElementIdScrollContainer = 'notes-scrollable'
export class ItemListController extends AbstractViewController implements InternalEventHandlerInterface {
@@ -277,10 +276,6 @@ export class ItemListController extends AbstractViewController implements Intern
this.showDisplayOptionsMenu = enabled
}
get searchBarElement() {
return document.getElementById(ElementIdSearchBar)
}
get isFiltering(): boolean {
return !!this.noteFilterText && this.noteFilterText.length > 0
}

View File

@@ -1,4 +1,4 @@
import { confirmDialog, NavigationControllerPersistableValue } from '@standardnotes/ui-services'
import { confirmDialog, CREATE_NEW_TAG_COMMAND, NavigationControllerPersistableValue } from '@standardnotes/ui-services'
import { STRING_DELETE_TAG } from '@/Constants/Strings'
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
import {
@@ -155,6 +155,15 @@ export class NavigationController
},
),
)
this.disposers.push(
this.application.keyboardService.addCommandHandler({
command: CREATE_NEW_TAG_COMMAND,
onKeyDown: () => {
this.createNewTemplate()
},
}),
)
}
private findAndSetTag = (uuid: UuidString) => {

View File

@@ -1,7 +1,9 @@
import { WebApplication } from '@/Application/Application'
import { InternalEventBus, SNNote } from '@standardnotes/snjs'
import { OPEN_NOTE_HISTORY_COMMAND } from '@standardnotes/ui-services'
import { action, makeObservable, observable } from 'mobx'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { NotesControllerInterface } from '../NotesController/NotesControllerInterface'
export class HistoryModalController extends AbstractViewController {
note?: SNNote = undefined
@@ -11,13 +13,23 @@ export class HistoryModalController extends AbstractViewController {
this.note = undefined
}
constructor(application: WebApplication, eventBus: InternalEventBus) {
constructor(application: WebApplication, eventBus: InternalEventBus, notesController: NotesControllerInterface) {
super(application, eventBus)
makeObservable(this, {
note: observable,
setNote: action,
})
this.disposers.push(
application.keyboardService.addCommandHandler({
command: OPEN_NOTE_HISTORY_COMMAND,
onKeyDown: () => {
this.openModal(notesController.firstSelectedNote)
return true
},
}),
)
}
setNote = (note: SNNote | undefined) => {

View File

@@ -1,16 +1,17 @@
import { destroyAllObjectProperties } from '@/Utils'
import { confirmDialog } from '@standardnotes/ui-services'
import { confirmDialog, PIN_NOTE_COMMAND, STAR_NOTE_COMMAND } from '@standardnotes/ui-services'
import { StringEmptyTrash, Strings, StringUtils } from '@/Constants/Strings'
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
import { SNNote, NoteMutator, ContentType, SNTag, TagMutator, InternalEventBus } from '@standardnotes/snjs'
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
import { WebApplication } from '../Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { SelectedItemsController } from './SelectedItemsController'
import { ItemListController } from './ItemList/ItemListController'
import { NavigationController } from './Navigation/NavigationController'
import { WebApplication } from '../../Application/Application'
import { AbstractViewController } from '../Abstract/AbstractViewController'
import { SelectedItemsController } from '../SelectedItemsController'
import { ItemListController } from '../ItemList/ItemListController'
import { NavigationController } from '../Navigation/NavigationController'
import { NotesControllerInterface } from './NotesControllerInterface'
export class NotesController extends AbstractViewController {
export class NotesController extends AbstractViewController implements NotesControllerInterface {
lastSelectedNote: SNNote | undefined
contextMenuOpen = false
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
@@ -57,6 +58,21 @@ export class NotesController extends AbstractViewController {
setShowProtectedWarning: action,
unselectNotes: action,
})
this.disposers.push(
this.application.keyboardService.addCommandHandler({
command: PIN_NOTE_COMMAND,
onKeyDown: () => {
this.togglePinSelectedNotes()
},
}),
this.application.keyboardService.addCommandHandler({
command: STAR_NOTE_COMMAND,
onKeyDown: () => {
this.toggleStarSelectedNotes()
},
}),
)
}
public setServicesPostConstruction(itemListController: ItemListController) {
@@ -239,6 +255,28 @@ export class NotesController extends AbstractViewController {
return false
}
togglePinSelectedNotes(): void {
const notes = this.selectedNotes
const pinned = notes.some((note) => note.pinned)
if (!pinned) {
this.setPinSelectedNotes(true)
} else {
this.setPinSelectedNotes(false)
}
}
toggleStarSelectedNotes(): void {
const notes = this.selectedNotes
const starred = notes.some((note) => note.starred)
if (!starred) {
this.setStarSelectedNotes(true)
} else {
this.setStarSelectedNotes(false)
}
}
setPinSelectedNotes(pinned: boolean): void {
this.changeSelectedNotes((mutator) => {
mutator.pinned = pinned

View File

@@ -0,0 +1,5 @@
import { SNNote } from '@standardnotes/models'
export interface NotesControllerInterface {
get firstSelectedNote(): SNNote | undefined
}

View File

@@ -1,35 +1,93 @@
import { TOGGLE_LIST_PANE_KEYBOARD_COMMAND, TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND } from '@standardnotes/ui-services'
import { ApplicationEvent, InternalEventBus, PrefKey } from '@standardnotes/snjs'
import { AppPaneId } from './../Components/ResponsivePane/AppPaneMetadata'
import { isMobileScreen } from '@/Utils'
import { makeObservable, observable, action } from 'mobx'
import { makeObservable, observable, action, computed } from 'mobx'
import { Disposer } from '@/Types/Disposer'
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
import { WebApplication } from '@/Application/Application'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { PrefDefaults } from '@/Constants/PrefDefaults'
import { PANEL_NAME_NAVIGATION, PANEL_NAME_NOTES } from '@/Constants/Constants'
export class PaneController {
const WidthForCollapsedPanel = 5
const MinimumNavPanelWidth = PrefDefaults[PrefKey.TagsPanelWidth]
const MinimumNotesPanelWidth = PrefDefaults[PrefKey.NotesPanelWidth]
export class PaneController extends AbstractViewController {
currentPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor
previousPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor
isInMobileView = isMobileScreen()
protected disposers: Disposer[] = []
constructor() {
currentNavPanelWidth = 0
currentItemsPanelWidth = 0
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
currentPane: observable,
previousPane: observable,
isInMobileView: observable,
currentNavPanelWidth: observable,
currentItemsPanelWidth: observable,
isListPaneCollapsed: computed,
isNavigationPaneCollapsed: computed,
setCurrentPane: action,
setPreviousPane: action,
setIsInMobileView: action,
toggleListPane: action,
toggleNavigationPane: action,
setCurrentItemsPanelWidth: action,
setCurrentNavPanelWidth: action,
})
this.setCurrentNavPanelWidth(application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth))
this.setCurrentItemsPanelWidth(application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth))
const mediaQuery = window.matchMedia(MediaQueryBreakpoints.md)
if (mediaQuery?.addEventListener != undefined) {
mediaQuery.addEventListener('change', this.mediumScreenMQHandler)
} else {
mediaQuery.addListener(this.mediumScreenMQHandler)
}
this.disposers.push(
application.addEventObserver(async () => {
this.setCurrentNavPanelWidth(application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth))
this.setCurrentItemsPanelWidth(application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth))
}, ApplicationEvent.PreferencesChanged),
application.keyboardService.addCommandHandler({
command: TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.toggleListPane()
},
}),
application.keyboardService.addCommandHandler({
command: TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.toggleNavigationPane()
},
}),
)
}
setCurrentNavPanelWidth(width: number) {
this.currentNavPanelWidth = width
}
setCurrentItemsPanelWidth(width: number) {
this.currentItemsPanelWidth = width
}
deinit() {
super.deinit()
const mq = window.matchMedia(MediaQueryBreakpoints.md)
if (mq?.removeEventListener != undefined) {
mq.removeEventListener('change', this.mediumScreenMQHandler)
@@ -57,4 +115,38 @@ export class PaneController {
setIsInMobileView(isInMobileView: boolean) {
this.isInMobileView = isInMobileView
}
toggleListPane = () => {
const currentItemsPanelWidth = this.application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth)
const isCollapsed = currentItemsPanelWidth <= WidthForCollapsedPanel
if (isCollapsed) {
void this.application.setPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth)
} else {
void this.application.setPreference(PrefKey.NotesPanelWidth, WidthForCollapsedPanel)
}
this.application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, !isCollapsed)
}
toggleNavigationPane = () => {
const currentNavPanelWidth = this.application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth)
const isCollapsed = currentNavPanelWidth <= WidthForCollapsedPanel
if (isCollapsed) {
void this.application.setPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth)
} else {
void this.application.setPreference(PrefKey.TagsPanelWidth, WidthForCollapsedPanel)
}
this.application.publishPanelDidResizeEvent(PANEL_NAME_NAVIGATION, !isCollapsed)
}
get isListPaneCollapsed() {
return this.currentItemsPanelWidth > WidthForCollapsedPanel
}
get isNavigationPaneCollapsed() {
return this.currentNavPanelWidth > WidthForCollapsedPanel
}
}

View File

@@ -1,11 +1,18 @@
import { InternalEventBus } from '@standardnotes/snjs'
import { WebApplication } from '@/Application/Application'
import { action, makeObservable, observable } from 'mobx'
import { AbstractViewController } from './Abstract/AbstractViewController'
import { TOGGLE_FOCUS_MODE_COMMAND } from '@standardnotes/ui-services'
import { toggleFocusMode } from '@/Utils/toggleFocusMode'
export class QuickSettingsController {
export class QuickSettingsController extends AbstractViewController {
open = false
shouldAnimateCloseMenu = false
focusModeEnabled = false
constructor() {
constructor(application: WebApplication, eventBus: InternalEventBus) {
super(application, eventBus)
makeObservable(this, {
open: observable,
shouldAnimateCloseMenu: observable,
@@ -17,6 +24,17 @@ export class QuickSettingsController {
toggle: action,
closeQuickSettingsMenu: action,
})
this.disposers.push(
application.keyboardService.addCommandHandler({
command: TOGGLE_FOCUS_MODE_COMMAND,
onKeyDown: (event) => {
event.preventDefault()
this.setFocusModeEnabled(!this.focusModeEnabled)
return true
},
}),
)
}
setOpen = (open: boolean): void => {
@@ -29,6 +47,8 @@ export class QuickSettingsController {
setFocusModeEnabled = (enabled: boolean): void => {
this.focusModeEnabled = enabled
toggleFocusMode(enabled)
}
toggle = (): void => {

View File

@@ -103,8 +103,8 @@ export class SelectedItemsController
)
}
private get io() {
return this.application.io
private get keyboardService() {
return this.application.keyboardService
}
get selectedItemsCount(): number {
@@ -196,7 +196,7 @@ export class SelectedItemsController
}
cancelMultipleSelection = () => {
this.io.cancelAllKeyboardModifiers()
this.keyboardService.cancelAllKeyboardModifiers()
const firstSelectedItem = this.firstSelectedItem
@@ -256,9 +256,9 @@ export class SelectedItemsController
log(LoggingDomain.Selection, 'selectItem', item.uuid)
const hasMeta = this.io.activeModifiers.has(KeyboardModifier.Meta)
const hasCtrl = this.io.activeModifiers.has(KeyboardModifier.Ctrl)
const hasShift = this.io.activeModifiers.has(KeyboardModifier.Shift)
const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta)
const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl)
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
const hasMoreThanOneSelected = this.selectedItemsCount > 1
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)

View File

@@ -24,7 +24,7 @@ import { action, makeObservable, observable } from 'mobx'
import { ActionsMenuController } from './ActionsMenuController'
import { FeaturesController } from './FeaturesController'
import { FilesController } from './FilesController'
import { NotesController } from './NotesController'
import { NotesController } from './NotesController/NotesController'
import { ItemListController } from './ItemList/ItemListController'
import { NoAccountWarningController } from './NoAccountWarningController'
import { PreferencesController } from './PreferencesController'
@@ -60,7 +60,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
readonly itemListController: ItemListController
readonly preferencesController: PreferencesController
readonly purchaseFlowController: PurchaseFlowController
readonly quickSettingsMenuController = new QuickSettingsController()
readonly quickSettingsMenuController: QuickSettingsController
readonly searchOptionsController: SearchOptionsController
readonly subscriptionController: SubscriptionController
readonly syncStatusController = new SyncStatusController()
@@ -92,7 +92,9 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
this.subscriptionManager = application.subscriptions
this.paneController = new PaneController()
this.quickSettingsMenuController = new QuickSettingsController(application, this.eventBus)
this.paneController = new PaneController(application, this.eventBus)
this.preferencesController = new PreferencesController(application, this.eventBus)
@@ -152,7 +154,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
this.subscriptionController,
)
this.historyModalController = new HistoryModalController(this.application, this.eventBus)
this.historyModalController = new HistoryModalController(this.application, this.eventBus, this.notesController)
this.toastService = new ToastService()