feat: keyboard shortcuts for primary actions (#2030)
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SNNote } from '@standardnotes/models'
|
||||
|
||||
export interface NotesControllerInterface {
|
||||
get firstSelectedNote(): SNNote | undefined
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user