feat: keyboard shortcuts for primary actions (#2030)
This commit is contained in:
@@ -1,212 +0,0 @@
|
|||||||
import { removeFromArray } from '@standardnotes/utils'
|
|
||||||
|
|
||||||
export enum KeyboardKey {
|
|
||||||
Tab = 'Tab',
|
|
||||||
Backspace = 'Backspace',
|
|
||||||
Up = 'ArrowUp',
|
|
||||||
Down = 'ArrowDown',
|
|
||||||
Left = 'ArrowLeft',
|
|
||||||
Right = 'ArrowRight',
|
|
||||||
Enter = 'Enter',
|
|
||||||
Escape = 'Escape',
|
|
||||||
Home = 'Home',
|
|
||||||
End = 'End',
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum KeyboardModifier {
|
|
||||||
Shift = 'Shift',
|
|
||||||
Ctrl = 'Control',
|
|
||||||
/** ⌘ key on Mac, ⊞ key on Windows */
|
|
||||||
Meta = 'Meta',
|
|
||||||
Alt = 'Alt',
|
|
||||||
}
|
|
||||||
|
|
||||||
enum KeyboardKeyEvent {
|
|
||||||
Down = 'KeyEventDown',
|
|
||||||
Up = 'KeyEventUp',
|
|
||||||
}
|
|
||||||
|
|
||||||
type KeyboardObserver = {
|
|
||||||
key?: KeyboardKey | string
|
|
||||||
modifiers?: KeyboardModifier[]
|
|
||||||
onKeyDown?: (event: KeyboardEvent) => void
|
|
||||||
onKeyUp?: (event: KeyboardEvent) => void
|
|
||||||
element?: HTMLElement
|
|
||||||
elements?: HTMLElement[]
|
|
||||||
notElement?: HTMLElement
|
|
||||||
notElementIds?: string[]
|
|
||||||
notTags?: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export class IOService {
|
|
||||||
readonly activeModifiers = new Set<KeyboardModifier>()
|
|
||||||
private observers: KeyboardObserver[] = []
|
|
||||||
|
|
||||||
constructor(private isMac: boolean) {
|
|
||||||
window.addEventListener('keydown', this.handleKeyDown)
|
|
||||||
window.addEventListener('keyup', this.handleKeyUp)
|
|
||||||
window.addEventListener('blur', this.handleWindowBlur)
|
|
||||||
}
|
|
||||||
|
|
||||||
public deinit() {
|
|
||||||
this.observers.length = 0
|
|
||||||
window.removeEventListener('keydown', this.handleKeyDown)
|
|
||||||
window.removeEventListener('keyup', this.handleKeyUp)
|
|
||||||
window.removeEventListener('blur', this.handleWindowBlur)
|
|
||||||
;(this.handleKeyDown as unknown) = undefined
|
|
||||||
;(this.handleKeyUp as unknown) = undefined
|
|
||||||
;(this.handleWindowBlur as unknown) = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
private addActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
|
||||||
if (!modifier) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (modifier) {
|
|
||||||
case KeyboardModifier.Meta: {
|
|
||||||
if (this.isMac) {
|
|
||||||
this.activeModifiers.add(modifier)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case KeyboardModifier.Ctrl: {
|
|
||||||
if (!this.isMac) {
|
|
||||||
this.activeModifiers.add(modifier)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
this.activeModifiers.add(modifier)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private removeActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
|
||||||
if (!modifier) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.activeModifiers.delete(modifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
public cancelAllKeyboardModifiers = (): void => {
|
|
||||||
this.activeModifiers.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleComponentKeyDown = (modifier: KeyboardModifier | undefined): void => {
|
|
||||||
this.addActiveModifier(modifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
public handleComponentKeyUp = (modifier: KeyboardModifier | undefined): void => {
|
|
||||||
this.removeActiveModifier(modifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleKeyDown = (event: KeyboardEvent): void => {
|
|
||||||
this.updateAllModifiersFromEvent(event)
|
|
||||||
this.notifyObserver(event, KeyboardKeyEvent.Down)
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleKeyUp = (event: KeyboardEvent): void => {
|
|
||||||
this.updateAllModifiersFromEvent(event)
|
|
||||||
this.notifyObserver(event, KeyboardKeyEvent.Up)
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateAllModifiersFromEvent(event: KeyboardEvent): void {
|
|
||||||
for (const modifier of Object.values(KeyboardModifier)) {
|
|
||||||
if (event.getModifierState(modifier)) {
|
|
||||||
this.addActiveModifier(modifier)
|
|
||||||
} else {
|
|
||||||
this.removeActiveModifier(modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleWindowBlur = (): void => {
|
|
||||||
for (const modifier of this.activeModifiers) {
|
|
||||||
this.activeModifiers.delete(modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiersForEvent(event: KeyboardEvent): KeyboardModifier[] {
|
|
||||||
const allModifiers = Object.values(KeyboardModifier)
|
|
||||||
const eventModifiers = allModifiers.filter((modifier) => {
|
|
||||||
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
|
||||||
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
|
||||||
const matches =
|
|
||||||
((event.ctrlKey || event.key === KeyboardModifier.Ctrl) && modifier === KeyboardModifier.Ctrl) ||
|
|
||||||
((event.metaKey || event.key === KeyboardModifier.Meta) && modifier === KeyboardModifier.Meta) ||
|
|
||||||
((event.altKey || event.key === KeyboardModifier.Alt) && modifier === KeyboardModifier.Alt) ||
|
|
||||||
((event.shiftKey || event.key === KeyboardModifier.Shift) && modifier === KeyboardModifier.Shift)
|
|
||||||
|
|
||||||
return matches
|
|
||||||
})
|
|
||||||
|
|
||||||
return eventModifiers
|
|
||||||
}
|
|
||||||
|
|
||||||
private eventMatchesKeyAndModifiers(
|
|
||||||
event: KeyboardEvent,
|
|
||||||
key: KeyboardKey | string | undefined,
|
|
||||||
modifiers: KeyboardModifier[] = [],
|
|
||||||
): boolean {
|
|
||||||
const eventModifiers = this.modifiersForEvent(event)
|
|
||||||
if (eventModifiers.length !== modifiers.length) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for (const modifier of modifiers) {
|
|
||||||
if (!eventModifiers.includes(modifier)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Modifers match, check key
|
|
||||||
if (!key) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// In the browser, shift + f results in key 'f', but in Electron, shift + f results in 'F'
|
|
||||||
// In our case we don't differentiate between the two.
|
|
||||||
return key.toLowerCase() === event.key.toLowerCase()
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyObserver(event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
|
||||||
const target = event.target as HTMLElement
|
|
||||||
for (const observer of this.observers) {
|
|
||||||
if (observer.element && event.target !== observer.element) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (observer.elements && !observer.elements.includes(target)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (observer.notElement && observer.notElement === event.target) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (observer.notTags && observer.notTags.includes(target.tagName)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.eventMatchesKeyAndModifiers(event, observer.key, observer.modifiers)) {
|
|
||||||
const callback = keyEvent === KeyboardKeyEvent.Down ? observer.onKeyDown : observer.onKeyUp
|
|
||||||
if (callback) {
|
|
||||||
callback(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addKeyObserver(observer: KeyboardObserver): () => void {
|
|
||||||
this.observers.push(observer)
|
|
||||||
|
|
||||||
const thislessObservers = this.observers
|
|
||||||
return () => {
|
|
||||||
removeFromArray(thislessObservers, observer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
packages/ui-services/src/Keyboard/KeyboardCommandHandler.ts
Normal file
12
packages/ui-services/src/Keyboard/KeyboardCommandHandler.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { KeyboardCommand } from './KeyboardCommands'
|
||||||
|
|
||||||
|
export type KeyboardCommandHandler = {
|
||||||
|
command: KeyboardCommand
|
||||||
|
onKeyDown?: (event: KeyboardEvent) => boolean | void
|
||||||
|
onKeyUp?: (event: KeyboardEvent) => boolean | void
|
||||||
|
element?: HTMLElement
|
||||||
|
elements?: HTMLElement[]
|
||||||
|
notElement?: HTMLElement
|
||||||
|
notElementIds?: string[]
|
||||||
|
notTags?: string[]
|
||||||
|
}
|
||||||
26
packages/ui-services/src/Keyboard/KeyboardCommands.ts
Normal file
26
packages/ui-services/src/Keyboard/KeyboardCommands.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
export type KeyboardCommand = symbol
|
||||||
|
|
||||||
|
function createKeyboardCommand(type: string): KeyboardCommand {
|
||||||
|
return Symbol(type)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TOGGLE_LIST_PANE_KEYBOARD_COMMAND = createKeyboardCommand('TOGGLE_LIST_PANE_KEYBOARD_COMMAND')
|
||||||
|
export const TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND = createKeyboardCommand('TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND')
|
||||||
|
export const CREATE_NEW_NOTE_KEYBOARD_COMMAND = createKeyboardCommand('CREATE_NEW_NOTE_KEYBOARD_COMMAND')
|
||||||
|
export const NEXT_LIST_ITEM_KEYBOARD_COMMAND = createKeyboardCommand('NEXT_LIST_ITEM_KEYBOARD_COMMAND')
|
||||||
|
export const PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND = createKeyboardCommand('PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND')
|
||||||
|
export const SEARCH_KEYBOARD_COMMAND = createKeyboardCommand('SEARCH_KEYBOARD_COMMAND')
|
||||||
|
export const CANCEL_SEARCH_COMMAND = createKeyboardCommand('CANCEL_SEARCH_COMMAND')
|
||||||
|
export const SELECT_ALL_ITEMS_KEYBOARD_COMMAND = createKeyboardCommand('SELECT_ALL_ITEMS_KEYBOARD_COMMAND')
|
||||||
|
export const SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND = createKeyboardCommand('SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND')
|
||||||
|
export const DELETE_NOTE_KEYBOARD_COMMAND = createKeyboardCommand('DELETE_NOTE_KEYBOARD_COMMAND')
|
||||||
|
export const TAB_COMMAND = createKeyboardCommand('PLAIN_EDITOR_INSERT_TAB_KEYBOARD_COMMAND')
|
||||||
|
export const ESCAPE_COMMAND = createKeyboardCommand('ESCAPE_COMMAND')
|
||||||
|
export const TOGGLE_FOCUS_MODE_COMMAND = createKeyboardCommand('TOGGLE_FOCUS_MODE_COMMAND')
|
||||||
|
export const CHANGE_EDITOR_COMMAND = createKeyboardCommand('CHANGE_EDITOR_COMMAND')
|
||||||
|
export const FOCUS_TAGS_INPUT_COMMAND = createKeyboardCommand('FOCUS_TAGS_INPUT_COMMAND')
|
||||||
|
export const CREATE_NEW_TAG_COMMAND = createKeyboardCommand('CREATE_NEW_TAG_COMMAND')
|
||||||
|
export const OPEN_NOTE_HISTORY_COMMAND = createKeyboardCommand('OPEN_NOTE_HISTORY_COMMAND')
|
||||||
|
export const CAPTURE_SAVE_COMMAND = createKeyboardCommand('CAPTURE_SAVE_COMMAND')
|
||||||
|
export const STAR_NOTE_COMMAND = createKeyboardCommand('STAR_NOTE_COMMAND')
|
||||||
|
export const PIN_NOTE_COMMAND = createKeyboardCommand('PIN_NOTE_COMMAND')
|
||||||
12
packages/ui-services/src/Keyboard/KeyboardKey.ts
Normal file
12
packages/ui-services/src/Keyboard/KeyboardKey.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export enum KeyboardKey {
|
||||||
|
Tab = 'Tab',
|
||||||
|
Backspace = 'Backspace',
|
||||||
|
Up = 'ArrowUp',
|
||||||
|
Down = 'ArrowDown',
|
||||||
|
Left = 'ArrowLeft',
|
||||||
|
Right = 'ArrowRight',
|
||||||
|
Enter = 'Enter',
|
||||||
|
Escape = 'Escape',
|
||||||
|
Home = 'Home',
|
||||||
|
End = 'End',
|
||||||
|
}
|
||||||
4
packages/ui-services/src/Keyboard/KeyboardKeyEvent.ts
Normal file
4
packages/ui-services/src/Keyboard/KeyboardKeyEvent.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum KeyboardKeyEvent {
|
||||||
|
Down = 'KeyEventDown',
|
||||||
|
Up = 'KeyEventUp',
|
||||||
|
}
|
||||||
7
packages/ui-services/src/Keyboard/KeyboardModifier.ts
Normal file
7
packages/ui-services/src/Keyboard/KeyboardModifier.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum KeyboardModifier {
|
||||||
|
Shift = 'Shift',
|
||||||
|
Ctrl = 'Control',
|
||||||
|
/** ⌘ key on Mac, ⊞ key on Windows */
|
||||||
|
Meta = 'Meta',
|
||||||
|
Alt = 'Alt',
|
||||||
|
}
|
||||||
203
packages/ui-services/src/Keyboard/KeyboardService.ts
Normal file
203
packages/ui-services/src/Keyboard/KeyboardService.ts
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
import { Environment, Platform } from '@standardnotes/snjs'
|
||||||
|
import { removeFromArray } from '@standardnotes/utils'
|
||||||
|
import { eventMatchesKeyAndModifiers } from './eventMatchesKeyAndModifiers'
|
||||||
|
import { KeyboardCommand } from './KeyboardCommands'
|
||||||
|
import { KeyboardKeyEvent } from './KeyboardKeyEvent'
|
||||||
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
|
import { KeyboardCommandHandler } from './KeyboardCommandHandler'
|
||||||
|
import { KeyboardShortcut, PlatformedKeyboardShortcut } from './KeyboardShortcut'
|
||||||
|
import { getKeyboardShortcuts } from './getKeyboardShortcuts'
|
||||||
|
|
||||||
|
export class KeyboardService {
|
||||||
|
readonly activeModifiers = new Set<KeyboardModifier>()
|
||||||
|
private commandHandlers: KeyboardCommandHandler[] = []
|
||||||
|
private commandMap = new Map<KeyboardCommand, KeyboardShortcut>()
|
||||||
|
|
||||||
|
constructor(private platform: Platform, environment: Environment) {
|
||||||
|
window.addEventListener('keydown', this.handleKeyDown)
|
||||||
|
window.addEventListener('keyup', this.handleKeyUp)
|
||||||
|
window.addEventListener('blur', this.handleWindowBlur)
|
||||||
|
|
||||||
|
const shortcuts = getKeyboardShortcuts(platform, environment)
|
||||||
|
for (const shortcut of shortcuts) {
|
||||||
|
this.registerShortcut(shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isMac() {
|
||||||
|
return this.platform === Platform.MacDesktop || this.platform === Platform.MacWeb
|
||||||
|
}
|
||||||
|
|
||||||
|
public deinit() {
|
||||||
|
this.commandHandlers.length = 0
|
||||||
|
window.removeEventListener('keydown', this.handleKeyDown)
|
||||||
|
window.removeEventListener('keyup', this.handleKeyUp)
|
||||||
|
window.removeEventListener('blur', this.handleWindowBlur)
|
||||||
|
;(this.handleKeyDown as unknown) = undefined
|
||||||
|
;(this.handleKeyUp as unknown) = undefined
|
||||||
|
;(this.handleWindowBlur as unknown) = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
private addActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||||
|
if (!modifier) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (modifier) {
|
||||||
|
case KeyboardModifier.Meta: {
|
||||||
|
if (this.isMac) {
|
||||||
|
this.activeModifiers.add(modifier)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case KeyboardModifier.Ctrl: {
|
||||||
|
if (!this.isMac) {
|
||||||
|
this.activeModifiers.add(modifier)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
this.activeModifiers.add(modifier)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeActiveModifier = (modifier: KeyboardModifier | undefined): void => {
|
||||||
|
if (!modifier) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.activeModifiers.delete(modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
public cancelAllKeyboardModifiers = (): void => {
|
||||||
|
this.activeModifiers.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleComponentKeyDown = (modifier: KeyboardModifier | undefined): void => {
|
||||||
|
this.addActiveModifier(modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleComponentKeyUp = (modifier: KeyboardModifier | undefined): void => {
|
||||||
|
this.removeActiveModifier(modifier)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyDown = (event: KeyboardEvent): void => {
|
||||||
|
this.updateAllModifiersFromEvent(event)
|
||||||
|
|
||||||
|
this.handleKeyboardEvent(event, KeyboardKeyEvent.Down)
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyUp = (event: KeyboardEvent): void => {
|
||||||
|
this.updateAllModifiersFromEvent(event)
|
||||||
|
|
||||||
|
this.handleKeyboardEvent(event, KeyboardKeyEvent.Up)
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateAllModifiersFromEvent(event: KeyboardEvent): void {
|
||||||
|
for (const modifier of Object.values(KeyboardModifier)) {
|
||||||
|
if (event.getModifierState(modifier)) {
|
||||||
|
this.addActiveModifier(modifier)
|
||||||
|
} else {
|
||||||
|
this.removeActiveModifier(modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleWindowBlur = (): void => {
|
||||||
|
for (const modifier of this.activeModifiers) {
|
||||||
|
this.activeModifiers.delete(modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleKeyboardEvent(event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
||||||
|
for (const command of this.commandMap.keys()) {
|
||||||
|
const shortcut = this.commandMap.get(command)
|
||||||
|
if (!shortcut) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventMatchesKeyAndModifiers(event, shortcut)) {
|
||||||
|
if (shortcut.preventDefault) {
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
this.handleCommand(command, event, keyEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleCommand(command: KeyboardCommand, event: KeyboardEvent, keyEvent: KeyboardKeyEvent): void {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
|
||||||
|
for (const observer of this.commandHandlers) {
|
||||||
|
if (observer.command !== command) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer.element && event.target !== observer.element) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer.elements && !observer.elements.includes(target)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer.notElement && observer.notElement === event.target) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer.notElementIds && observer.notElementIds.includes(target.id)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (observer.notTags && observer.notTags.includes(target.tagName)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const callback = keyEvent === KeyboardKeyEvent.Down ? observer.onKeyDown : observer.onKeyUp
|
||||||
|
if (callback) {
|
||||||
|
const exclusive = callback(event)
|
||||||
|
if (exclusive) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerShortcut(shortcut: KeyboardShortcut): void {
|
||||||
|
this.commandMap.set(shortcut.command, shortcut)
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommandHandler(observer: KeyboardCommandHandler): () => void {
|
||||||
|
this.commandHandlers.push(observer)
|
||||||
|
|
||||||
|
const thislessObservers = this.commandHandlers
|
||||||
|
return () => {
|
||||||
|
observer.onKeyDown = undefined
|
||||||
|
observer.onKeyDown = undefined
|
||||||
|
removeFromArray(thislessObservers, observer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addCommandHandlers(handlers: KeyboardCommandHandler[]): () => void {
|
||||||
|
const disposers = handlers.map((handler) => this.addCommandHandler(handler))
|
||||||
|
return () => {
|
||||||
|
for (const disposer of disposers) {
|
||||||
|
disposer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
keyboardShortcutForCommand(command: KeyboardCommand): PlatformedKeyboardShortcut | undefined {
|
||||||
|
const shortcut = this.commandMap.get(command)
|
||||||
|
if (!shortcut) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
platform: this.platform,
|
||||||
|
...shortcut,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
packages/ui-services/src/Keyboard/KeyboardShortcut.ts
Normal file
20
packages/ui-services/src/Keyboard/KeyboardShortcut.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Platform } from '@standardnotes/snjs'
|
||||||
|
import { KeyboardCommand } from './KeyboardCommands'
|
||||||
|
import { KeyboardKey } from './KeyboardKey'
|
||||||
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
|
|
||||||
|
export type KeyboardShortcut = {
|
||||||
|
command: KeyboardCommand
|
||||||
|
modifiers?: KeyboardModifier[]
|
||||||
|
key?: KeyboardKey | string
|
||||||
|
/**
|
||||||
|
* Alternative to using key, if the key can be affected by alt + shift. For example, if you want alt + shift + n,
|
||||||
|
* use code 'KeyN' instead of key 'n', as the modifiers would turn n into '˜' on Mac.
|
||||||
|
*/
|
||||||
|
code?: string
|
||||||
|
preventDefault?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlatformedKeyboardShortcut = KeyboardShortcut & {
|
||||||
|
platform: Platform
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { KeyboardShortcut } from './KeyboardShortcut'
|
||||||
|
import { modifiersForEvent } from './modifiersForEvent'
|
||||||
|
|
||||||
|
export function eventMatchesKeyAndModifiers(event: KeyboardEvent, shortcut: KeyboardShortcut): boolean {
|
||||||
|
const eventModifiers = modifiersForEvent(event)
|
||||||
|
const shortcutModifiers = shortcut.modifiers ?? []
|
||||||
|
|
||||||
|
if (eventModifiers.length !== shortcutModifiers.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const modifier of shortcutModifiers) {
|
||||||
|
if (!eventModifiers.includes(modifier)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shortcut.key && !shortcut.code) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcut.key) {
|
||||||
|
return shortcut.key.toLowerCase() === event.key.toLowerCase()
|
||||||
|
} else {
|
||||||
|
return shortcut.code === event.code
|
||||||
|
}
|
||||||
|
}
|
||||||
136
packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts
Normal file
136
packages/ui-services/src/Keyboard/getKeyboardShortcuts.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import { Environment, Platform } from '@standardnotes/snjs'
|
||||||
|
import { isMacPlatform } from './platformCheck'
|
||||||
|
import {
|
||||||
|
CREATE_NEW_NOTE_KEYBOARD_COMMAND,
|
||||||
|
TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
|
||||||
|
TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
|
||||||
|
NEXT_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
|
PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
|
SEARCH_KEYBOARD_COMMAND,
|
||||||
|
SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
|
||||||
|
SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||||
|
DELETE_NOTE_KEYBOARD_COMMAND,
|
||||||
|
TAB_COMMAND,
|
||||||
|
ESCAPE_COMMAND,
|
||||||
|
CANCEL_SEARCH_COMMAND,
|
||||||
|
TOGGLE_FOCUS_MODE_COMMAND,
|
||||||
|
CHANGE_EDITOR_COMMAND,
|
||||||
|
FOCUS_TAGS_INPUT_COMMAND,
|
||||||
|
CREATE_NEW_TAG_COMMAND,
|
||||||
|
OPEN_NOTE_HISTORY_COMMAND,
|
||||||
|
CAPTURE_SAVE_COMMAND,
|
||||||
|
STAR_NOTE_COMMAND,
|
||||||
|
PIN_NOTE_COMMAND,
|
||||||
|
} from './KeyboardCommands'
|
||||||
|
import { KeyboardKey } from './KeyboardKey'
|
||||||
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
|
import { KeyboardShortcut } from './KeyboardShortcut'
|
||||||
|
|
||||||
|
export function getKeyboardShortcuts(platform: Platform, _environment: Environment): KeyboardShortcut[] {
|
||||||
|
const isMac = isMacPlatform(platform)
|
||||||
|
|
||||||
|
const primaryModifier = isMac ? KeyboardModifier.Meta : KeyboardModifier.Ctrl
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
command: TOGGLE_LIST_PANE_KEYBOARD_COMMAND,
|
||||||
|
key: 'l',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND,
|
||||||
|
key: 'e',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
|
||||||
|
code: 'KeyN',
|
||||||
|
modifiers: [KeyboardModifier.Alt, KeyboardModifier.Shift],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
|
key: KeyboardKey.Down,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
|
key: KeyboardKey.Up,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: SEARCH_KEYBOARD_COMMAND,
|
||||||
|
code: 'KeyF',
|
||||||
|
modifiers: [KeyboardModifier.Alt, KeyboardModifier.Shift],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: CANCEL_SEARCH_COMMAND,
|
||||||
|
key: KeyboardKey.Escape,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
|
||||||
|
key: 'a',
|
||||||
|
modifiers: [primaryModifier],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||||
|
modifiers: [KeyboardModifier.Alt],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: DELETE_NOTE_KEYBOARD_COMMAND,
|
||||||
|
key: KeyboardKey.Backspace,
|
||||||
|
modifiers: [primaryModifier],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: TAB_COMMAND,
|
||||||
|
key: KeyboardKey.Tab,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: ESCAPE_COMMAND,
|
||||||
|
key: KeyboardKey.Escape,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: TOGGLE_FOCUS_MODE_COMMAND,
|
||||||
|
key: 'f',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: CHANGE_EDITOR_COMMAND,
|
||||||
|
key: '/',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: FOCUS_TAGS_INPUT_COMMAND,
|
||||||
|
code: 'KeyT',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Alt],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: CREATE_NEW_TAG_COMMAND,
|
||||||
|
code: 'KeyN',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Alt],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: OPEN_NOTE_HISTORY_COMMAND,
|
||||||
|
key: 'h',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: CAPTURE_SAVE_COMMAND,
|
||||||
|
key: 's',
|
||||||
|
modifiers: [primaryModifier],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: STAR_NOTE_COMMAND,
|
||||||
|
key: 's',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command: PIN_NOTE_COMMAND,
|
||||||
|
key: 'p',
|
||||||
|
modifiers: [primaryModifier, KeyboardModifier.Shift],
|
||||||
|
preventDefault: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Platform } from '@standardnotes/snjs'
|
||||||
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
|
|
||||||
|
function isMacPlatform(platform: Platform) {
|
||||||
|
return platform === Platform.MacDesktop || platform === Platform.MacWeb
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keyboardCharacterForModifier(modifier: KeyboardModifier, platform: Platform) {
|
||||||
|
const isMac = isMacPlatform(platform)
|
||||||
|
|
||||||
|
if (modifier === KeyboardModifier.Meta) {
|
||||||
|
return isMac ? '⌘' : '⊞'
|
||||||
|
} else if (modifier === KeyboardModifier.Ctrl) {
|
||||||
|
return isMac ? '⌃' : 'Ctrl'
|
||||||
|
} else if (modifier === KeyboardModifier.Alt) {
|
||||||
|
return isMac ? '⌥' : 'Alt'
|
||||||
|
} else if (modifier === KeyboardModifier.Shift) {
|
||||||
|
return isMac ? '⇧' : 'Shift'
|
||||||
|
} else {
|
||||||
|
return KeyboardModifier[modifier]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { isMacPlatform } from '@standardnotes/ui-services'
|
||||||
|
import { keyboardCharacterForModifier } from './keyboardCharacterForModifier'
|
||||||
|
import { PlatformedKeyboardShortcut } from './KeyboardShortcut'
|
||||||
|
|
||||||
|
function stringForCode(code = ''): string {
|
||||||
|
return code.replace('Key', '').replace('Digit', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function keyboardStringForShortcut(shortcut: PlatformedKeyboardShortcut | undefined) {
|
||||||
|
if (!shortcut) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const key = shortcut.key?.toUpperCase() || stringForCode(shortcut.code)
|
||||||
|
|
||||||
|
if (!shortcut.modifiers || shortcut.modifiers.length === 0) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifierCharacters = shortcut.modifiers.map((modifier) =>
|
||||||
|
keyboardCharacterForModifier(modifier, shortcut.platform),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isMacPlatform(shortcut.platform)) {
|
||||||
|
return `${modifierCharacters.join('')}${key}`
|
||||||
|
} else {
|
||||||
|
return `${modifierCharacters.join('+')}+${key}`
|
||||||
|
}
|
||||||
|
}
|
||||||
18
packages/ui-services/src/Keyboard/modifiersForEvent.ts
Normal file
18
packages/ui-services/src/Keyboard/modifiersForEvent.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { KeyboardModifier } from './KeyboardModifier'
|
||||||
|
|
||||||
|
export function modifiersForEvent(event: KeyboardEvent): KeyboardModifier[] {
|
||||||
|
const allModifiers = Object.values(KeyboardModifier)
|
||||||
|
const eventModifiers = allModifiers.filter((modifier) => {
|
||||||
|
// For a modifier like ctrlKey, must check both event.ctrlKey and event.key.
|
||||||
|
// That's because on keyup, event.ctrlKey would be false, but event.key == Control would be true.
|
||||||
|
const matches =
|
||||||
|
((event.ctrlKey || event.key === KeyboardModifier.Ctrl) && modifier === KeyboardModifier.Ctrl) ||
|
||||||
|
((event.metaKey || event.key === KeyboardModifier.Meta) && modifier === KeyboardModifier.Meta) ||
|
||||||
|
((event.altKey || event.key === KeyboardModifier.Alt) && modifier === KeyboardModifier.Alt) ||
|
||||||
|
((event.shiftKey || event.key === KeyboardModifier.Shift) && modifier === KeyboardModifier.Shift)
|
||||||
|
|
||||||
|
return matches
|
||||||
|
})
|
||||||
|
|
||||||
|
return eventModifiers
|
||||||
|
}
|
||||||
9
packages/ui-services/src/Keyboard/platformCheck.ts
Normal file
9
packages/ui-services/src/Keyboard/platformCheck.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Platform } from '@standardnotes/snjs'
|
||||||
|
|
||||||
|
export function isMacPlatform(platform: Platform) {
|
||||||
|
return platform === Platform.MacDesktop || platform === Platform.MacWeb
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindowsPlatform(platform: Platform) {
|
||||||
|
return platform === Platform.WindowsDesktop || platform === Platform.WindowsWeb
|
||||||
|
}
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
export * from './Alert/Functions'
|
export * from './Alert/Functions'
|
||||||
export * from './Alert/WebAlertService'
|
export * from './Alert/WebAlertService'
|
||||||
export * from './Archive/ArchiveManager'
|
export * from './Archive/ArchiveManager'
|
||||||
export * from './IO/IOService'
|
export * from './Keyboard/KeyboardService'
|
||||||
|
export * from './Keyboard/KeyboardShortcut'
|
||||||
|
export * from './Keyboard/KeyboardCommands'
|
||||||
|
export * from './Keyboard/platformCheck'
|
||||||
|
export * from './Keyboard/KeyboardKey'
|
||||||
|
export * from './Keyboard/KeyboardModifier'
|
||||||
|
export * from './Keyboard/keyboardCharacterForModifier'
|
||||||
|
export * from './Keyboard/keyboardStringForShortcut'
|
||||||
export * from './Preferences/PreferenceId'
|
export * from './Preferences/PreferenceId'
|
||||||
export * from './Route/Params/DemoParams'
|
export * from './Route/Params/DemoParams'
|
||||||
export * from './Route/Params/OnboardingParams'
|
export * from './Route/Params/OnboardingParams'
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
--sn-stylekit-accessory-tint-color-1: #941648;
|
--sn-stylekit-accessory-tint-color-1: #941648;
|
||||||
|
|
||||||
--sn-stylekit-shadow-color: var(--background-2);
|
--sn-stylekit-shadow-color: #c4b89f;
|
||||||
--sn-stylekit-background-color: var(--background-1);
|
--sn-stylekit-background-color: var(--background-1);
|
||||||
--sn-stylekit-foreground-color: var(--foreground-color);
|
--sn-stylekit-foreground-color: var(--foreground-color);
|
||||||
--sn-stylekit-border-color: var(--border-color);
|
--sn-stylekit-border-color: var(--border-color);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import { DesktopManager } from './Device/DesktopManager'
|
|||||||
import {
|
import {
|
||||||
ArchiveManager,
|
ArchiveManager,
|
||||||
AutolockService,
|
AutolockService,
|
||||||
IOService,
|
KeyboardService,
|
||||||
RouteService,
|
RouteService,
|
||||||
RouteServiceInterface,
|
RouteServiceInterface,
|
||||||
ThemeManager,
|
ThemeManager,
|
||||||
@@ -85,19 +85,17 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
this.itemControllerGroup = new ItemGroupController(this)
|
this.itemControllerGroup = new ItemGroupController(this)
|
||||||
this.routeService = new RouteService(this, internalEventBus)
|
this.routeService = new RouteService(this, internalEventBus)
|
||||||
|
|
||||||
const viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
this.webServices = {} as WebServices
|
||||||
const archiveService = new ArchiveManager(this)
|
this.webServices.keyboardService = new KeyboardService(platform, this.environment)
|
||||||
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
this.webServices.archiveService = new ArchiveManager(this)
|
||||||
const themeService = new ThemeManager(this, internalEventBus)
|
this.webServices.themeService = new ThemeManager(this, internalEventBus)
|
||||||
|
this.webServices.autolockService = this.isNativeMobileWeb()
|
||||||
this.setWebServices({
|
? undefined
|
||||||
viewControllerManager,
|
: new AutolockService(this, internalEventBus)
|
||||||
archiveService,
|
this.webServices.desktopService = isDesktopDevice(deviceInterface)
|
||||||
desktopService: isDesktopDevice(deviceInterface) ? new DesktopManager(this, deviceInterface) : undefined,
|
? new DesktopManager(this, deviceInterface)
|
||||||
io,
|
: undefined
|
||||||
autolockService: this.isNativeMobileWeb() ? undefined : new AutolockService(this, internalEventBus),
|
this.webServices.viewControllerManager = new ViewControllerManager(this, deviceInterface)
|
||||||
themeService,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.isNativeMobileWeb()) {
|
if (this.isNativeMobileWeb()) {
|
||||||
this.mobileWebReceiver = new MobileWebReceiver(this)
|
this.mobileWebReceiver = new MobileWebReceiver(this)
|
||||||
@@ -152,10 +150,6 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setWebServices(services: WebServices): void {
|
|
||||||
this.webServices = services
|
|
||||||
}
|
|
||||||
|
|
||||||
public addWebEventObserver(observer: WebEventObserver): () => void {
|
public addWebEventObserver(observer: WebEventObserver): () => void {
|
||||||
this.webEventObservers.push(observer)
|
this.webEventObservers.push(observer)
|
||||||
|
|
||||||
@@ -194,6 +188,10 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
return this.webServices.archiveService
|
return this.webServices.archiveService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get paneController() {
|
||||||
|
return this.webServices.viewControllerManager.paneController
|
||||||
|
}
|
||||||
|
|
||||||
public get desktopDevice(): DesktopDeviceInterface | undefined {
|
public get desktopDevice(): DesktopDeviceInterface | undefined {
|
||||||
if (isDesktopDevice(this.deviceInterface)) {
|
if (isDesktopDevice(this.deviceInterface)) {
|
||||||
return this.deviceInterface
|
return this.deviceInterface
|
||||||
@@ -221,8 +219,8 @@ export class WebApplication extends SNApplication implements WebApplicationInter
|
|||||||
return this.webServices.themeService
|
return this.webServices.themeService
|
||||||
}
|
}
|
||||||
|
|
||||||
public get io() {
|
public get keyboardService() {
|
||||||
return this.webServices.io
|
return this.webServices.keyboardService
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForSecurityUpdate() {
|
async checkForSecurityUpdate() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||||
import { DesktopManager } from './Device/DesktopManager'
|
import { DesktopManager } from './Device/DesktopManager'
|
||||||
import { ArchiveManager, AutolockService, IOService, ThemeManager } from '@standardnotes/ui-services'
|
import { ArchiveManager, AutolockService, KeyboardService, ThemeManager } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
export type WebServices = {
|
export type WebServices = {
|
||||||
viewControllerManager: ViewControllerManager
|
viewControllerManager: ViewControllerManager
|
||||||
@@ -8,5 +8,5 @@ export type WebServices = {
|
|||||||
autolockService?: AutolockService
|
autolockService?: AutolockService
|
||||||
archiveService: ArchiveManager
|
archiveService: ArchiveManager
|
||||||
themeService: ThemeManager
|
themeService: ThemeManager
|
||||||
io: IOService
|
keyboardService: KeyboardService
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModa
|
|||||||
import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
|
import DarkModeHandler from '../DarkModeHandler/DarkModeHandler'
|
||||||
import ApplicationProvider from './ApplicationProvider'
|
import ApplicationProvider from './ApplicationProvider'
|
||||||
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
|
import { ErrorBoundary } from '@/Utils/ErrorBoundary'
|
||||||
|
import CommandProvider from './CommandProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -196,85 +197,90 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ApplicationProvider application={application}>
|
<ApplicationProvider application={application}>
|
||||||
<AndroidBackHandlerProvider application={application}>
|
<CommandProvider service={application.keyboardService}>
|
||||||
<DarkModeHandler application={application} />
|
<AndroidBackHandlerProvider application={application}>
|
||||||
<ResponsivePaneProvider paneController={application.getViewControllerManager().paneController}>
|
<DarkModeHandler application={application} />
|
||||||
<PremiumModalProvider application={application} featuresController={viewControllerManager.featuresController}>
|
<ResponsivePaneProvider paneController={application.getViewControllerManager().paneController}>
|
||||||
<div className={platformString + ' main-ui-view sn-component h-full'}>
|
<PremiumModalProvider
|
||||||
<div id="app" className="app app-column-container" ref={appColumnContainerRef}>
|
application={application}
|
||||||
<FileDragNDropProvider
|
featuresController={viewControllerManager.featuresController}
|
||||||
application={application}
|
>
|
||||||
featuresController={viewControllerManager.featuresController}
|
<div className={platformString + ' main-ui-view sn-component h-full'}>
|
||||||
filesController={viewControllerManager.filesController}
|
<div id="app" className="app app-column-container" ref={appColumnContainerRef}>
|
||||||
>
|
<FileDragNDropProvider
|
||||||
<Navigation application={application} />
|
|
||||||
<ContentListView
|
|
||||||
application={application}
|
application={application}
|
||||||
accountMenuController={viewControllerManager.accountMenuController}
|
featuresController={viewControllerManager.featuresController}
|
||||||
filesController={viewControllerManager.filesController}
|
filesController={viewControllerManager.filesController}
|
||||||
itemListController={viewControllerManager.itemListController}
|
>
|
||||||
navigationController={viewControllerManager.navigationController}
|
<Navigation application={application} />
|
||||||
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
<ContentListView
|
||||||
|
application={application}
|
||||||
|
accountMenuController={viewControllerManager.accountMenuController}
|
||||||
|
filesController={viewControllerManager.filesController}
|
||||||
|
itemListController={viewControllerManager.itemListController}
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
noAccountWarningController={viewControllerManager.noAccountWarningController}
|
||||||
|
notesController={viewControllerManager.notesController}
|
||||||
|
selectionController={viewControllerManager.selectionController}
|
||||||
|
searchOptionsController={viewControllerManager.searchOptionsController}
|
||||||
|
linkingController={viewControllerManager.linkingController}
|
||||||
|
/>
|
||||||
|
<ErrorBoundary>
|
||||||
|
<NoteGroupView application={application} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</FileDragNDropProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<>
|
||||||
|
<Footer application={application} applicationGroup={mainApplicationGroup} />
|
||||||
|
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
|
||||||
|
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
|
||||||
|
<RevisionHistoryModal
|
||||||
|
application={application}
|
||||||
|
historyModalController={viewControllerManager.historyModalController}
|
||||||
notesController={viewControllerManager.notesController}
|
notesController={viewControllerManager.notesController}
|
||||||
selectionController={viewControllerManager.selectionController}
|
selectionController={viewControllerManager.selectionController}
|
||||||
searchOptionsController={viewControllerManager.searchOptionsController}
|
subscriptionController={viewControllerManager.subscriptionController}
|
||||||
linkingController={viewControllerManager.linkingController}
|
|
||||||
/>
|
/>
|
||||||
<ErrorBoundary>
|
</>
|
||||||
<NoteGroupView application={application} />
|
|
||||||
</ErrorBoundary>
|
{renderChallenges()}
|
||||||
</FileDragNDropProvider>
|
|
||||||
|
<>
|
||||||
|
<NotesContextMenu
|
||||||
|
application={application}
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
notesController={viewControllerManager.notesController}
|
||||||
|
linkingController={viewControllerManager.linkingController}
|
||||||
|
historyModalController={viewControllerManager.historyModalController}
|
||||||
|
/>
|
||||||
|
<TagContextMenuWrapper
|
||||||
|
navigationController={viewControllerManager.navigationController}
|
||||||
|
featuresController={viewControllerManager.featuresController}
|
||||||
|
/>
|
||||||
|
<FileContextMenuWrapper
|
||||||
|
filesController={viewControllerManager.filesController}
|
||||||
|
selectionController={viewControllerManager.selectionController}
|
||||||
|
/>
|
||||||
|
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||||
|
<ConfirmSignoutContainer
|
||||||
|
applicationGroup={mainApplicationGroup}
|
||||||
|
viewControllerManager={viewControllerManager}
|
||||||
|
application={application}
|
||||||
|
/>
|
||||||
|
<ToastContainer />
|
||||||
|
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
||||||
|
<PermissionsModalWrapper application={application} />
|
||||||
|
<ConfirmDeleteAccountContainer
|
||||||
|
application={application}
|
||||||
|
viewControllerManager={viewControllerManager}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
</div>
|
</div>
|
||||||
|
</PremiumModalProvider>
|
||||||
<>
|
</ResponsivePaneProvider>
|
||||||
<Footer application={application} applicationGroup={mainApplicationGroup} />
|
</AndroidBackHandlerProvider>
|
||||||
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
|
</CommandProvider>
|
||||||
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
|
|
||||||
<RevisionHistoryModal
|
|
||||||
application={application}
|
|
||||||
historyModalController={viewControllerManager.historyModalController}
|
|
||||||
notesController={viewControllerManager.notesController}
|
|
||||||
selectionController={viewControllerManager.selectionController}
|
|
||||||
subscriptionController={viewControllerManager.subscriptionController}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
|
|
||||||
{renderChallenges()}
|
|
||||||
|
|
||||||
<>
|
|
||||||
<NotesContextMenu
|
|
||||||
application={application}
|
|
||||||
navigationController={viewControllerManager.navigationController}
|
|
||||||
notesController={viewControllerManager.notesController}
|
|
||||||
linkingController={viewControllerManager.linkingController}
|
|
||||||
historyModalController={viewControllerManager.historyModalController}
|
|
||||||
/>
|
|
||||||
<TagContextMenuWrapper
|
|
||||||
navigationController={viewControllerManager.navigationController}
|
|
||||||
featuresController={viewControllerManager.featuresController}
|
|
||||||
/>
|
|
||||||
<FileContextMenuWrapper
|
|
||||||
filesController={viewControllerManager.filesController}
|
|
||||||
selectionController={viewControllerManager.selectionController}
|
|
||||||
/>
|
|
||||||
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
|
|
||||||
<ConfirmSignoutContainer
|
|
||||||
applicationGroup={mainApplicationGroup}
|
|
||||||
viewControllerManager={viewControllerManager}
|
|
||||||
application={application}
|
|
||||||
/>
|
|
||||||
<ToastContainer />
|
|
||||||
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
|
|
||||||
<PermissionsModalWrapper application={application} />
|
|
||||||
<ConfirmDeleteAccountContainer
|
|
||||||
application={application}
|
|
||||||
viewControllerManager={viewControllerManager}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
</div>
|
|
||||||
</PremiumModalProvider>
|
|
||||||
</ResponsivePaneProvider>
|
|
||||||
</AndroidBackHandlerProvider>
|
|
||||||
</ApplicationProvider>
|
</ApplicationProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { ReactNode, createContext, useContext, memo } from 'react'
|
||||||
|
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import { KeyboardService } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
|
const CommandServiceContext = createContext<KeyboardService | undefined>(undefined)
|
||||||
|
|
||||||
|
export const useCommandService = () => {
|
||||||
|
const value = useContext(CommandServiceContext)
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
throw new Error('Component must be a child of <CommandServiceProvider />')
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChildrenProps = {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProviderProps = {
|
||||||
|
service: KeyboardService
|
||||||
|
} & ChildrenProps
|
||||||
|
|
||||||
|
const MemoizedChildren = memo(({ children }: ChildrenProps) => <>{children}</>)
|
||||||
|
|
||||||
|
const CommandServiceProvider = ({ service, children }: ProviderProps) => {
|
||||||
|
return (
|
||||||
|
<CommandServiceContext.Provider value={service}>
|
||||||
|
<MemoizedChildren children={children} />
|
||||||
|
</CommandServiceContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(CommandServiceProvider)
|
||||||
@@ -13,7 +13,7 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RoundIconButton = forwardRef(
|
const RoundIconButton = forwardRef(
|
||||||
({ onClick, className, icon: iconType, iconClassName, id }: Props, ref: ForwardedRef<HTMLButtonElement>) => {
|
({ onClick, className, icon: iconType, iconClassName, id, label }: Props, ref: ForwardedRef<HTMLButtonElement>) => {
|
||||||
const click: MouseEventHandler = (e) => {
|
const click: MouseEventHandler = (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
onClick()
|
onClick()
|
||||||
@@ -29,6 +29,8 @@ const RoundIconButton = forwardRef(
|
|||||||
onClick={click}
|
onClick={click}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={id}
|
id={id}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
>
|
>
|
||||||
<Icon type={iconType} className={iconClassName} />
|
<Icon type={iconType} className={iconClassName} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
import { FunctionComponent, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import ChangeEditorMenu from './ChangeEditorMenu'
|
import ChangeEditorMenu from './ChangeEditorMenu'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
import RoundIconButton from '../Button/RoundIconButton'
|
import RoundIconButton from '../Button/RoundIconButton'
|
||||||
import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType'
|
import { getIconAndTintForNoteType } from '@/Utils/Items/Icons/getIconAndTintForNoteType'
|
||||||
|
import { CHANGE_EDITOR_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -40,10 +41,24 @@ const ChangeEditorButton: FunctionComponent<Props> = ({
|
|||||||
setIsClickOutsideDisabled(true)
|
setIsClickOutsideDisabled(true)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return application.keyboardService.addCommandHandler({
|
||||||
|
command: CHANGE_EDITOR_COMMAND,
|
||||||
|
onKeyDown: () => {
|
||||||
|
void toggleMenu()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [application, toggleMenu])
|
||||||
|
|
||||||
|
const shortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(CHANGE_EDITOR_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
<RoundIconButton
|
<RoundIconButton
|
||||||
label="Change note type"
|
label={`Change note type (${shortcut && keyboardStringForShortcut(shortcut)})`}
|
||||||
onClick={toggleMenu}
|
onClick={toggleMenu}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
icon={selectedEditorIcon}
|
icon={selectedEditorIcon}
|
||||||
|
|||||||
@@ -143,10 +143,10 @@ const ComponentView: FunctionComponent<IProps> = ({ application, onLoad, compone
|
|||||||
const removeActionObserver = componentViewer.addActionObserver((action, data) => {
|
const removeActionObserver = componentViewer.addActionObserver((action, data) => {
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case ComponentAction.KeyDown:
|
case ComponentAction.KeyDown:
|
||||||
application.io.handleComponentKeyDown(data.keyboardModifier)
|
application.keyboardService.handleComponentKeyDown(data.keyboardModifier)
|
||||||
break
|
break
|
||||||
case ComponentAction.KeyUp:
|
case ComponentAction.KeyUp:
|
||||||
application.io.handleComponentKeyUp(data.keyboardModifier)
|
application.keyboardService.handleComponentKeyUp(data.keyboardModifier)
|
||||||
break
|
break
|
||||||
case ComponentAction.Click:
|
case ComponentAction.Click:
|
||||||
application.getViewControllerManager().notesController.setContextMenuOpen(false)
|
application.getViewControllerManager().notesController.setContextMenuOpen(false)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
|||||||
import { FilesController } from '@/Controllers/FilesController'
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import { ContentType, SNTag } from '@standardnotes/snjs'
|
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 { WebApplication } from '@/Application/Application'
|
||||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||||
import { FileItem, PrefKey } from '@standardnotes/snjs'
|
import { FileItem, PrefKey } from '@standardnotes/snjs'
|
||||||
@@ -12,7 +20,7 @@ import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
|||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { FilesController } from '@/Controllers/FilesController'
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
import { NoAccountWarningController } from '@/Controllers/NoAccountWarningController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import ContentListHeader from './Header/ContentListHeader'
|
import ContentListHeader from './Header/ContentListHeader'
|
||||||
@@ -110,7 +118,6 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
panelWidth,
|
panelWidth,
|
||||||
renderedItems,
|
renderedItems,
|
||||||
items,
|
items,
|
||||||
searchBarElement,
|
|
||||||
isCurrentNoteTemplate,
|
isCurrentNoteTemplate,
|
||||||
} = itemListController
|
} = itemListController
|
||||||
|
|
||||||
@@ -142,80 +149,69 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
|
}, [isFilesSmartView, filesController, createNewNote, toggleAppPane, application])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const searchBarElement = document.getElementById(ElementIds.SearchBar)
|
||||||
/**
|
/**
|
||||||
* In the browser we're not allowed to override cmd/ctrl + n, so we have to
|
* 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
|
* use Control modifier as well. These rules don't apply to desktop, but
|
||||||
* probably better to be consistent.
|
* probably better to be consistent.
|
||||||
*/
|
*/
|
||||||
const disposeNewNoteKeyObserver = application.io.addKeyObserver({
|
return application.keyboardService.addCommandHandlers([
|
||||||
key: 'n',
|
{
|
||||||
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Ctrl],
|
command: CREATE_NEW_NOTE_KEYBOARD_COMMAND,
|
||||||
onKeyDown: (event) => {
|
onKeyDown: (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
void addNewItem()
|
void addNewItem()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
command: NEXT_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
const disposeNextNoteKeyObserver = application.io.addKeyObserver({
|
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
|
||||||
key: KeyboardKey.Down,
|
onKeyDown: () => {
|
||||||
elements: [document.body, ...(searchBarElement ? [searchBarElement] : [])],
|
if (searchBarElement === document.activeElement) {
|
||||||
onKeyDown: () => {
|
searchBarElement?.blur()
|
||||||
if (searchBarElement === document.activeElement) {
|
}
|
||||||
searchBarElement?.blur()
|
selectNextItem()
|
||||||
}
|
},
|
||||||
selectNextItem()
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
command: PREVIOUS_LIST_ITEM_KEYBOARD_COMMAND,
|
||||||
const disposePreviousNoteKeyObserver = application.io.addKeyObserver({
|
element: document.body,
|
||||||
key: KeyboardKey.Up,
|
onKeyDown: () => {
|
||||||
element: document.body,
|
selectPreviousItem()
|
||||||
onKeyDown: () => {
|
},
|
||||||
selectPreviousItem()
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
command: SEARCH_KEYBOARD_COMMAND,
|
||||||
const disposeSearchKeyObserver = application.io.addKeyObserver({
|
onKeyDown: (event) => {
|
||||||
key: 'f',
|
if (searchBarElement) {
|
||||||
modifiers: [KeyboardModifier.Meta, KeyboardModifier.Shift],
|
event.preventDefault()
|
||||||
onKeyDown: () => {
|
searchBarElement.focus()
|
||||||
if (searchBarElement) {
|
}
|
||||||
searchBarElement.focus()
|
},
|
||||||
}
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
command: CANCEL_SEARCH_COMMAND,
|
||||||
const disposeSelectAllKeyObserver = application.io.addKeyObserver({
|
onKeyDown: () => {
|
||||||
key: 'a',
|
if (searchBarElement) {
|
||||||
modifiers: [KeyboardModifier.Ctrl],
|
searchBarElement.blur()
|
||||||
onKeyDown: (event) => {
|
}
|
||||||
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
|
},
|
||||||
|
|
||||||
if (!isTargetInsideContentList) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
event.preventDefault()
|
|
||||||
selectionController.selectAll()
|
|
||||||
},
|
},
|
||||||
})
|
{
|
||||||
|
command: SELECT_ALL_ITEMS_KEYBOARD_COMMAND,
|
||||||
|
onKeyDown: (event) => {
|
||||||
|
const isTargetInsideContentList = (event.target as HTMLElement).closest(`#${ElementIds.ContentList}`)
|
||||||
|
|
||||||
return () => {
|
if (!isTargetInsideContentList) {
|
||||||
disposeNewNoteKeyObserver()
|
return
|
||||||
disposeNextNoteKeyObserver()
|
}
|
||||||
disposePreviousNoteKeyObserver()
|
|
||||||
disposeSearchKeyObserver()
|
event.preventDefault()
|
||||||
disposeSelectAllKeyObserver()
|
selectionController.selectAll()
|
||||||
}
|
},
|
||||||
}, [
|
},
|
||||||
addNewItem,
|
])
|
||||||
application.io,
|
}, [addNewItem, application.keyboardService, createNewNote, selectNextItem, selectPreviousItem, selectionController])
|
||||||
createNewNote,
|
|
||||||
searchBarElement,
|
|
||||||
selectNextItem,
|
|
||||||
selectPreviousItem,
|
|
||||||
selectionController,
|
|
||||||
])
|
|
||||||
|
|
||||||
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
const panelResizeFinishCallback: ResizeFinishCallback = useCallback(
|
||||||
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
(width, _lastLeft, _isMaxWidth, isCollapsed) => {
|
||||||
@@ -229,11 +225,17 @@ const ContentListView: FunctionComponent<Props> = ({
|
|||||||
[application, selectedAsTag, navigationController],
|
[application, selectedAsTag, navigationController],
|
||||||
)
|
)
|
||||||
|
|
||||||
const addButtonLabel = useMemo(
|
const shortcutForCreate = useMemo(
|
||||||
() => (isFilesSmartView ? 'Upload file' : 'Create a new note in the selected tag'),
|
() => application.keyboardService.keyboardShortcutForCommand(CREATE_NEW_NOTE_KEYBOARD_COMMAND),
|
||||||
[isFilesSmartView],
|
[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 matchesMediumBreakpoint = useMediaQuery(MediaQueryBreakpoints.md)
|
||||||
const matchesXLBreakpoint = useMediaQuery(MediaQueryBreakpoints.xl)
|
const matchesXLBreakpoint = useMediaQuery(MediaQueryBreakpoints.xl)
|
||||||
const isTabletScreenSize = matchesMediumBreakpoint && !matchesXLBreakpoint
|
const isTabletScreenSize = matchesMediumBreakpoint && !matchesXLBreakpoint
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { FilesController } from '@/Controllers/FilesController'
|
import { FilesController } from '@/Controllers/FilesController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
|
import { SortableItem, SNTag, Uuids } from '@standardnotes/snjs'
|
||||||
import { ListableContentItem } from './ListableContentItem'
|
import { ListableContentItem } from './ListableContentItem'
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { PlatformedKeyboardShortcut, keyboardCharacterForModifier, isMacPlatform } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
shortcut: PlatformedKeyboardShortcut
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const KeyboardShortcutIndicator = ({ shortcut, className }: Props) => {
|
||||||
|
const modifiers = shortcut.modifiers || []
|
||||||
|
const primaryKey = (shortcut.key || '').toUpperCase()
|
||||||
|
const addPluses = !isMacPlatform(shortcut.platform)
|
||||||
|
const spacingClass = addPluses ? '' : 'ml-0.5'
|
||||||
|
|
||||||
|
const keys: string[] = []
|
||||||
|
modifiers.forEach((modifier, index) => {
|
||||||
|
keys.push(keyboardCharacterForModifier(modifier, shortcut.platform))
|
||||||
|
|
||||||
|
if (addPluses && (primaryKey || index !== modifiers.length - 1)) {
|
||||||
|
keys.push('+')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (primaryKey) {
|
||||||
|
keys.push(primaryKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`keyboard-shortcut-indicator flex opacity-[0.35] ${className}`}>
|
||||||
|
{keys.map((key, index) => {
|
||||||
|
return (
|
||||||
|
<div className={index !== 0 ? spacingClass : ''} key={index}>
|
||||||
|
{key}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -26,9 +26,16 @@ type Props = {
|
|||||||
focusPreviousItem: () => void
|
focusPreviousItem: () => void
|
||||||
focusedId: string | undefined
|
focusedId: string | undefined
|
||||||
setFocusedId: (id: string) => void
|
setFocusedId: (id: string) => void
|
||||||
|
hoverLabel?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const ItemLinkAutocompleteInput = ({ linkingController, focusPreviousItem, focusedId, setFocusedId }: Props) => {
|
const ItemLinkAutocompleteInput = ({
|
||||||
|
linkingController,
|
||||||
|
focusPreviousItem,
|
||||||
|
focusedId,
|
||||||
|
setFocusedId,
|
||||||
|
hoverLabel,
|
||||||
|
}: Props) => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
const { tags, linkItemToSelectedItem, createAndAddNewTag, isEntitledToNoteLinking, activeItem } = linkingController
|
const { tags, linkItemToSelectedItem, createAndAddNewTag, isEntitledToNoteLinking, activeItem } = linkingController
|
||||||
|
|
||||||
@@ -128,6 +135,8 @@ const ItemLinkAutocompleteInput = ({ linkingController, focusPreviousItem, focus
|
|||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
id={ElementIds.ItemLinkAutocompleteInput}
|
id={ElementIds.ItemLinkAutocompleteInput}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
title={hoverLabel}
|
||||||
|
aria-label={hoverLabel}
|
||||||
/>
|
/>
|
||||||
{areSearchResultsVisible && (
|
{areSearchResultsVisible && (
|
||||||
<DisclosurePanel
|
<DisclosurePanel
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import { observer } from 'mobx-react-lite'
|
|||||||
import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
|
import ItemLinkAutocompleteInput from './ItemLinkAutocompleteInput'
|
||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
import LinkedItemBubble from './LinkedItemBubble'
|
import LinkedItemBubble from './LinkedItemBubble'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
import { ElementIds } from '@/Constants/ElementIDs'
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import { ContentType } from '@standardnotes/snjs'
|
import { ContentType } from '@standardnotes/snjs'
|
||||||
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
import { LinkableItem } from '@/Utils/Items/Search/LinkableItem'
|
||||||
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
import { ItemLink } from '@/Utils/Items/Search/ItemLink'
|
||||||
|
import { FOCUS_TAGS_INPUT_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
||||||
|
import { useCommandService } from '../ApplicationView/CommandProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
linkingController: LinkingController
|
linkingController: LinkingController
|
||||||
@@ -16,6 +18,9 @@ type Props = {
|
|||||||
|
|
||||||
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
||||||
const { toggleAppPane } = useResponsiveAppPane()
|
const { toggleAppPane } = useResponsiveAppPane()
|
||||||
|
|
||||||
|
const commandService = useCommandService()
|
||||||
|
|
||||||
const {
|
const {
|
||||||
allItemLinks,
|
allItemLinks,
|
||||||
notesLinkingToActiveItem,
|
notesLinkingToActiveItem,
|
||||||
@@ -24,6 +29,23 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
activateItem,
|
activateItem,
|
||||||
} = linkingController
|
} = linkingController
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return commandService.addCommandHandler({
|
||||||
|
command: FOCUS_TAGS_INPUT_COMMAND,
|
||||||
|
onKeyDown: () => {
|
||||||
|
const input = document.getElementById(ElementIds.ItemLinkAutocompleteInput)
|
||||||
|
if (input) {
|
||||||
|
input.focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}, [commandService])
|
||||||
|
|
||||||
|
const shortcut = useMemo(
|
||||||
|
() => keyboardStringForShortcut(commandService.keyboardShortcutForCommand(FOCUS_TAGS_INPUT_COMMAND)),
|
||||||
|
[commandService],
|
||||||
|
)
|
||||||
|
|
||||||
const [focusedId, setFocusedId] = useState<string>()
|
const [focusedId, setFocusedId] = useState<string>()
|
||||||
const focusableIds = allItemLinks
|
const focusableIds = allItemLinks
|
||||||
.map((link) => link.id)
|
.map((link) => link.id)
|
||||||
@@ -75,7 +97,7 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'hidden min-w-80 max-w-full flex-wrap items-center gap-2 bg-transparent md:-mr-2 md:flex',
|
'note-view-linking-container hidden min-w-80 max-w-full flex-wrap items-center gap-2 bg-transparent md:-mr-2 md:flex',
|
||||||
allItemLinks.length || notesLinkingToActiveItem.length ? 'mt-1' : 'mt-0.5',
|
allItemLinks.length || notesLinkingToActiveItem.length ? 'mt-1' : 'mt-0.5',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -100,6 +122,7 @@ const LinkedItemBubblesContainer = ({ linkingController }: Props) => {
|
|||||||
linkingController={linkingController}
|
linkingController={linkingController}
|
||||||
focusPreviousItem={focusPreviousItem}
|
focusPreviousItem={focusPreviousItem}
|
||||||
setFocusedId={setFocusedId}
|
setFocusedId={setFocusedId}
|
||||||
|
hoverLabel={`Focus input to add a link (${shortcut})`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
|||||||
import { MenuItemType } from './MenuItemType'
|
import { MenuItemType } from './MenuItemType'
|
||||||
import RadioIndicator from '../Radio/RadioIndicator'
|
import RadioIndicator from '../Radio/RadioIndicator'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
import { PlatformedKeyboardShortcut } from '@standardnotes/ui-services'
|
||||||
|
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||||
|
|
||||||
type MenuItemProps = {
|
type MenuItemProps = {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -20,6 +22,7 @@ type MenuItemProps = {
|
|||||||
iconClassName?: string
|
iconClassName?: string
|
||||||
tabIndex?: number
|
tabIndex?: number
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
shortcut?: PlatformedKeyboardShortcut
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItem = forwardRef(
|
const MenuItem = forwardRef(
|
||||||
@@ -36,6 +39,7 @@ const MenuItem = forwardRef(
|
|||||||
iconClassName,
|
iconClassName,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
disabled,
|
disabled,
|
||||||
|
shortcut,
|
||||||
}: MenuItemProps,
|
}: MenuItemProps,
|
||||||
ref: Ref<HTMLButtonElement>,
|
ref: Ref<HTMLButtonElement>,
|
||||||
) => {
|
) => {
|
||||||
@@ -58,7 +62,10 @@ const MenuItem = forwardRef(
|
|||||||
aria-checked={checked}
|
aria-checked={checked}
|
||||||
>
|
>
|
||||||
<span className="flex flex-grow items-center">{children}</span>
|
<span className="flex flex-grow items-center">{children}</span>
|
||||||
<Switch disabled={disabled} className="px-0" checked={checked} />
|
<div className="flex">
|
||||||
|
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
|
||||||
|
<Switch disabled={disabled} className="px-0" checked={checked} />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
) : (
|
) : (
|
||||||
@@ -78,6 +85,7 @@ const MenuItem = forwardRef(
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
{...(type === MenuItemType.RadioButton ? { 'aria-checked': checked } : {})}
|
||||||
>
|
>
|
||||||
|
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
|
||||||
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
|
{type === MenuItemType.IconButton && icon ? <Icon type={icon} className={iconClassName} /> : null}
|
||||||
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
|
{type === MenuItemType.RadioButton && typeof checked === 'boolean' ? (
|
||||||
<RadioIndicator disabled={disabled} checked={checked} className="flex-shrink-0" />
|
<RadioIndicator disabled={disabled} checked={checked} className="flex-shrink-0" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
|
|||||||
import Button from '../Button/Button'
|
import Button from '../Button/Button'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const IndicatorWithTooltip = ({
|
|||||||
children: ReactNode
|
children: ReactNode
|
||||||
animateIcon?: boolean
|
animateIcon?: boolean
|
||||||
}) => (
|
}) => (
|
||||||
<div className="relative">
|
<div className="note-status-tooltip-container relative">
|
||||||
<button
|
<button
|
||||||
className={classNames('peer flex h-5 w-5 items-center justify-center rounded-full', className)}
|
className={classNames('peer flex h-5 w-5 items-center justify-center rounded-full', className)}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import {
|
import {
|
||||||
ApplicationEvent,
|
ApplicationEvent,
|
||||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
SNComponent,
|
SNComponent,
|
||||||
SNNote,
|
SNNote,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { confirmDialog, KeyboardKey, KeyboardModifier } from '@standardnotes/ui-services'
|
import { confirmDialog, DELETE_NOTE_KEYBOARD_COMMAND, KeyboardKey } from '@standardnotes/ui-services'
|
||||||
import { ChangeEventHandler, createRef, KeyboardEventHandler, RefObject } from 'react'
|
import { ChangeEventHandler, createRef, KeyboardEventHandler, RefObject } from 'react'
|
||||||
import { SuperEditor } from './SuperEditor/SuperEditor'
|
import { SuperEditor } from './SuperEditor/SuperEditor'
|
||||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||||
@@ -764,11 +764,10 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
registerKeyboardShortcuts() {
|
registerKeyboardShortcuts() {
|
||||||
this.removeTrashKeyObserver = this.application.io.addKeyObserver({
|
this.removeTrashKeyObserver = this.application.keyboardService.addCommandHandler({
|
||||||
key: KeyboardKey.Backspace,
|
command: DELETE_NOTE_KEYBOARD_COMMAND,
|
||||||
notTags: ['INPUT', 'TEXTAREA'],
|
notTags: ['INPUT', 'TEXTAREA'],
|
||||||
notElementIds: [SuperEditorContentId],
|
notElementIds: [SuperEditorContentId],
|
||||||
modifiers: [KeyboardModifier.Meta],
|
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
this.deleteNote(false).catch(console.error)
|
this.deleteNote(false).catch(console.error)
|
||||||
},
|
},
|
||||||
@@ -877,7 +876,7 @@ class NoteView extends AbstractComponent<NoteViewProps, State> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{renderHeaderOptions && (
|
{renderHeaderOptions && (
|
||||||
<div className="flex items-center gap-3">
|
<div className="note-view-options-buttons flex items-center gap-3">
|
||||||
<LinkedItemsButton
|
<LinkedItemsButton
|
||||||
filesController={this.viewControllerManager.filesController}
|
filesController={this.viewControllerManager.filesController}
|
||||||
linkingController={this.viewControllerManager.linkingController}
|
linkingController={this.viewControllerManager.linkingController}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
PrefKey,
|
PrefKey,
|
||||||
WebAppEvent,
|
WebAppEvent,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
import { TAB_COMMAND } from '@standardnotes/ui-services'
|
||||||
import { ChangeEventHandler, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
import { ChangeEventHandler, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'
|
||||||
import { NoteViewController } from '../Controller/NoteViewController'
|
import { NoteViewController } from '../Controller/NoteViewController'
|
||||||
|
|
||||||
@@ -181,9 +181,9 @@ export const PlainEditor = forwardRef<PlainEditorInterface, Props>(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tabObserverDisposer.current = application.io.addKeyObserver({
|
tabObserverDisposer.current = application.keyboardService.addCommandHandler({
|
||||||
element: editor,
|
element: editor,
|
||||||
key: KeyboardKey.Tab,
|
command: TAB_COMMAND,
|
||||||
onKeyDown: (event) => {
|
onKeyDown: (event) => {
|
||||||
if (document.hidden || note.current.locked || event.shiftKey) {
|
if (document.hidden || note.current.locked || event.shiftKey) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { observer } from 'mobx-react-lite'
|
|||||||
import NotesOptions from '@/Components/NotesOptions/NotesOptions'
|
import NotesOptions from '@/Components/NotesOptions/NotesOptions'
|
||||||
import { useRef } from 'react'
|
import { useRef } from 'react'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { observer } from 'mobx-react-lite'
|
|||||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
import { IconType } from '@standardnotes/snjs'
|
import { IconType } from '@standardnotes/snjs'
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
import { CHANGE_EDITOR_COMMAND, KeyboardKey } from '@standardnotes/ui-services'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { SNNote } from '@standardnotes/snjs'
|
import { SNNote } from '@standardnotes/snjs'
|
||||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
import { FunctionComponent, useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import ChangeEditorMenu from '@/Components/ChangeEditor/ChangeEditorMenu'
|
import ChangeEditorMenu from '@/Components/ChangeEditor/ChangeEditorMenu'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
|
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||||
|
|
||||||
type ChangeEditorOptionProps = {
|
type ChangeEditorOptionProps = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -27,6 +28,11 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
|
|||||||
setIsOpen((isOpen) => !isOpen)
|
setIsOpen((isOpen) => !isOpen)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const shortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(CHANGE_EDITOR_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={menuContainerRef}>
|
<div ref={menuContainerRef}>
|
||||||
<button
|
<button
|
||||||
@@ -43,7 +49,10 @@ const ChangeEditorOption: FunctionComponent<ChangeEditorOptionProps> = ({
|
|||||||
<Icon type="dashboard" className={`${iconClassName} mr-2 text-neutral`} />
|
<Icon type="dashboard" className={`${iconClassName} mr-2 text-neutral`} />
|
||||||
Change note type
|
Change note type
|
||||||
</div>
|
</div>
|
||||||
<Icon type="chevron-right" className="text-neutral" />
|
<div className="flex">
|
||||||
|
{shortcut && <KeyboardShortcutIndicator className={'mr-2'} shortcut={shortcut} />}
|
||||||
|
<Icon type="chevron-right" className="text-neutral" />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<Popover
|
<Popover
|
||||||
align="start"
|
align="start"
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ import Switch from '@/Components/Switch/Switch'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { useState, useEffect, useMemo, useCallback, FunctionComponent } from 'react'
|
import { useState, useEffect, useMemo, useCallback, FunctionComponent } from 'react'
|
||||||
import { Platform, SNApplication, SNComponent, SNNote } from '@standardnotes/snjs'
|
import { Platform, SNApplication, SNComponent, SNNote } from '@standardnotes/snjs'
|
||||||
import { KeyboardModifier } from '@standardnotes/ui-services'
|
import {
|
||||||
|
OPEN_NOTE_HISTORY_COMMAND,
|
||||||
|
PIN_NOTE_COMMAND,
|
||||||
|
SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||||
|
STAR_NOTE_COMMAND,
|
||||||
|
} from '@standardnotes/ui-services'
|
||||||
import ChangeEditorOption from './ChangeEditorOption'
|
import ChangeEditorOption from './ChangeEditorOption'
|
||||||
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
|
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
|
||||||
import ListedActionsOption from './ListedActionsOption'
|
import ListedActionsOption from './ListedActionsOption'
|
||||||
import AddTagOption from './AddTagOption'
|
import AddTagOption from './AddTagOption'
|
||||||
import { addToast, dismissToast, ToastType } from '@standardnotes/toast'
|
import { addToast, dismissToast, ToastType } from '@standardnotes/toast'
|
||||||
import { NotesOptionsProps } from './NotesOptionsProps'
|
import { NotesOptionsProps } from './NotesOptionsProps'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
import HorizontalSeparator from '../Shared/HorizontalSeparator'
|
||||||
import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
import { formatDateForContextMenu } from '@/Utils/DateUtils'
|
||||||
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
@@ -21,6 +26,7 @@ import { downloadSelectedNotesOnAndroid } from '@/NativeMobileWeb/DownloadSelect
|
|||||||
import ProtectedUnauthorizedLabel from '../ProtectedItemOverlay/ProtectedUnauthorizedLabel'
|
import ProtectedUnauthorizedLabel from '../ProtectedItemOverlay/ProtectedUnauthorizedLabel'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||||||
|
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||||
|
|
||||||
type DeletePermanentlyButtonProps = {
|
type DeletePermanentlyButtonProps = {
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
@@ -216,8 +222,8 @@ const NotesOptions = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const removeAltKeyObserver = application.io.addKeyObserver({
|
const removeAltKeyObserver = application.keyboardService.addCommandHandler({
|
||||||
modifiers: [KeyboardModifier.Alt],
|
command: SHOW_HIDDEN_OPTIONS_KEYBOARD_COMMAND,
|
||||||
onKeyDown: () => {
|
onKeyDown: () => {
|
||||||
setAltKeyDown(true)
|
setAltKeyDown(true)
|
||||||
},
|
},
|
||||||
@@ -274,6 +280,21 @@ const NotesOptions = ({
|
|||||||
historyModalController.openModal(notesController.firstSelectedNote)
|
historyModalController.openModal(notesController.firstSelectedNote)
|
||||||
}, [historyModalController, notesController.firstSelectedNote])
|
}, [historyModalController, notesController.firstSelectedNote])
|
||||||
|
|
||||||
|
const historyShortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(OPEN_NOTE_HISTORY_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
const pinShortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(PIN_NOTE_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
const starShortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(STAR_NOTE_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
const unauthorized = notes.some((note) => !application.isAuthorizedToRenderItem(note))
|
const unauthorized = notes.some((note) => !application.isAuthorizedToRenderItem(note))
|
||||||
if (unauthorized) {
|
if (unauthorized) {
|
||||||
return <ProtectedUnauthorizedLabel />
|
return <ProtectedUnauthorizedLabel />
|
||||||
@@ -295,8 +316,13 @@ const NotesOptions = ({
|
|||||||
{notes.length === 1 && (
|
{notes.length === 1 && (
|
||||||
<>
|
<>
|
||||||
<button className={classNames(defaultClassNames, firstItemClass)} onClick={openRevisionHistoryModal}>
|
<button className={classNames(defaultClassNames, firstItemClass)} onClick={openRevisionHistoryModal}>
|
||||||
<Icon type="history" className={iconClass} />
|
<div className="flex w-full items-center justify-between">
|
||||||
Note history
|
<span className="flex">
|
||||||
|
<Icon type="history" className={iconClass} />
|
||||||
|
Note history
|
||||||
|
</span>
|
||||||
|
{historyShortcut && <KeyboardShortcutIndicator className={''} shortcut={historyShortcut} />}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
<HorizontalSeparator classes="my-2" />
|
<HorizontalSeparator classes="my-2" />
|
||||||
</>
|
</>
|
||||||
@@ -364,8 +390,13 @@ const NotesOptions = ({
|
|||||||
notesController.setStarSelectedNotes(!starred)
|
notesController.setStarSelectedNotes(!starred)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="star" className={iconClass} />
|
<div className="flex w-full items-center justify-between">
|
||||||
{starred ? 'Unstar' : 'Star'}
|
<span className="flex">
|
||||||
|
<Icon type="star" className={iconClass} />
|
||||||
|
{starred ? 'Unstar' : 'Star'}
|
||||||
|
</span>
|
||||||
|
{starShortcut && <KeyboardShortcutIndicator className={''} shortcut={starShortcut} />}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{unpinned && (
|
{unpinned && (
|
||||||
@@ -375,8 +406,13 @@ const NotesOptions = ({
|
|||||||
notesController.setPinSelectedNotes(true)
|
notesController.setPinSelectedNotes(true)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="pin" className={iconClass} />
|
<div className="flex w-full items-center justify-between">
|
||||||
Pin to top
|
<span className="flex">
|
||||||
|
<Icon type="pin" className={iconClass} />
|
||||||
|
Pin to top
|
||||||
|
</span>
|
||||||
|
{pinShortcut && <KeyboardShortcutIndicator className={''} shortcut={pinShortcut} />}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{pinned && (
|
{pinned && (
|
||||||
@@ -386,8 +422,13 @@ const NotesOptions = ({
|
|||||||
notesController.setPinSelectedNotes(false)
|
notesController.setPinSelectedNotes(false)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon type="unpin" className={iconClass} />
|
<div className="flex w-full items-center justify-between">
|
||||||
Unpin
|
<span className="flex">
|
||||||
|
<Icon type="unpin" className={iconClass} />
|
||||||
|
Unpin
|
||||||
|
</span>
|
||||||
|
{pinShortcut && <KeyboardShortcutIndicator className={''} shortcut={pinShortcut} />}
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useCallback, useRef, useState } from 'react'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import NotesOptions from './NotesOptions'
|
import NotesOptions from './NotesOptions'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
import Popover from '../Popover/Popover'
|
import Popover from '../Popover/Popover'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { LinkingController } from '@/Controllers/LinkingController'
|
import { LinkingController } from '@/Controllers/LinkingController'
|
||||||
|
|
||||||
export type NotesOptionsProps = {
|
export type NotesOptionsProps = {
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { VisuallyHidden } from '@reach/visually-hidden'
|
import { VisuallyHidden } from '@reach/visually-hidden'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useCallback } from 'react'
|
import { FunctionComponent, useCallback, useMemo } from 'react'
|
||||||
import Icon from '@/Components/Icon/Icon'
|
import Icon from '@/Components/Icon/Icon'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
import { keyboardStringForShortcut, PIN_NOTE_COMMAND } from '@standardnotes/ui-services'
|
||||||
|
import { useCommandService } from '../ApplicationView/CommandProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string
|
className?: string
|
||||||
@@ -18,18 +21,29 @@ const PinNoteButton: FunctionComponent<Props> = ({ className = '', notesControll
|
|||||||
if (onClickPreprocessing) {
|
if (onClickPreprocessing) {
|
||||||
await onClickPreprocessing()
|
await onClickPreprocessing()
|
||||||
}
|
}
|
||||||
if (!pinned) {
|
notesController.togglePinSelectedNotes()
|
||||||
notesController.setPinSelectedNotes(true)
|
}, [onClickPreprocessing, notesController])
|
||||||
} else {
|
|
||||||
notesController.setPinSelectedNotes(false)
|
const commandService = useCommandService()
|
||||||
}
|
|
||||||
}, [onClickPreprocessing, pinned, notesController])
|
const shortcut = useMemo(
|
||||||
|
() => keyboardStringForShortcut(commandService.keyboardShortcutForCommand(PIN_NOTE_COMMAND)),
|
||||||
|
[commandService],
|
||||||
|
)
|
||||||
|
|
||||||
|
const label = pinned ? `Unpin note (${shortcut})` : `Pin note (${shortcut})`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`sn-icon-button flex h-10 min-w-10 cursor-pointer items-center justify-center rounded-full border border-solid border-border text-neutral hover:bg-contrast focus:bg-contrast
|
className={classNames(
|
||||||
md:h-8 md:min-w-8 ${pinned ? 'toggled' : ''} ${className}`}
|
'sn-icon-button flex h-10 min-w-10 cursor-pointer items-center justify-center',
|
||||||
|
'focus:bg-contras rounded-full border border-solid border-border text-neutral hover:bg-contrast',
|
||||||
|
`md:h-8 md:min-w-8 ${pinned ? 'toggled' : ''}`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
onClick={togglePinned}
|
onClick={togglePinned}
|
||||||
|
title={label}
|
||||||
|
aria-label={label}
|
||||||
>
|
>
|
||||||
<VisuallyHidden>Pin selected notes</VisuallyHidden>
|
<VisuallyHidden>Pin selected notes</VisuallyHidden>
|
||||||
<Icon type="pin" className="block" />
|
<Icon type="pin" className="block" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobi
|
|||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
|
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
|
||||||
|
import { ESCAPE_COMMAND } from '@standardnotes/ui-services'
|
||||||
|
|
||||||
const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
||||||
application,
|
application,
|
||||||
@@ -26,8 +27,8 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
menu.selectPane(viewControllerManager.preferencesController.currentPane)
|
menu.selectPane(viewControllerManager.preferencesController.currentPane)
|
||||||
const removeEscKeyObserver = application.io.addKeyObserver({
|
const removeEscKeyObserver = application.keyboardService.addCommandHandler({
|
||||||
key: 'Escape',
|
command: ESCAPE_COMMAND,
|
||||||
onKeyDown: (event) => {
|
onKeyDown: (event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
closePreferences()
|
closePreferences()
|
||||||
@@ -36,7 +37,7 @@ const PreferencesView: FunctionComponent<PreferencesProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
removeEscKeyObserver()
|
removeEscKeyObserver()
|
||||||
}
|
}
|
||||||
}, [menu, viewControllerManager.preferencesController.currentPane, application.io, closePreferences])
|
}, [menu, viewControllerManager.preferencesController.currentPane, application.keyboardService, closePreferences])
|
||||||
|
|
||||||
useDisableBodyScrollOnMobile()
|
useDisableBodyScrollOnMobile()
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { FunctionComponent, MouseEventHandler, useCallback } from 'react'
|
import { FunctionComponent, MouseEventHandler, useCallback, useMemo } from 'react'
|
||||||
import Switch from '@/Components/Switch/Switch'
|
import Switch from '@/Components/Switch/Switch'
|
||||||
import { isMobileScreen } from '@/Utils'
|
import { isMobileScreen } from '@/Utils'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
import { TOGGLE_FOCUS_MODE_COMMAND } from '@standardnotes/ui-services'
|
||||||
|
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -22,6 +24,11 @@ const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onCl
|
|||||||
[onToggle, isEnabled, onClose],
|
[onToggle, isEnabled, onClose],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const shortcut = useMemo(
|
||||||
|
() => application.keyboardService.keyboardShortcutForCommand(TOGGLE_FOCUS_MODE_COMMAND),
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
const isMobile = application.isNativeMobileWeb() || isMobileScreen()
|
const isMobile = application.isNativeMobileWeb() || isMobileScreen()
|
||||||
|
|
||||||
if (isMobile) {
|
if (isMobile) {
|
||||||
@@ -37,8 +44,11 @@ const FocusModeSwitch: FunctionComponent<Props> = ({ application, onToggle, onCl
|
|||||||
)}
|
)}
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
>
|
>
|
||||||
<div className="flex items-center">Focused Writing</div>
|
<div className="flex items-center">Focus Mode</div>
|
||||||
<Switch className="px-0" checked={isEnabled} />
|
<div className="flex">
|
||||||
|
{shortcut && <KeyboardShortcutIndicator className="mr-2" shortcut={shortcut} />}
|
||||||
|
<Switch className="px-0" checked={isEnabled} />
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,48 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { TOGGLE_LIST_PANE_KEYBOARD_COMMAND, TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND } from '@standardnotes/ui-services'
|
||||||
import { memo, useEffect, useState } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
|
||||||
import MenuItem from '../Menu/MenuItem'
|
import MenuItem from '../Menu/MenuItem'
|
||||||
import { MenuItemType } from '../Menu/MenuItemType'
|
import { MenuItemType } from '../Menu/MenuItemType'
|
||||||
import { PANEL_NAME_NAVIGATION, PANEL_NAME_NOTES } from '@/Constants/Constants'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { useResponsiveAppPane } from '../ResponsivePane/ResponsivePaneProvider'
|
||||||
|
import { useCommandService } from '../ApplicationView/CommandProvider'
|
||||||
|
|
||||||
type Props = {
|
const PanelSettingsSection = () => {
|
||||||
application: WebApplication
|
const { isListPaneCollapsed, isNavigationPaneCollapsed, toggleListPane, toggleNavigationPane } =
|
||||||
}
|
useResponsiveAppPane()
|
||||||
|
|
||||||
const WidthForCollapsedPanel = 5
|
const commandService = useCommandService()
|
||||||
const MinimumNavPanelWidth = PrefDefaults[PrefKey.TagsPanelWidth]
|
|
||||||
const MinimumNotesPanelWidth = PrefDefaults[PrefKey.NotesPanelWidth]
|
|
||||||
|
|
||||||
const PanelSettingsSection = ({ application }: Props) => {
|
const navigationShortcut = useMemo(
|
||||||
const [currentNavPanelWidth, setCurrentNavPanelWidth] = useState(
|
() => commandService.keyboardShortcutForCommand(TOGGLE_NAVIGATION_PANE_KEYBOARD_COMMAND),
|
||||||
application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth),
|
[commandService],
|
||||||
)
|
)
|
||||||
|
|
||||||
const [currentItemsPanelWidth, setCurrentItemsPanelWidth] = useState(
|
const listShortcut = useMemo(
|
||||||
application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth),
|
() => commandService.keyboardShortcutForCommand(TOGGLE_LIST_PANE_KEYBOARD_COMMAND),
|
||||||
|
[commandService],
|
||||||
)
|
)
|
||||||
|
|
||||||
const toggleNavigationPanel = () => {
|
|
||||||
const isCollapsed = currentNavPanelWidth <= WidthForCollapsedPanel
|
|
||||||
if (isCollapsed) {
|
|
||||||
void application.setPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth)
|
|
||||||
} else {
|
|
||||||
void application.setPreference(PrefKey.TagsPanelWidth, WidthForCollapsedPanel)
|
|
||||||
}
|
|
||||||
application.publishPanelDidResizeEvent(PANEL_NAME_NAVIGATION, !isCollapsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleItemsListPanel = () => {
|
|
||||||
const isCollapsed = currentItemsPanelWidth <= WidthForCollapsedPanel
|
|
||||||
if (isCollapsed) {
|
|
||||||
void application.setPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth)
|
|
||||||
} else {
|
|
||||||
void application.setPreference(PrefKey.NotesPanelWidth, WidthForCollapsedPanel)
|
|
||||||
}
|
|
||||||
application.publishPanelDidResizeEvent(PANEL_NAME_NOTES, !isCollapsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const removeObserver = application.addEventObserver(async () => {
|
|
||||||
setCurrentNavPanelWidth(application.getPreference(PrefKey.TagsPanelWidth, MinimumNavPanelWidth))
|
|
||||||
setCurrentItemsPanelWidth(application.getPreference(PrefKey.NotesPanelWidth, MinimumNotesPanelWidth))
|
|
||||||
}, ApplicationEvent.PreferencesChanged)
|
|
||||||
|
|
||||||
return removeObserver
|
|
||||||
}, [application])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="hidden md:block pointer-coarse:md-only:hidden pointer-coarse:lg-only:hidden">
|
<div className="hidden md:block pointer-coarse:md-only:hidden pointer-coarse:lg-only:hidden">
|
||||||
<MenuItem
|
<MenuItem
|
||||||
type={MenuItemType.SwitchButton}
|
type={MenuItemType.SwitchButton}
|
||||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||||
checked={currentNavPanelWidth > WidthForCollapsedPanel}
|
checked={isNavigationPaneCollapsed}
|
||||||
onChange={toggleNavigationPanel}
|
onChange={toggleNavigationPane}
|
||||||
|
shortcut={navigationShortcut}
|
||||||
>
|
>
|
||||||
Show navigation panel
|
Show Tags Panel
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
type={MenuItemType.SwitchButton}
|
type={MenuItemType.SwitchButton}
|
||||||
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
className="py-1 hover:bg-contrast focus:bg-info-backdrop"
|
||||||
checked={currentItemsPanelWidth > WidthForCollapsedPanel}
|
checked={isListPaneCollapsed}
|
||||||
onChange={toggleItemsListPanel}
|
onChange={toggleListPane}
|
||||||
|
shortcut={listShortcut}
|
||||||
>
|
>
|
||||||
Show list panel
|
Show Notes Panel
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
export default memo(PanelSettingsSection)
|
export default observer(PanelSettingsSection)
|
||||||
|
|||||||
@@ -23,27 +23,13 @@ import PanelSettingsSection from './PanelSettingsSection'
|
|||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import { classNames } from '@/Utils/ConcatenateClassNames'
|
import { classNames } from '@/Utils/ConcatenateClassNames'
|
||||||
|
|
||||||
const focusModeAnimationDuration = 1255
|
export const focusModeAnimationDuration = 1255
|
||||||
|
|
||||||
type MenuProps = {
|
type MenuProps = {
|
||||||
quickSettingsMenuController: QuickSettingsController
|
quickSettingsMenuController: QuickSettingsController
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleFocusMode = (enabled: boolean) => {
|
|
||||||
if (enabled) {
|
|
||||||
document.body.classList.add('focus-mode')
|
|
||||||
} else {
|
|
||||||
if (document.body.classList.contains('focus-mode')) {
|
|
||||||
document.body.classList.add('disable-focus-mode')
|
|
||||||
document.body.classList.remove('focus-mode')
|
|
||||||
setTimeout(() => {
|
|
||||||
document.body.classList.remove('disable-focus-mode')
|
|
||||||
}, focusModeAnimationDuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSettingsMenuController }) => {
|
const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSettingsMenuController }) => {
|
||||||
const { closeQuickSettingsMenu, focusModeEnabled, setFocusModeEnabled } = quickSettingsMenuController
|
const { closeQuickSettingsMenu, focusModeEnabled, setFocusModeEnabled } = quickSettingsMenuController
|
||||||
const [themes, setThemes] = useState<ThemeItem[]>([])
|
const [themes, setThemes] = useState<ThemeItem[]>([])
|
||||||
@@ -71,10 +57,6 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
const prefsButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
const defaultThemeButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
toggleFocusMode(focusModeEnabled)
|
|
||||||
}, [focusModeEnabled])
|
|
||||||
|
|
||||||
const reloadThemes = useCallback(() => {
|
const reloadThemes = useCallback(() => {
|
||||||
const themes = application.items
|
const themes = application.items
|
||||||
.getDisplayableComponents()
|
.getDisplayableComponents()
|
||||||
@@ -210,13 +192,14 @@ const QuickSettingsMenu: FunctionComponent<MenuProps> = ({ application, quickSet
|
|||||||
{themes.map((theme) => (
|
{themes.map((theme) => (
|
||||||
<ThemesMenuButton item={theme} application={application} key={theme.component?.uuid ?? theme.identifier} />
|
<ThemesMenuButton item={theme} application={application} key={theme.component?.uuid ?? theme.identifier} />
|
||||||
))}
|
))}
|
||||||
|
<HorizontalSeparator classes="my-2" />
|
||||||
<FocusModeSwitch
|
<FocusModeSwitch
|
||||||
application={application}
|
application={application}
|
||||||
onToggle={setFocusModeEnabled}
|
onToggle={setFocusModeEnabled}
|
||||||
onClose={closeQuickSettingsMenu}
|
onClose={closeQuickSettingsMenu}
|
||||||
isEnabled={focusModeEnabled}
|
isEnabled={focusModeEnabled}
|
||||||
/>
|
/>
|
||||||
<PanelSettingsSection application={application} />
|
<PanelSettingsSection />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,12 @@ import { observer } from 'mobx-react-lite'
|
|||||||
type ResponsivePaneData = {
|
type ResponsivePaneData = {
|
||||||
selectedPane: AppPaneId
|
selectedPane: AppPaneId
|
||||||
toggleAppPane: (paneId: AppPaneId) => void
|
toggleAppPane: (paneId: AppPaneId) => void
|
||||||
isNotesListVisibleOnTablets: boolean
|
|
||||||
toggleNotesListOnTablets: () => void
|
toggleNotesListOnTablets: () => void
|
||||||
|
toggleListPane: () => void
|
||||||
|
toggleNavigationPane: () => void
|
||||||
|
isNotesListVisibleOnTablets: boolean
|
||||||
|
isListPaneCollapsed: boolean
|
||||||
|
isNavigationPaneCollapsed: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResponsivePaneContext = createContext<ResponsivePaneData | undefined>(undefined)
|
const ResponsivePaneContext = createContext<ResponsivePaneData | undefined>(undefined)
|
||||||
@@ -112,8 +116,21 @@ const ResponsivePaneProvider = ({ paneController, children }: ProviderProps) =>
|
|||||||
toggleAppPane,
|
toggleAppPane,
|
||||||
isNotesListVisibleOnTablets,
|
isNotesListVisibleOnTablets,
|
||||||
toggleNotesListOnTablets,
|
toggleNotesListOnTablets,
|
||||||
|
isListPaneCollapsed: paneController.isListPaneCollapsed,
|
||||||
|
isNavigationPaneCollapsed: paneController.isNavigationPaneCollapsed,
|
||||||
|
toggleListPane: paneController.toggleListPane,
|
||||||
|
toggleNavigationPane: paneController.toggleNavigationPane,
|
||||||
}),
|
}),
|
||||||
[currentSelectedPane, isNotesListVisibleOnTablets, toggleAppPane, toggleNotesListOnTablets],
|
[
|
||||||
|
currentSelectedPane,
|
||||||
|
isNotesListVisibleOnTablets,
|
||||||
|
toggleAppPane,
|
||||||
|
toggleNotesListOnTablets,
|
||||||
|
paneController.toggleListPane,
|
||||||
|
paneController.toggleNavigationPane,
|
||||||
|
paneController.isListPaneCollapsed,
|
||||||
|
paneController.isNavigationPaneCollapsed,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import RevisionContentLocked from './RevisionContentLocked'
|
|||||||
import SelectedRevisionContent from './SelectedRevisionContent'
|
import SelectedRevisionContent from './SelectedRevisionContent'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
|
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
|
||||||
import { NoteHistoryController, RevisionContentState } from '@/Controllers/NoteHistory/NoteHistoryController'
|
import { NoteHistoryController, RevisionContentState } from '@/Controllers/NoteHistory/NoteHistoryController'
|
||||||
import Spinner from '@/Components/Spinner/Spinner'
|
import Spinner from '@/Components/Spinner/Spinner'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
import { HistoryModalController } from '@/Controllers/NoteHistory/HistoryModalController'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
import { SelectedItemsController } from '@/Controllers/SelectedItemsController'
|
||||||
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
|
import { SubscriptionController } from '@/Controllers/Subscription/SubscriptionController'
|
||||||
import { SNNote } from '@standardnotes/snjs'
|
import { SNNote } from '@standardnotes/snjs'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ContentType, SNNote } from '@standardnotes/snjs'
|
|||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent, useEffect, useMemo } from 'react'
|
import { FunctionComponent, useEffect, useMemo } from 'react'
|
||||||
import ComponentView from '@/Components/ComponentView/ComponentView'
|
import ComponentView from '@/Components/ComponentView/ComponentView'
|
||||||
import { NotesController } from '@/Controllers/NotesController'
|
import { NotesController } from '@/Controllers/NotesController/NotesController'
|
||||||
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
|
import { NoteHistoryController } from '@/Controllers/NoteHistory/NoteHistoryController'
|
||||||
|
|
||||||
const ABSOLUTE_CENTER_CLASSNAME = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
|
const ABSOLUTE_CENTER_CLASSNAME = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2'
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Icon from '../Icon/Icon'
|
|||||||
import DecoratedInput from '../Input/DecoratedInput'
|
import DecoratedInput from '../Input/DecoratedInput'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
import ClearInputButton from '../ClearInputButton/ClearInputButton'
|
||||||
|
import { ElementIds } from '@/Constants/ElementIDs'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
itemListController: ItemListController
|
itemListController: ItemListController
|
||||||
@@ -48,6 +49,7 @@ const SearchBar = ({ itemListController, searchOptionsController }: Props) => {
|
|||||||
<div className="pt-3 pb-0.5" role="search">
|
<div className="pt-3 pb-0.5" role="search">
|
||||||
<DecoratedInput
|
<DecoratedInput
|
||||||
autocomplete={false}
|
autocomplete={false}
|
||||||
|
id={ElementIds.SearchBar}
|
||||||
className={{
|
className={{
|
||||||
container: 'px-1',
|
container: 'px-1',
|
||||||
input: 'text-base placeholder:text-passive-0 lg:text-sm',
|
input: 'text-base placeholder:text-passive-0 lg:text-sm',
|
||||||
|
|||||||
@@ -263,7 +263,7 @@ export const TagsListItem: FunctionComponent<Props> = observer(
|
|||||||
{isEditing && (
|
{isEditing && (
|
||||||
<input
|
<input
|
||||||
className={
|
className={
|
||||||
'title editing text-mobile-navigation-list-item focus:shadow-none focus:outline-none lg:text-navigation-list-item'
|
'title editing overflow-hidden text-mobile-navigation-list-item focus:shadow-none focus:outline-none lg:text-navigation-list-item'
|
||||||
}
|
}
|
||||||
id={`react-tag-${tag.uuid}-${type}`}
|
id={`react-tag-${tag.uuid}-${type}`}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import IconButton from '@/Components/Button/IconButton'
|
import IconButton from '@/Components/Button/IconButton'
|
||||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||||
|
import { CREATE_NEW_TAG_COMMAND, keyboardStringForShortcut } from '@standardnotes/ui-services'
|
||||||
import { observer } from 'mobx-react-lite'
|
import { observer } from 'mobx-react-lite'
|
||||||
import { FunctionComponent } from 'react'
|
import { FunctionComponent, useMemo } from 'react'
|
||||||
|
import { useCommandService } from '../ApplicationView/CommandProvider'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
tags: NavigationController
|
tags: NavigationController
|
||||||
@@ -10,11 +12,17 @@ type Props = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags }) => {
|
const TagsSectionAddButton: FunctionComponent<Props> = ({ tags }) => {
|
||||||
|
const commandService = useCommandService()
|
||||||
|
|
||||||
|
const shortcut = useMemo(
|
||||||
|
() => keyboardStringForShortcut(commandService.keyboardShortcutForCommand(CREATE_NEW_TAG_COMMAND)),
|
||||||
|
[commandService],
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<IconButton
|
<IconButton
|
||||||
focusable={true}
|
focusable={true}
|
||||||
icon="add"
|
icon="add"
|
||||||
title="Create a new tag"
|
title={`Create a new tag (${shortcut})`}
|
||||||
className="p-0 text-neutral"
|
className="p-0 text-neutral"
|
||||||
onClick={() => tags.createNewTemplate()}
|
onClick={() => tags.createNewTemplate()}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -11,4 +11,5 @@ export const ElementIds = {
|
|||||||
RootId: 'app-group-root',
|
RootId: 'app-group-root',
|
||||||
NoteStatusTooltip: 'note-status-tooltip',
|
NoteStatusTooltip: 'note-status-tooltip',
|
||||||
ItemLinkAutocompleteInput: 'item-link-autocomplete-input',
|
ItemLinkAutocompleteInput: 'item-link-autocomplete-input',
|
||||||
|
SearchBar: 'search-bar',
|
||||||
} as const
|
} as const
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { addToast, dismissToast, ToastType, updateToast } from '@standardnotes/t
|
|||||||
import { action, makeObservable, observable, reaction } from 'mobx'
|
import { action, makeObservable, observable, reaction } from 'mobx'
|
||||||
import { WebApplication } from '../Application/Application'
|
import { WebApplication } from '../Application/Application'
|
||||||
import { AbstractViewController } from './Abstract/AbstractViewController'
|
import { AbstractViewController } from './Abstract/AbstractViewController'
|
||||||
import { NotesController } from './NotesController'
|
import { NotesController } from './NotesController/NotesController'
|
||||||
import { downloadOrShareBlobBasedOnPlatform } from '@/Utils/DownloadOrShareBasedOnPlatform'
|
import { downloadOrShareBlobBasedOnPlatform } from '@/Utils/DownloadOrShareBasedOnPlatform'
|
||||||
|
|
||||||
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]
|
const UnprotectedFileActions = [PopoverFileItemActionType.ToggleFileProtection]
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { InternalEventBus } from '@standardnotes/services'
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { LinkingController } from '../LinkingController'
|
import { LinkingController } from '../LinkingController'
|
||||||
import { NavigationController } from '../Navigation/NavigationController'
|
import { NavigationController } from '../Navigation/NavigationController'
|
||||||
import { NotesController } from '../NotesController'
|
import { NotesController } from '../NotesController/NotesController'
|
||||||
import { SearchOptionsController } from '../SearchOptionsController'
|
import { SearchOptionsController } from '../SearchOptionsController'
|
||||||
import { SelectedItemsController } from '../SelectedItemsController'
|
import { SelectedItemsController } from '../SelectedItemsController'
|
||||||
import { ItemListController } from './ItemListController'
|
import { ItemListController } from './ItemListController'
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { NavigationController } from '../Navigation/NavigationController'
|
|||||||
import { CrossControllerEvent } from '../CrossControllerEvent'
|
import { CrossControllerEvent } from '../CrossControllerEvent'
|
||||||
import { SearchOptionsController } from '../SearchOptionsController'
|
import { SearchOptionsController } from '../SearchOptionsController'
|
||||||
import { SelectedItemsController } from '../SelectedItemsController'
|
import { SelectedItemsController } from '../SelectedItemsController'
|
||||||
import { NotesController } from '../NotesController'
|
import { NotesController } from '../NotesController/NotesController'
|
||||||
import { formatDateAndTimeForNote } from '@/Utils/DateUtils'
|
import { formatDateAndTimeForNote } from '@/Utils/DateUtils'
|
||||||
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
import { PrefDefaults } from '@/Constants/PrefDefaults'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -42,7 +42,6 @@ import { ItemsReloadSource } from './ItemsReloadSource'
|
|||||||
|
|
||||||
const MinNoteCellHeight = 51.0
|
const MinNoteCellHeight = 51.0
|
||||||
const DefaultListNumNotes = 20
|
const DefaultListNumNotes = 20
|
||||||
const ElementIdSearchBar = 'search-bar'
|
|
||||||
const ElementIdScrollContainer = 'notes-scrollable'
|
const ElementIdScrollContainer = 'notes-scrollable'
|
||||||
|
|
||||||
export class ItemListController extends AbstractViewController implements InternalEventHandlerInterface {
|
export class ItemListController extends AbstractViewController implements InternalEventHandlerInterface {
|
||||||
@@ -277,10 +276,6 @@ export class ItemListController extends AbstractViewController implements Intern
|
|||||||
this.showDisplayOptionsMenu = enabled
|
this.showDisplayOptionsMenu = enabled
|
||||||
}
|
}
|
||||||
|
|
||||||
get searchBarElement() {
|
|
||||||
return document.getElementById(ElementIdSearchBar)
|
|
||||||
}
|
|
||||||
|
|
||||||
get isFiltering(): boolean {
|
get isFiltering(): boolean {
|
||||||
return !!this.noteFilterText && this.noteFilterText.length > 0
|
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 { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||||
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
||||||
import {
|
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) => {
|
private findAndSetTag = (uuid: UuidString) => {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { InternalEventBus, SNNote } from '@standardnotes/snjs'
|
import { InternalEventBus, SNNote } from '@standardnotes/snjs'
|
||||||
|
import { OPEN_NOTE_HISTORY_COMMAND } from '@standardnotes/ui-services'
|
||||||
import { action, makeObservable, observable } from 'mobx'
|
import { action, makeObservable, observable } from 'mobx'
|
||||||
import { AbstractViewController } from '../Abstract/AbstractViewController'
|
import { AbstractViewController } from '../Abstract/AbstractViewController'
|
||||||
|
import { NotesControllerInterface } from '../NotesController/NotesControllerInterface'
|
||||||
|
|
||||||
export class HistoryModalController extends AbstractViewController {
|
export class HistoryModalController extends AbstractViewController {
|
||||||
note?: SNNote = undefined
|
note?: SNNote = undefined
|
||||||
@@ -11,13 +13,23 @@ export class HistoryModalController extends AbstractViewController {
|
|||||||
this.note = undefined
|
this.note = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(application: WebApplication, eventBus: InternalEventBus) {
|
constructor(application: WebApplication, eventBus: InternalEventBus, notesController: NotesControllerInterface) {
|
||||||
super(application, eventBus)
|
super(application, eventBus)
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
note: observable,
|
note: observable,
|
||||||
setNote: action,
|
setNote: action,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
this.disposers.push(
|
||||||
|
application.keyboardService.addCommandHandler({
|
||||||
|
command: OPEN_NOTE_HISTORY_COMMAND,
|
||||||
|
onKeyDown: () => {
|
||||||
|
this.openModal(notesController.firstSelectedNote)
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
setNote = (note: SNNote | undefined) => {
|
setNote = (note: SNNote | undefined) => {
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { destroyAllObjectProperties } from '@/Utils'
|
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 { StringEmptyTrash, Strings, StringUtils } from '@/Constants/Strings'
|
||||||
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
||||||
import { SNNote, NoteMutator, ContentType, SNTag, TagMutator, InternalEventBus } from '@standardnotes/snjs'
|
import { SNNote, NoteMutator, ContentType, SNTag, TagMutator, InternalEventBus } from '@standardnotes/snjs'
|
||||||
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
import { makeObservable, observable, action, computed, runInAction } from 'mobx'
|
||||||
import { WebApplication } from '../Application/Application'
|
import { WebApplication } from '../../Application/Application'
|
||||||
import { AbstractViewController } from './Abstract/AbstractViewController'
|
import { AbstractViewController } from '../Abstract/AbstractViewController'
|
||||||
import { SelectedItemsController } from './SelectedItemsController'
|
import { SelectedItemsController } from '../SelectedItemsController'
|
||||||
import { ItemListController } from './ItemList/ItemListController'
|
import { ItemListController } from '../ItemList/ItemListController'
|
||||||
import { NavigationController } from './Navigation/NavigationController'
|
import { NavigationController } from '../Navigation/NavigationController'
|
||||||
|
import { NotesControllerInterface } from './NotesControllerInterface'
|
||||||
|
|
||||||
export class NotesController extends AbstractViewController {
|
export class NotesController extends AbstractViewController implements NotesControllerInterface {
|
||||||
lastSelectedNote: SNNote | undefined
|
lastSelectedNote: SNNote | undefined
|
||||||
contextMenuOpen = false
|
contextMenuOpen = false
|
||||||
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
|
contextMenuPosition: { top?: number; left: number; bottom?: number } = {
|
||||||
@@ -57,6 +58,21 @@ export class NotesController extends AbstractViewController {
|
|||||||
setShowProtectedWarning: action,
|
setShowProtectedWarning: action,
|
||||||
unselectNotes: 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) {
|
public setServicesPostConstruction(itemListController: ItemListController) {
|
||||||
@@ -239,6 +255,28 @@ export class NotesController extends AbstractViewController {
|
|||||||
return false
|
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 {
|
setPinSelectedNotes(pinned: boolean): void {
|
||||||
this.changeSelectedNotes((mutator) => {
|
this.changeSelectedNotes((mutator) => {
|
||||||
mutator.pinned = pinned
|
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 { AppPaneId } from './../Components/ResponsivePane/AppPaneMetadata'
|
||||||
import { isMobileScreen } from '@/Utils'
|
import { isMobileScreen } from '@/Utils'
|
||||||
import { makeObservable, observable, action } from 'mobx'
|
import { makeObservable, observable, action, computed } from 'mobx'
|
||||||
import { Disposer } from '@/Types/Disposer'
|
import { Disposer } from '@/Types/Disposer'
|
||||||
import { MediaQueryBreakpoints } from '@/Hooks/useMediaQuery'
|
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
|
currentPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor
|
||||||
previousPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor
|
previousPane: AppPaneId = isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor
|
||||||
isInMobileView = isMobileScreen()
|
isInMobileView = isMobileScreen()
|
||||||
protected disposers: Disposer[] = []
|
protected disposers: Disposer[] = []
|
||||||
|
|
||||||
constructor() {
|
currentNavPanelWidth = 0
|
||||||
|
currentItemsPanelWidth = 0
|
||||||
|
|
||||||
|
constructor(application: WebApplication, eventBus: InternalEventBus) {
|
||||||
|
super(application, eventBus)
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
currentPane: observable,
|
currentPane: observable,
|
||||||
previousPane: observable,
|
previousPane: observable,
|
||||||
isInMobileView: observable,
|
isInMobileView: observable,
|
||||||
|
currentNavPanelWidth: observable,
|
||||||
|
currentItemsPanelWidth: observable,
|
||||||
|
|
||||||
|
isListPaneCollapsed: computed,
|
||||||
|
isNavigationPaneCollapsed: computed,
|
||||||
|
|
||||||
setCurrentPane: action,
|
setCurrentPane: action,
|
||||||
setPreviousPane: action,
|
setPreviousPane: action,
|
||||||
setIsInMobileView: 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)
|
const mediaQuery = window.matchMedia(MediaQueryBreakpoints.md)
|
||||||
if (mediaQuery?.addEventListener != undefined) {
|
if (mediaQuery?.addEventListener != undefined) {
|
||||||
mediaQuery.addEventListener('change', this.mediumScreenMQHandler)
|
mediaQuery.addEventListener('change', this.mediumScreenMQHandler)
|
||||||
} else {
|
} else {
|
||||||
mediaQuery.addListener(this.mediumScreenMQHandler)
|
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() {
|
deinit() {
|
||||||
|
super.deinit()
|
||||||
const mq = window.matchMedia(MediaQueryBreakpoints.md)
|
const mq = window.matchMedia(MediaQueryBreakpoints.md)
|
||||||
if (mq?.removeEventListener != undefined) {
|
if (mq?.removeEventListener != undefined) {
|
||||||
mq.removeEventListener('change', this.mediumScreenMQHandler)
|
mq.removeEventListener('change', this.mediumScreenMQHandler)
|
||||||
@@ -57,4 +115,38 @@ export class PaneController {
|
|||||||
setIsInMobileView(isInMobileView: boolean) {
|
setIsInMobileView(isInMobileView: boolean) {
|
||||||
this.isInMobileView = isInMobileView
|
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 { 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
|
open = false
|
||||||
shouldAnimateCloseMenu = false
|
shouldAnimateCloseMenu = false
|
||||||
focusModeEnabled = false
|
focusModeEnabled = false
|
||||||
|
|
||||||
constructor() {
|
constructor(application: WebApplication, eventBus: InternalEventBus) {
|
||||||
|
super(application, eventBus)
|
||||||
|
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
open: observable,
|
open: observable,
|
||||||
shouldAnimateCloseMenu: observable,
|
shouldAnimateCloseMenu: observable,
|
||||||
@@ -17,6 +24,17 @@ export class QuickSettingsController {
|
|||||||
toggle: action,
|
toggle: action,
|
||||||
closeQuickSettingsMenu: 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 => {
|
setOpen = (open: boolean): void => {
|
||||||
@@ -29,6 +47,8 @@ export class QuickSettingsController {
|
|||||||
|
|
||||||
setFocusModeEnabled = (enabled: boolean): void => {
|
setFocusModeEnabled = (enabled: boolean): void => {
|
||||||
this.focusModeEnabled = enabled
|
this.focusModeEnabled = enabled
|
||||||
|
|
||||||
|
toggleFocusMode(enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
toggle = (): void => {
|
toggle = (): void => {
|
||||||
|
|||||||
@@ -103,8 +103,8 @@ export class SelectedItemsController
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private get io() {
|
private get keyboardService() {
|
||||||
return this.application.io
|
return this.application.keyboardService
|
||||||
}
|
}
|
||||||
|
|
||||||
get selectedItemsCount(): number {
|
get selectedItemsCount(): number {
|
||||||
@@ -196,7 +196,7 @@ export class SelectedItemsController
|
|||||||
}
|
}
|
||||||
|
|
||||||
cancelMultipleSelection = () => {
|
cancelMultipleSelection = () => {
|
||||||
this.io.cancelAllKeyboardModifiers()
|
this.keyboardService.cancelAllKeyboardModifiers()
|
||||||
|
|
||||||
const firstSelectedItem = this.firstSelectedItem
|
const firstSelectedItem = this.firstSelectedItem
|
||||||
|
|
||||||
@@ -256,9 +256,9 @@ export class SelectedItemsController
|
|||||||
|
|
||||||
log(LoggingDomain.Selection, 'selectItem', item.uuid)
|
log(LoggingDomain.Selection, 'selectItem', item.uuid)
|
||||||
|
|
||||||
const hasMeta = this.io.activeModifiers.has(KeyboardModifier.Meta)
|
const hasMeta = this.keyboardService.activeModifiers.has(KeyboardModifier.Meta)
|
||||||
const hasCtrl = this.io.activeModifiers.has(KeyboardModifier.Ctrl)
|
const hasCtrl = this.keyboardService.activeModifiers.has(KeyboardModifier.Ctrl)
|
||||||
const hasShift = this.io.activeModifiers.has(KeyboardModifier.Shift)
|
const hasShift = this.keyboardService.activeModifiers.has(KeyboardModifier.Shift)
|
||||||
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
const hasMoreThanOneSelected = this.selectedItemsCount > 1
|
||||||
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)
|
const isAuthorizedForAccess = await this.application.protections.authorizeItemAccess(item)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { action, makeObservable, observable } from 'mobx'
|
|||||||
import { ActionsMenuController } from './ActionsMenuController'
|
import { ActionsMenuController } from './ActionsMenuController'
|
||||||
import { FeaturesController } from './FeaturesController'
|
import { FeaturesController } from './FeaturesController'
|
||||||
import { FilesController } from './FilesController'
|
import { FilesController } from './FilesController'
|
||||||
import { NotesController } from './NotesController'
|
import { NotesController } from './NotesController/NotesController'
|
||||||
import { ItemListController } from './ItemList/ItemListController'
|
import { ItemListController } from './ItemList/ItemListController'
|
||||||
import { NoAccountWarningController } from './NoAccountWarningController'
|
import { NoAccountWarningController } from './NoAccountWarningController'
|
||||||
import { PreferencesController } from './PreferencesController'
|
import { PreferencesController } from './PreferencesController'
|
||||||
@@ -60,7 +60,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
readonly itemListController: ItemListController
|
readonly itemListController: ItemListController
|
||||||
readonly preferencesController: PreferencesController
|
readonly preferencesController: PreferencesController
|
||||||
readonly purchaseFlowController: PurchaseFlowController
|
readonly purchaseFlowController: PurchaseFlowController
|
||||||
readonly quickSettingsMenuController = new QuickSettingsController()
|
readonly quickSettingsMenuController: QuickSettingsController
|
||||||
readonly searchOptionsController: SearchOptionsController
|
readonly searchOptionsController: SearchOptionsController
|
||||||
readonly subscriptionController: SubscriptionController
|
readonly subscriptionController: SubscriptionController
|
||||||
readonly syncStatusController = new SyncStatusController()
|
readonly syncStatusController = new SyncStatusController()
|
||||||
@@ -92,7 +92,9 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
|
|
||||||
this.subscriptionManager = application.subscriptions
|
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)
|
this.preferencesController = new PreferencesController(application, this.eventBus)
|
||||||
|
|
||||||
@@ -152,7 +154,7 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
|
|||||||
this.subscriptionController,
|
this.subscriptionController,
|
||||||
)
|
)
|
||||||
|
|
||||||
this.historyModalController = new HistoryModalController(this.application, this.eventBus)
|
this.historyModalController = new HistoryModalController(this.application, this.eventBus, this.notesController)
|
||||||
|
|
||||||
this.toastService = new ToastService()
|
this.toastService = new ToastService()
|
||||||
|
|
||||||
|
|||||||
20
packages/web/src/javascripts/Utils/toggleFocusMode.tsx
Normal file
20
packages/web/src/javascripts/Utils/toggleFocusMode.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { focusModeAnimationDuration } from '../Components/QuickSettingsMenu/QuickSettingsMenu'
|
||||||
|
|
||||||
|
export const FOCUS_MODE_CLASS_NAME = 'focus-mode'
|
||||||
|
export const DISABLING_FOCUS_MODE_CLASS_NAME = 'disable-focus-mode'
|
||||||
|
|
||||||
|
export const toggleFocusMode = (enabled: boolean) => {
|
||||||
|
if (enabled) {
|
||||||
|
document.body.classList.add(FOCUS_MODE_CLASS_NAME)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.body.classList.contains(FOCUS_MODE_CLASS_NAME)) {
|
||||||
|
document.body.classList.add(DISABLING_FOCUS_MODE_CLASS_NAME)
|
||||||
|
document.body.classList.remove(FOCUS_MODE_CLASS_NAME)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.classList.remove(DISABLING_FOCUS_MODE_CLASS_NAME)
|
||||||
|
}, focusModeAnimationDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,8 +15,41 @@
|
|||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor-title-bar {
|
#editor-column {
|
||||||
display: none;
|
padding: 25px 10% 0px 10%;
|
||||||
|
|
||||||
|
@media screen and (min-width: 992px) {
|
||||||
|
padding: 25px 15% 0px 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1200px) {
|
||||||
|
padding: 25px 20% 0px 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
background-color: var(--sn-stylekit-contrast-background-color);
|
||||||
|
|
||||||
|
.content {
|
||||||
|
box-shadow: 0 0 4px 1px var(--sn-stylekit-shadow-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-view-linking-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-title-bar:hover .note-view-linking-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-view-options-buttons,
|
||||||
|
.note-status-tooltip-container {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#editor-title-bar:hover .note-view-options-buttons,
|
||||||
|
#editor-title-bar:hover .note-status-tooltip-container {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#editor-menu-bar {
|
#editor-menu-bar {
|
||||||
@@ -27,20 +60,21 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer-bar {
|
#footer-bar .left,
|
||||||
|
#footer-bar .right {
|
||||||
opacity: 0.08;
|
opacity: 0.08;
|
||||||
transition: opacity 0.25s;
|
transition: opacity 0.25s;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer-bar:hover {
|
#footer-bar *:hover {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#navigation,
|
#navigation,
|
||||||
#items-column {
|
#items-column {
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
animation: fade-out 1.25s forwards;
|
animation: fade-out 0.5s forwards;
|
||||||
transition: width 1.25s;
|
transition: width 0.5s;
|
||||||
transition-delay: 0s;
|
transition-delay: 0s;
|
||||||
width: 0px !important;
|
width: 0px !important;
|
||||||
flex: none !important;
|
flex: none !important;
|
||||||
@@ -61,9 +95,9 @@
|
|||||||
.disable-focus-mode {
|
.disable-focus-mode {
|
||||||
#navigation,
|
#navigation,
|
||||||
#items-column {
|
#items-column {
|
||||||
transition: width 1.25s;
|
transition: width 0.5s;
|
||||||
will-change: opacity;
|
will-change: opacity;
|
||||||
animation: fade-in 1.25s forwards;
|
animation: fade-in 0.5s forwards;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user