feat(web): extract ui-services package
This commit is contained in:
@@ -1,11 +1,5 @@
|
||||
import { WebCrypto } from '@/Application/Crypto'
|
||||
import { WebAlertService } from '@/Services/AlertService'
|
||||
import { ArchiveManager } from '@/Services/ArchiveManager'
|
||||
import { AutolockService } from '@/Services/AutolockService'
|
||||
import { DesktopManager } from '@/Services/DesktopManager'
|
||||
import { IOService } from '@/Services/IOService'
|
||||
import { ThemeManager } from '@/Services/ThemeManager'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
|
||||
import {
|
||||
DeinitSource,
|
||||
@@ -21,11 +15,14 @@ import {
|
||||
SNTag,
|
||||
ContentType,
|
||||
DecryptedItemInterface,
|
||||
WebAppEvent,
|
||||
WebApplicationInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
import { makeObservable, observable } from 'mobx'
|
||||
import { PanelResizedData } from '@/Types/PanelResizedData'
|
||||
import { WebAppEvent } from './WebAppEvent'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import { DesktopManager } from './Device/DesktopManager'
|
||||
import { ArchiveManager, AutolockService, IOService, ThemeManager, WebAlertService } from '@standardnotes/ui-services'
|
||||
|
||||
type WebServices = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
@@ -38,7 +35,7 @@ type WebServices = {
|
||||
|
||||
export type WebEventObserver = (event: WebAppEvent, data?: unknown) => void
|
||||
|
||||
export class WebApplication extends SNApplication {
|
||||
export class WebApplication extends SNApplication implements WebApplicationInterface {
|
||||
private webServices!: WebServices
|
||||
private webEventObservers: WebEventObserver[] = []
|
||||
public itemControllerGroup: ItemGroupController
|
||||
|
||||
@@ -6,14 +6,12 @@ import {
|
||||
InternalEventBus,
|
||||
isDesktopDevice,
|
||||
} from '@standardnotes/snjs'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ArchiveManager, IOService, AutolockService, ThemeManager } from '@standardnotes/ui-services'
|
||||
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { getPlatform, isDesktopApplication } from '@/Utils'
|
||||
import { ArchiveManager } from '@/Services/ArchiveManager'
|
||||
import { DesktopManager } from '@/Services/DesktopManager'
|
||||
import { IOService } from '@/Services/IOService'
|
||||
import { AutolockService } from '@/Services/AutolockService'
|
||||
import { ThemeManager } from '@/Services/ThemeManager'
|
||||
import { WebOrDesktopDevice } from '@/Application/Device/WebOrDesktopDevice'
|
||||
import { DesktopManager } from './Device/DesktopManager'
|
||||
|
||||
const createApplication = (
|
||||
descriptor: ApplicationDescriptor,
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
assert,
|
||||
DesktopClientRequiresWebMethods,
|
||||
DesktopDeviceInterface,
|
||||
WebApplicationInterface,
|
||||
WebAppEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
|
||||
export class DesktopManager
|
||||
extends ApplicationService
|
||||
@@ -27,12 +27,12 @@ export class DesktopManager
|
||||
dataLoaded = false
|
||||
lastSearchedText?: string
|
||||
|
||||
constructor(application: WebApplication, private device: DesktopDeviceInterface) {
|
||||
constructor(application: WebApplicationInterface, private device: DesktopDeviceInterface) {
|
||||
super(application, new InternalEventBus())
|
||||
}
|
||||
|
||||
get webApplication() {
|
||||
return this.application as WebApplication
|
||||
return this.application as WebApplicationInterface
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
@@ -80,7 +80,7 @@ export class DesktopManager
|
||||
.catch(console.error)
|
||||
}
|
||||
|
||||
registerUpdateObserver(callback: (component: SNComponent) => void) {
|
||||
registerUpdateObserver(callback: (component: SNComponent) => void): () => void {
|
||||
const observer = {
|
||||
callback: callback,
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
export enum WebAppEvent {
|
||||
NewUpdateAvailable = 'NewUpdateAvailable',
|
||||
EditorFocused = 'EditorFocused',
|
||||
BeganBackupDownload = 'BeganBackupDownload',
|
||||
EndedBackupDownload = 'EndedBackupDownload',
|
||||
PanelResized = 'PanelResized',
|
||||
WindowDidFocus = 'WindowDidFocus',
|
||||
WindowDidBlur = 'WindowDidBlur',
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { autorun, IReactionDisposer, IReactionPublic } from 'mobx'
|
||||
import { Component } from 'react'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { useCallback, FunctionComponent, KeyboardEventHandler } from 'react'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
import Checkbox from '@/Components/Checkbox/Checkbox'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { STRING_NON_MATCHING_PASSWORDS } from '@/Constants/Strings'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { AccountMenuPane } from './AccountMenuPane'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { AccountMenuPane } from './AccountMenuPane'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { SyncQueueStrategy } from '@standardnotes/snjs'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useState } from 'react'
|
||||
import { AccountMenuPane } from './AccountMenuPane'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { isDev } from '@/Utils'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import React, { FunctionComponent, KeyboardEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { User as UserType } from '@standardnotes/snjs'
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import MenuItem from '@/Components/Menu/MenuItem'
|
||||
import { MenuItemType } from '@/Components/Menu/MenuItemType'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { ApplicationDescriptor } from '@standardnotes/snjs'
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { ApplicationDescriptor, ApplicationGroupEvent, ButtonType } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { getPlatformString, getWindowUrlParams } from '@/Utils'
|
||||
import { ApplicationEvent, Challenge, removeFromArray } from '@standardnotes/snjs'
|
||||
import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@standardnotes/snjs'
|
||||
import { PANEL_NAME_NOTES, PANEL_NAME_NAVIGATION } from '@/Constants/Constants'
|
||||
import { alertDialog } from '@/Services/AlertService'
|
||||
import { alertDialog } from '@standardnotes/ui-services'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
import Navigation from '@/Components/Navigation/Navigation'
|
||||
import NoteGroupView from '@/Components/NoteGroupView/NoteGroupView'
|
||||
import Footer from '@/Components/Footer/Footer'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { formatSizeToReadableString } from '@standardnotes/filepicker'
|
||||
import { FileItem } from '@standardnotes/snjs'
|
||||
import {
|
||||
|
||||
@@ -15,7 +15,7 @@ import Icon from '@/Components/Icon/Icon'
|
||||
import ChallengeModalPrompt from './ChallengePrompt'
|
||||
import LockscreenWorkspaceSwitcher from './LockscreenWorkspaceSwitcher'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { ChallengeModalValues } from './ChallengeModalValues'
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import WorkspaceSwitcherMenu from '@/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
FeatureStatus,
|
||||
SNComponent,
|
||||
dateToLocalizedString,
|
||||
ComponentViewer,
|
||||
ComponentViewerInterface,
|
||||
ComponentViewerEvent,
|
||||
ComponentViewerError,
|
||||
} from '@standardnotes/snjs'
|
||||
@@ -19,8 +19,8 @@ import { openSubscriptionDashboard } from '@/Utils/ManageSubscription'
|
||||
|
||||
interface IProps {
|
||||
application: WebApplication
|
||||
componentViewer: ComponentViewer
|
||||
requestReload?: (viewer: ComponentViewer, force?: boolean) => void
|
||||
componentViewer: ComponentViewerInterface
|
||||
requestReload?: (viewer: ComponentViewerInterface, force?: boolean) => void
|
||||
onLoad?: (component: SNComponent) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FunctionComponent, useEffect, useRef, useState } from 'react'
|
||||
import { AlertDialog, AlertDialogDescription, AlertDialogLabel } from '@reach/alert-dialog'
|
||||
import { STRING_SIGN_OUT_CONFIRMATION } from '@/Constants/Strings'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { UuidString } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, KeyboardEventHandler, UIEventHandler, useCallback } from 'react'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KeyboardKey, KeyboardModifier } from '@/Services/IOService'
|
||||
import { KeyboardKey, KeyboardModifier } from '@standardnotes/ui-services'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { PANEL_NAME_NOTES } from '@/Constants/Constants'
|
||||
import { PrefKey, SystemViewId } from '@standardnotes/snjs'
|
||||
|
||||
@@ -5,8 +5,8 @@ import { getFileIconComponent } from '@/Components/AttachedFilesPopover/getFileI
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import FilePreviewInfoPanel from './FilePreviewInfoPanel'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import FilePreview from './FilePreview'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { FileItem } from '@standardnotes/snjs/dist/@types'
|
||||
|
||||
export type FileViewProps = {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
import { ApplicationGroup } from '@/Application/ApplicationGroup'
|
||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||
import { destroyAllObjectProperties, preventRefreshing } from '@/Utils'
|
||||
import { ApplicationEvent, ApplicationDescriptor } from '@standardnotes/snjs'
|
||||
import { ApplicationEvent, ApplicationDescriptor, WebAppEvent } from '@standardnotes/snjs'
|
||||
import {
|
||||
STRING_NEW_UPDATE_READY,
|
||||
STRING_CONFIRM_APP_QUIT_DURING_UPGRADE,
|
||||
@@ -11,7 +10,7 @@ import {
|
||||
STRING_UPGRADE_ACCOUNT_CONFIRM_TITLE,
|
||||
STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON,
|
||||
} from '@/Constants/Strings'
|
||||
import { alertDialog, confirmDialog } from '@/Services/AlertService'
|
||||
import { alertDialog, confirmDialog } from '@standardnotes/ui-services'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import SyncResolutionMenu from '@/Components/SyncResolutionMenu/SyncResolutionMenu'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
|
||||
|
||||
type MenuProps = {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { SNTag } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import AutocompleteTagInput from '@/Components/TagAutocomplete/AutocompleteTagInput'
|
||||
import NoteTag from './NoteTag'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import {
|
||||
ApplicationEvent,
|
||||
|
||||
@@ -8,16 +8,16 @@ import {
|
||||
SNNote,
|
||||
ComponentArea,
|
||||
PrefKey,
|
||||
ComponentViewer,
|
||||
ComponentViewerInterface,
|
||||
ProposedSecondsToDeferUILevelSessionExpirationDuringActiveInteraction,
|
||||
NoteViewController,
|
||||
PayloadEmitSource,
|
||||
WebAppEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { debounce, isDesktopApplication } from '@/Utils'
|
||||
import { EditorEventSource } from '../../Types/EditorEventSource'
|
||||
import { KeyboardModifier, KeyboardKey } from '@/Services/IOService'
|
||||
import { confirmDialog, KeyboardModifier, KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { STRING_DELETE_PLACEHOLDER_ATTEMPT, STRING_DELETE_LOCKED_ATTEMPT, StringDeleteNote } from '@/Constants/Strings'
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { PureComponent } from '@/Components/Abstract/PureComponent'
|
||||
import ProtectedItemOverlay from '@/Components/ProtectedItemOverlay/ProtectedItemOverlay'
|
||||
import PinNoteButton from '@/Components/PinNoteButton/PinNoteButton'
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
} from './TransactionFunctions'
|
||||
import { reloadFont } from './FontFunctions'
|
||||
import { NoteViewProps } from './NoteViewProps'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
import IndicatorCircle from '../IndicatorCircle/IndicatorCircle'
|
||||
|
||||
const MINIMUM_STATUS_DURATION = 400
|
||||
@@ -53,7 +52,7 @@ function sortAlphabetically(array: SNComponent[]): SNComponent[] {
|
||||
|
||||
type State = {
|
||||
availableStackComponents: SNComponent[]
|
||||
editorComponentViewer?: ComponentViewer
|
||||
editorComponentViewer?: ComponentViewerInterface
|
||||
editorComponentViewerDidAlreadyReload?: boolean
|
||||
editorStateDidLoad: boolean
|
||||
editorTitle: string
|
||||
@@ -68,7 +67,7 @@ type State = {
|
||||
showLockedIcon: boolean
|
||||
showProtectedWarning: boolean
|
||||
spellcheck: boolean
|
||||
stackComponentViewers: ComponentViewer[]
|
||||
stackComponentViewers: ComponentViewerInterface[]
|
||||
syncTakingTooLong: boolean
|
||||
/** Setting to true then false will allow the main content textarea to be destroyed
|
||||
* then re-initialized. Used when reloading spellcheck status. */
|
||||
@@ -404,7 +403,10 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
return viewer
|
||||
}
|
||||
|
||||
public editorComponentViewerRequestsReload = async (viewer: ComponentViewer, force?: boolean): Promise<void> => {
|
||||
public editorComponentViewerRequestsReload = async (
|
||||
viewer: ComponentViewerInterface,
|
||||
force?: boolean,
|
||||
): Promise<void> => {
|
||||
if (this.state.editorComponentViewerDidAlreadyReload && !force) {
|
||||
return
|
||||
}
|
||||
@@ -726,7 +728,7 @@ class NoteView extends PureComponent<NoteViewProps, State> {
|
||||
return !viewerComponentExistsInEnabledComponents
|
||||
})
|
||||
|
||||
const newViewers: ComponentViewer[] = []
|
||||
const newViewers: ComponentViewerInterface[] = []
|
||||
for (const component of needsNewViewer) {
|
||||
newViewers.push(this.application.componentManager.createComponentViewer(component, this.note.uuid))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import Icon from '@/Components/Icon/Icon'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import { NotesController } from '@/Controllers/NotesController'
|
||||
import { NoteTagsController } from '@/Controllers/NoteTagsController'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SNNote } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useCallback, useRef, useState } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import ListedActionsMenu from './ListedActionsMenu'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import Popover from '../Popover/Popover'
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -3,7 +3,7 @@ import Switch from '@/Components/Switch/Switch'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useState, useEffect, useMemo, useCallback, FunctionComponent } from 'react'
|
||||
import { SNApplication, SNComponent, SNNote } from '@standardnotes/snjs'
|
||||
import { KeyboardModifier } from '@/Services/IOService'
|
||||
import { KeyboardModifier } from '@standardnotes/ui-services'
|
||||
import ChangeEditorOption from './ChangeEditorOption'
|
||||
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
|
||||
import ListedActionsOption from './ListedActionsOption'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { AlertDialog, AlertDialogDescription, AlertDialogLabel } from '@reach/alert-dialog'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import Authentication from './Authentication'
|
||||
import Credentials from './Credentials'
|
||||
import Sync from './Sync'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { AccountIllustration } from '@standardnotes/icons'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Text } from '../../PreferencesComponents/Content'
|
||||
|
||||
@@ -6,7 +6,7 @@ import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { dateToLocalizedString } from '@standardnotes/snjs'
|
||||
import { useCallback, useState, FunctionComponent } from 'react'
|
||||
import ChangeEmail from '@/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import PasswordWizard from '@/Components/PasswordWizard/PasswordWizard'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||
|
||||
@@ -2,7 +2,7 @@ import Button from '@/Components/Button/Button'
|
||||
import OtherSessionsSignOutContainer from '@/Components/OtherSessionsSignOut/OtherSessionsSignOut'
|
||||
import HorizontalSeparator from '@/Components/Shared/HorizontalSeparator'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Subtitle, Title, Text } from '../../PreferencesComponents/Content'
|
||||
|
||||
@@ -4,7 +4,7 @@ import SubscriptionInformation from './SubscriptionInformation'
|
||||
import NoSubscription from './NoSubscription'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import PreferencesGroup from '@/Components/Preferences/PreferencesComponents/PreferencesGroup'
|
||||
import PreferencesSegment from '@/Components/Preferences/PreferencesComponents/PreferencesSegment'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { FunctionComponent } from 'react'
|
||||
import PreferencesPane from '@/Components/Preferences/PreferencesComponents/PreferencesPane'
|
||||
import CloudLink from './CloudBackups/CloudBackups'
|
||||
|
||||
@@ -19,7 +19,7 @@ import { WebApplication } from '@/Application/Application'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { isDev, openInNewTab } from '@/Utils'
|
||||
import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { isDesktopApplication } from '@/Utils'
|
||||
import { alertDialog } from '@/Services/AlertService'
|
||||
import { alertDialog } from '@standardnotes/ui-services'
|
||||
import {
|
||||
STRING_IMPORT_SUCCESS,
|
||||
STRING_INVALID_IMPORT_FILE,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
import { BackupFile } from '@standardnotes/snjs'
|
||||
import { ChangeEventHandler, MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
@@ -2,7 +2,7 @@ import { FunctionComponent } from 'react'
|
||||
import OfflineSubscription from '@/Components/Preferences/Panes/General/Advanced/OfflineSubscription'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import PackagesPreferencesSection from '@/Components/Preferences/Panes/General/Advanced/Packages/Section'
|
||||
import { PackageProvider } from '@/Components/Preferences/Panes/General/Advanced/Packages/Provider/PackageProvider'
|
||||
import AccordionItem from '@/Components/Shared/AccordionItem'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Subtitle } from '@/Components/Preferences/PreferencesComponents/Content
|
||||
import DecoratedInput from '@/Components/Input/DecoratedInput'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { STRING_REMOVE_OFFLINE_KEY_CONFIRMATION } from '@/Constants/Strings'
|
||||
import { ButtonType, ClientDisplayableError } from '@standardnotes/snjs'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { PackageProvider } from '@/Components/Preferences/Panes/General/Advanced/Packages/Provider/PackageProvider'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED } from '@/Constants/Strings'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import { Title, Text } from '../../PreferencesComponents/Content'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import EncryptionStatusItem from './EncryptionStatusItem'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Fragment, FunctionComponent, useState } from 'react'
|
||||
import { Text, Title, Subtitle } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
|
||||
@@ -10,11 +10,11 @@ import {
|
||||
} from '@/Constants/Strings'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { preventRefreshing } from '@/Utils'
|
||||
import { alertDialog } from '@/Services/AlertService'
|
||||
import { alertDialog } from '@standardnotes/ui-services'
|
||||
import { FormEvent, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { ApplicationEvent } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { Title, Text } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||
import Button from '@/Components/Button/Button'
|
||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { FunctionComponent } from 'react'
|
||||
import TwoFactorAuthWrapper from './TwoFactorAuth/TwoFactorAuthWrapper'
|
||||
import { MfaProps } from './TwoFactorAuth/MfaProps'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { MfaProps } from './Panes/Security/TwoFactorAuth/MfaProps'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
|
||||
export interface PreferencesProps extends MfaProps {
|
||||
application: WebApplication
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
|
||||
export interface PreferencesViewWrapperProps {
|
||||
viewControllerManager: ViewControllerManager
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import Button from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, FunctionComponent, useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Button from '@/Components/Button/Button'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ChangeEventHandler, FunctionComponent, useEffect, useRef, useState } from 'react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { PurchaseFlowPane } from '@/Controllers/PurchaseFlow/PurchaseFlowPane'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
|
||||
export type PurchaseFlowWrapperProps = {
|
||||
viewControllerManager: ViewControllerManager
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ItemListController } from '@/Controllers/ItemList/ItemListController'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { useState, useCallback, KeyboardEventHandler, useRef } from 'react'
|
||||
import SearchOptions from '@/Components/SearchOptions/SearchOptions'
|
||||
import { SearchOptionsController } from '@/Controllers/SearchOptionsController'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { SNApplication, SessionStrings, UuidString, isNullOrUndefined, RemoteSession } from '@standardnotes/snjs'
|
||||
import { FunctionComponent, useState, useEffect, useRef, useMemo } from 'react'
|
||||
import { Alert } from '@reach/alert'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { useRef, useEffect, useCallback, FocusEventHandler, KeyboardEventHandler } from 'react'
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from 'react'
|
||||
import { Disclosure, DisclosurePanel } from '@reach/disclosure'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import AutocompleteTagResult from './AutocompleteTagResult'
|
||||
import AutocompleteTagHint from './AutocompleteTagHint'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { splitQueryInString } from '@/Utils/StringUtils'
|
||||
import { SNTag } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import SmartViewsListItem from './SmartViewsListItem'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'react'
|
||||
import SmartViewsList from './SmartViewsList'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { SNTag } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback } from 'react'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Icon from '@/Components/Icon/Icon'
|
||||
import { TAG_FOLDERS_FEATURE_NAME } from '@/Constants/Constants'
|
||||
import { usePremiumModal } from '@/Hooks/usePremiumModal'
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { FeaturesController } from '@/Controllers/FeaturesController'
|
||||
import { NavigationController } from '@/Controllers/Navigation/NavigationController'
|
||||
import '@reach/tooltip/styles.css'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import TagsList from '@/Components/Tags/TagsList'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { ApplicationEvent } from '@/__mocks__/@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
PopoverFileItemActionType,
|
||||
} from '@/Components/AttachedFilesPopover/PopoverFileItemAction'
|
||||
import { BYTES_IN_ONE_MEGABYTE } from '@/Constants/Constants'
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { confirmDialog } from '@standardnotes/ui-services'
|
||||
import { Strings, StringUtils } from '@/Constants/Strings'
|
||||
import { concatenateUint8Arrays } from '@/Utils/ConcatenateUint8Arrays'
|
||||
import {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
InternalEventInterface,
|
||||
FileViewController,
|
||||
FileItem,
|
||||
WebAppEvent,
|
||||
} from '@standardnotes/snjs'
|
||||
import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'
|
||||
import { WebApplication } from '../../Application/Application'
|
||||
@@ -28,7 +29,6 @@ import { SearchOptionsController } from '../SearchOptionsController'
|
||||
import { SelectedItemsController } from '../SelectedItemsController'
|
||||
import { NotesController } from '../NotesController'
|
||||
import { NoteTagsController } from '../NoteTagsController'
|
||||
import { WebAppEvent } from '@/Application/WebAppEvent'
|
||||
|
||||
const MinNoteCellHeight = 51.0
|
||||
const DefaultListNumNotes = 20
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { confirmDialog } from '@standardnotes/ui-services'
|
||||
import { STRING_DELETE_TAG } from '@/Constants/Strings'
|
||||
import { MAX_MENU_SIZE_MULTIPLIER, MENU_MARGIN_FROM_APP_BORDER, SMART_TAGS_FEATURE_NAME } from '@/Constants/Constants'
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { storage, StorageKey } from '@/Services/LocalStorage'
|
||||
import { storage, StorageKey } from '@standardnotes/ui-services'
|
||||
import { ApplicationEvent, InternalEventBus } from '@standardnotes/snjs'
|
||||
import { runInAction, makeObservable, observable, action } from 'mobx'
|
||||
import { WebApplication } from '../Application/Application'
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
sortRevisionListIntoGroups,
|
||||
} from '@/Components/RevisionHistoryModal/utils'
|
||||
import { STRING_RESTORE_LOCKED_ATTEMPT } from '@/Constants/Strings'
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { confirmDialog } from '@standardnotes/ui-services'
|
||||
import {
|
||||
Action,
|
||||
ActionVerb,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { confirmDialog } from '@/Services/AlertService'
|
||||
import { confirmDialog } from '@standardnotes/ui-services'
|
||||
import { StringEmptyTrash, Strings, StringUtils } from '@/Constants/Strings'
|
||||
import { MENU_MARGIN_FROM_APP_BORDER } from '@/Constants/Constants'
|
||||
import { SNNote, NoteMutator, ContentType, SNTag, TagMutator, InternalEventBus } from '@standardnotes/snjs'
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import { storage, StorageKey } from '@/Services/LocalStorage'
|
||||
import { storage, StorageKey } from '@standardnotes/ui-services'
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { AccountMenuController } from '@/Controllers/AccountMenu/AccountMenuController'
|
||||
import { destroyAllObjectProperties } from '@/Utils'
|
||||
import { ApplicationEvent, DeinitSource, WebOrDesktopDeviceInterface, InternalEventBus } from '@standardnotes/snjs'
|
||||
import { action, makeObservable, observable } from 'mobx'
|
||||
import { ActionsMenuController } from '../Controllers/ActionsMenuController'
|
||||
import { FeaturesController } from '../Controllers/FeaturesController'
|
||||
import { FilesController } from '../Controllers/FilesController'
|
||||
import { NotesController } from '../Controllers/NotesController'
|
||||
import { ItemListController } from '../Controllers/ItemList/ItemListController'
|
||||
import { NoteTagsController } from '../Controllers/NoteTagsController'
|
||||
import { NoAccountWarningController } from '../Controllers/NoAccountWarningController'
|
||||
import { PreferencesController } from '../Controllers/PreferencesController'
|
||||
import { PurchaseFlowController } from '../Controllers/PurchaseFlow/PurchaseFlowController'
|
||||
import { QuickSettingsController } from '../Controllers/QuickSettingsController'
|
||||
import { SearchOptionsController } from '../Controllers/SearchOptionsController'
|
||||
import { SubscriptionController } from '../Controllers/Subscription/SubscriptionController'
|
||||
import { SyncStatusController } from '../Controllers/SyncStatusController'
|
||||
import { NavigationController } from '../Controllers/Navigation/NavigationController'
|
||||
import { FilePreviewModalController } from '../Controllers/FilePreviewModalController'
|
||||
import { SelectedItemsController } from '../Controllers/SelectedItemsController'
|
||||
import { HistoryModalController } from '../Controllers/NoteHistory/HistoryModalController'
|
||||
import { ActionsMenuController } from './ActionsMenuController'
|
||||
import { FeaturesController } from './FeaturesController'
|
||||
import { FilesController } from './FilesController'
|
||||
import { NotesController } from './NotesController'
|
||||
import { ItemListController } from './ItemList/ItemListController'
|
||||
import { NoteTagsController } from './NoteTagsController'
|
||||
import { NoAccountWarningController } from './NoAccountWarningController'
|
||||
import { PreferencesController } from './PreferencesController'
|
||||
import { PurchaseFlowController } from './PurchaseFlow/PurchaseFlowController'
|
||||
import { QuickSettingsController } from './QuickSettingsController'
|
||||
import { SearchOptionsController } from './SearchOptionsController'
|
||||
import { SubscriptionController } from './Subscription/SubscriptionController'
|
||||
import { SyncStatusController } from './SyncStatusController'
|
||||
import { NavigationController } from './Navigation/NavigationController'
|
||||
import { FilePreviewModalController } from './FilePreviewModalController'
|
||||
import { SelectedItemsController } from './SelectedItemsController'
|
||||
import { HistoryModalController } from './NoteHistory/HistoryModalController'
|
||||
|
||||
export class ViewControllerManager {
|
||||
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures
|
||||
@@ -1,4 +1,4 @@
|
||||
import { KeyboardKey } from '@/Services/IOService'
|
||||
import { KeyboardKey } from '@standardnotes/ui-services'
|
||||
import { FOCUSABLE_BUT_NOT_TABBABLE } from '@/Constants/Constants'
|
||||
import { useCallback, useState, useEffect, RefObject } from 'react'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { ViewControllerManager } from '@/Services/ViewControllerManager'
|
||||
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent, createContext, useCallback, useContext, ReactNode } from 'react'
|
||||
import PremiumFeaturesModal from '@/Components/PremiumFeaturesModal/PremiumFeaturesModal'
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import { AlertService, ButtonType } from '@standardnotes/services'
|
||||
import { sanitizeHtmlString } from '@standardnotes/utils'
|
||||
import { SKAlert } from '@standardnotes/styles'
|
||||
|
||||
/** @returns a promise resolving to true if the user confirmed, false if they canceled */
|
||||
export function confirmDialog({
|
||||
text,
|
||||
title,
|
||||
confirmButtonText = 'Confirm',
|
||||
cancelButtonText = 'Cancel',
|
||||
confirmButtonStyle = 'info',
|
||||
}: {
|
||||
text: string
|
||||
title?: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
confirmButtonStyle?: 'danger' | 'info'
|
||||
}) {
|
||||
return new Promise<boolean>((resolve) => {
|
||||
const alert = new SKAlert({
|
||||
title: title && sanitizeHtmlString(title),
|
||||
text: sanitizeHtmlString(text),
|
||||
buttons: [
|
||||
{
|
||||
text: cancelButtonText,
|
||||
style: 'neutral',
|
||||
action() {
|
||||
resolve(false)
|
||||
},
|
||||
},
|
||||
{
|
||||
text: confirmButtonText,
|
||||
style: confirmButtonStyle,
|
||||
action() {
|
||||
resolve(true)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
alert.present()
|
||||
})
|
||||
}
|
||||
|
||||
export function alertDialog({
|
||||
title,
|
||||
text,
|
||||
closeButtonText = 'OK',
|
||||
}: {
|
||||
title?: string
|
||||
text: string
|
||||
closeButtonText?: string
|
||||
}) {
|
||||
return new Promise<void>((resolve) => {
|
||||
const alert = new SKAlert({
|
||||
title: title && sanitizeHtmlString(title),
|
||||
text: sanitizeHtmlString(text),
|
||||
buttons: [
|
||||
{
|
||||
text: closeButtonText,
|
||||
style: 'neutral',
|
||||
action: resolve,
|
||||
},
|
||||
],
|
||||
})
|
||||
alert.present()
|
||||
})
|
||||
}
|
||||
|
||||
export class WebAlertService extends AlertService {
|
||||
alert(text: string, title?: string, closeButtonText?: string) {
|
||||
return alertDialog({ text, title, closeButtonText })
|
||||
}
|
||||
|
||||
confirm(
|
||||
text: string,
|
||||
title?: string,
|
||||
confirmButtonText?: string,
|
||||
confirmButtonType?: ButtonType,
|
||||
cancelButtonText?: string,
|
||||
): Promise<boolean> {
|
||||
return confirmDialog({
|
||||
text,
|
||||
title,
|
||||
confirmButtonText,
|
||||
cancelButtonText,
|
||||
confirmButtonStyle: confirmButtonType === ButtonType.Danger ? 'danger' : 'info',
|
||||
})
|
||||
}
|
||||
|
||||
blockingDialog(text: string, title?: string) {
|
||||
const alert = new SKAlert({
|
||||
title: title && sanitizeHtmlString(title),
|
||||
text: sanitizeHtmlString(text),
|
||||
buttons: [],
|
||||
})
|
||||
alert.present()
|
||||
return () => {
|
||||
alert.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import { parseFileName } from '@standardnotes/filepicker'
|
||||
import {
|
||||
BackupFile,
|
||||
ContentType,
|
||||
BackupFileDecryptedContextualPayload,
|
||||
NoteContent,
|
||||
EncryptedItemInterface,
|
||||
} from '@standardnotes/snjs'
|
||||
|
||||
function sanitizeFileName(name: string): string {
|
||||
return name.trim().replace(/[.\\/:"?*|<>]/g, '_')
|
||||
}
|
||||
|
||||
function zippableFileName(name: string, suffix = '', format = 'txt'): string {
|
||||
const sanitizedName = sanitizeFileName(name)
|
||||
const nameEnd = suffix + '.' + format
|
||||
const maxFileNameLength = 100
|
||||
return sanitizedName.slice(0, maxFileNameLength - nameEnd.length) + nameEnd
|
||||
}
|
||||
|
||||
type ZippableData = {
|
||||
name: string
|
||||
content: Blob
|
||||
}[]
|
||||
|
||||
type ObjectURL = string
|
||||
|
||||
export class ArchiveManager {
|
||||
private readonly application: WebApplication
|
||||
private textFile?: string
|
||||
|
||||
constructor(application: WebApplication) {
|
||||
this.application = application
|
||||
}
|
||||
|
||||
public async getMimeType(ext: string) {
|
||||
return (await import('@zip.js/zip.js')).getMimeType(ext)
|
||||
}
|
||||
|
||||
public async downloadBackup(encrypted: boolean): Promise<void> {
|
||||
const data = encrypted
|
||||
? await this.application.createEncryptedBackupFile()
|
||||
: await this.application.createDecryptedBackupFile()
|
||||
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
||||
const blobData = new Blob([JSON.stringify(data, null, 2)], {
|
||||
type: 'text/json',
|
||||
})
|
||||
|
||||
if (encrypted) {
|
||||
this.downloadData(blobData, `Standard Notes Encrypted Backup and Import File - ${this.formattedDate()}.txt`)
|
||||
} else {
|
||||
this.downloadZippedDecryptedItems(data).catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
private formattedDate() {
|
||||
const string = `${new Date()}`
|
||||
// Match up to the first parenthesis, i.e do not include '(Central Standard Time)'
|
||||
const matches = string.match(/^(.*?) \(/)
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1]
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
private async downloadZippedDecryptedItems(data: BackupFile) {
|
||||
const zip = await import('@zip.js/zip.js')
|
||||
const zipWriter = new zip.ZipWriter(new zip.BlobWriter('application/zip'))
|
||||
const items = data.items
|
||||
|
||||
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||
type: 'text/plain',
|
||||
})
|
||||
|
||||
const fileName = zippableFileName('Standard Notes Backup and Import File')
|
||||
await zipWriter.add(fileName, new zip.BlobReader(blob))
|
||||
|
||||
let index = 0
|
||||
const nextFile = async () => {
|
||||
const item = items[index]
|
||||
let name, contents
|
||||
|
||||
if (item.content_type === ContentType.Note) {
|
||||
const note = item as BackupFileDecryptedContextualPayload<NoteContent>
|
||||
name = note.content.title
|
||||
contents = note.content.text
|
||||
} else {
|
||||
name = item.content_type
|
||||
contents = JSON.stringify(item.content, null, 2)
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
name = ''
|
||||
}
|
||||
|
||||
const blob = new Blob([contents], { type: 'text/plain' })
|
||||
const fileName =
|
||||
`Items/${sanitizeFileName(item.content_type)}/` + zippableFileName(name, `-${item.uuid.split('-')[0]}`)
|
||||
await zipWriter.add(fileName, new zip.BlobReader(blob))
|
||||
|
||||
index++
|
||||
if (index < items.length) {
|
||||
await nextFile()
|
||||
} else {
|
||||
const finalBlob = await zipWriter.close()
|
||||
this.downloadData(finalBlob, `Standard Notes Backup - ${this.formattedDate()}.zip`)
|
||||
}
|
||||
}
|
||||
|
||||
await nextFile()
|
||||
}
|
||||
|
||||
async zipData(data: ZippableData): Promise<Blob> {
|
||||
const zip = await import('@zip.js/zip.js')
|
||||
const writer = new zip.ZipWriter(new zip.BlobWriter('application/zip'))
|
||||
|
||||
const filenameCounts: Record<string, number> = {}
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
const file = data[i]
|
||||
|
||||
const { name, ext } = parseFileName(file.name)
|
||||
|
||||
filenameCounts[file.name] = filenameCounts[file.name] == undefined ? 0 : filenameCounts[file.name] + 1
|
||||
|
||||
const currentFileNameIndex = filenameCounts[file.name]
|
||||
|
||||
await writer.add(
|
||||
zippableFileName(name, currentFileNameIndex > 0 ? ` - ${currentFileNameIndex}` : '', ext),
|
||||
new zip.BlobReader(file.content),
|
||||
)
|
||||
}
|
||||
|
||||
const zipFileAsBlob = await writer.close()
|
||||
|
||||
return zipFileAsBlob
|
||||
}
|
||||
|
||||
async downloadDataAsZip(data: ZippableData) {
|
||||
const zipFileAsBlob = await this.zipData(data)
|
||||
this.downloadData(zipFileAsBlob, `Standard Notes Export - ${this.formattedDate()}.zip`)
|
||||
}
|
||||
|
||||
private hrefForData(data: Blob) {
|
||||
// If we are replacing a previously generated file we need to
|
||||
// manually revoke the object URL to avoid memory leaks.
|
||||
if (this.textFile) {
|
||||
window.URL.revokeObjectURL(this.textFile)
|
||||
}
|
||||
this.textFile = window.URL.createObjectURL(data)
|
||||
// returns a URL you can use as a href
|
||||
return this.textFile
|
||||
}
|
||||
|
||||
downloadData(data: Blob | ObjectURL, fileName: string): void {
|
||||
const link = document.createElement('a')
|
||||
link.setAttribute('download', fileName)
|
||||
link.href = typeof data === 'string' ? data : this.hrefForData(data)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
link.remove()
|
||||
}
|
||||
|
||||
downloadEncryptedItem(item: EncryptedItemInterface) {
|
||||
this.downloadData(new Blob([JSON.stringify(item.payload.ejected())]), `${item.uuid}.txt`)
|
||||
}
|
||||
|
||||
downloadEncryptedItems(items: EncryptedItemInterface[]) {
|
||||
const data = JSON.stringify(items.map((i) => i.payload.ejected()))
|
||||
this.downloadData(new Blob([data]), 'errored-items.txt')
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
import { ApplicationService } from '@standardnotes/snjs'
|
||||
|
||||
const MILLISECONDS_PER_SECOND = 1000
|
||||
const POLL_INTERVAL = 50
|
||||
|
||||
const LockInterval = {
|
||||
None: 0,
|
||||
Immediate: 1,
|
||||
OneMinute: 60 * MILLISECONDS_PER_SECOND,
|
||||
FiveMinutes: 300 * MILLISECONDS_PER_SECOND,
|
||||
OneHour: 3600 * MILLISECONDS_PER_SECOND,
|
||||
}
|
||||
|
||||
const STORAGE_KEY_AUTOLOCK_INTERVAL = 'AutoLockIntervalKey'
|
||||
|
||||
export class AutolockService extends ApplicationService {
|
||||
private pollInterval: any
|
||||
private lastFocusState?: 'hidden' | 'visible'
|
||||
private lockAfterDate?: Date
|
||||
|
||||
override onAppLaunch() {
|
||||
this.beginPolling()
|
||||
return super.onAppLaunch()
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
this.cancelAutoLockTimer()
|
||||
if (this.pollInterval) {
|
||||
clearInterval(this.pollInterval)
|
||||
}
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
private lockApplication() {
|
||||
if (!this.application.hasPasscode()) {
|
||||
throw Error('Attempting to lock application with no passcode')
|
||||
}
|
||||
this.application.lock().catch(console.error)
|
||||
}
|
||||
|
||||
async setAutoLockInterval(interval: number) {
|
||||
return this.application.setValue(STORAGE_KEY_AUTOLOCK_INTERVAL, interval)
|
||||
}
|
||||
|
||||
async getAutoLockInterval() {
|
||||
const interval = (await this.application.getValue(STORAGE_KEY_AUTOLOCK_INTERVAL)) as number
|
||||
if (interval) {
|
||||
return interval
|
||||
} else {
|
||||
return LockInterval.None
|
||||
}
|
||||
}
|
||||
|
||||
async deleteAutolockPreference() {
|
||||
await this.application.removeValue(STORAGE_KEY_AUTOLOCK_INTERVAL)
|
||||
this.cancelAutoLockTimer()
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify document is in focus every so often as visibilitychange event is
|
||||
* not triggered on a typical window blur event but rather on tab changes.
|
||||
*/
|
||||
beginPolling() {
|
||||
this.pollInterval = setInterval(async () => {
|
||||
const locked = await this.application.isLocked()
|
||||
if (!locked && this.lockAfterDate && new Date() > this.lockAfterDate) {
|
||||
this.lockApplication()
|
||||
}
|
||||
const hasFocus = document.hasFocus()
|
||||
if (hasFocus && this.lastFocusState === 'hidden') {
|
||||
this.documentVisibilityChanged(true).catch(console.error)
|
||||
} else if (!hasFocus && this.lastFocusState === 'visible') {
|
||||
this.documentVisibilityChanged(false).catch(console.error)
|
||||
}
|
||||
/* Save this to compare against next time around */
|
||||
this.lastFocusState = hasFocus ? 'visible' : 'hidden'
|
||||
}, POLL_INTERVAL)
|
||||
}
|
||||
|
||||
getAutoLockIntervalOptions() {
|
||||
return [
|
||||
{
|
||||
value: LockInterval.None,
|
||||
label: 'Off',
|
||||
},
|
||||
{
|
||||
value: LockInterval.Immediate,
|
||||
label: 'Immediately',
|
||||
},
|
||||
{
|
||||
value: LockInterval.OneMinute,
|
||||
label: '1m',
|
||||
},
|
||||
{
|
||||
value: LockInterval.FiveMinutes,
|
||||
label: '5m',
|
||||
},
|
||||
{
|
||||
value: LockInterval.OneHour,
|
||||
label: '1h',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async documentVisibilityChanged(visible: boolean) {
|
||||
if (visible) {
|
||||
this.cancelAutoLockTimer()
|
||||
} else {
|
||||
this.beginAutoLockTimer().catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
async beginAutoLockTimer() {
|
||||
const interval = await this.getAutoLockInterval()
|
||||
if (interval === LockInterval.None) {
|
||||
return
|
||||
}
|
||||
const addToNow = (seconds: number) => {
|
||||
const date = new Date()
|
||||
date.setSeconds(date.getSeconds() + seconds)
|
||||
return date
|
||||
}
|
||||
this.lockAfterDate = addToNow(interval / MILLISECONDS_PER_SECOND)
|
||||
}
|
||||
|
||||
cancelAutoLockTimer() {
|
||||
this.lockAfterDate = undefined
|
||||
}
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
import { removeFromArray } from '@standardnotes/snjs'
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export enum StorageKey {
|
||||
AnonymousUserId = 'AnonymousUserId',
|
||||
ShowBetaWarning = 'ShowBetaWarning',
|
||||
ShowNoAccountWarning = 'ShowNoAccountWarning',
|
||||
FilesNavigationEnabled = 'FilesNavigationEnabled',
|
||||
}
|
||||
|
||||
export type StorageValue = {
|
||||
[StorageKey.AnonymousUserId]: string
|
||||
[StorageKey.ShowBetaWarning]: boolean
|
||||
[StorageKey.ShowNoAccountWarning]: boolean
|
||||
[StorageKey.FilesNavigationEnabled]: boolean
|
||||
}
|
||||
|
||||
export const storage = {
|
||||
get<K extends StorageKey>(key: K): StorageValue[K] | null {
|
||||
const value = localStorage.getItem(key)
|
||||
return value ? JSON.parse(value) : null
|
||||
},
|
||||
set<K extends StorageKey>(key: K, value: StorageValue[K]): void {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
},
|
||||
remove(key: StorageKey): void {
|
||||
localStorage.removeItem(key)
|
||||
},
|
||||
}
|
||||
|
||||
type LocalStorageHookReturnType<Key extends StorageKey> = [StorageValue[Key] | null, (value: StorageValue[Key]) => void]
|
||||
|
||||
export const useLocalStorageItem = <Key extends StorageKey>(key: Key): LocalStorageHookReturnType<Key> => {
|
||||
const [value, setValue] = useState(() => storage.get(key))
|
||||
|
||||
const set = useCallback(
|
||||
(value: StorageValue[Key]) => {
|
||||
storage.set(key, value)
|
||||
setValue(value)
|
||||
},
|
||||
[key],
|
||||
)
|
||||
|
||||
return [value, set]
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
import { WebApplication } from '@/Application/Application'
|
||||
import {
|
||||
StorageValueModes,
|
||||
ApplicationService,
|
||||
SNTheme,
|
||||
removeFromArray,
|
||||
ApplicationEvent,
|
||||
ContentType,
|
||||
UuidString,
|
||||
FeatureStatus,
|
||||
PrefKey,
|
||||
CreateDecryptedLocalStorageContextPayload,
|
||||
InternalEventBus,
|
||||
PayloadEmitSource,
|
||||
LocalStorageDecryptedContextualPayload,
|
||||
} from '@standardnotes/snjs'
|
||||
import { dismissToast, ToastType, addTimedToast } from '@standardnotes/toast'
|
||||
|
||||
const CachedThemesKey = 'cachedThemes'
|
||||
const TimeBeforeApplyingColorScheme = 5
|
||||
const DefaultThemeIdentifier = 'Default'
|
||||
|
||||
export class ThemeManager extends ApplicationService {
|
||||
private activeThemes: UuidString[] = []
|
||||
private unregisterDesktop?: () => void
|
||||
private unregisterStream!: () => void
|
||||
private lastUseDeviceThemeSettings = false
|
||||
|
||||
constructor(application: WebApplication) {
|
||||
super(application, new InternalEventBus())
|
||||
this.colorSchemeEventHandler = this.colorSchemeEventHandler.bind(this)
|
||||
}
|
||||
|
||||
override async onAppStart() {
|
||||
super.onAppStart().catch(console.error)
|
||||
this.registerObservers()
|
||||
}
|
||||
|
||||
override async onAppEvent(event: ApplicationEvent) {
|
||||
super.onAppEvent(event).catch(console.error)
|
||||
|
||||
switch (event) {
|
||||
case ApplicationEvent.SignedOut: {
|
||||
this.deactivateAllThemes()
|
||||
this.activeThemes = []
|
||||
this.application?.removeValue(CachedThemesKey, StorageValueModes.Nonwrapped).catch(console.error)
|
||||
break
|
||||
}
|
||||
case ApplicationEvent.StorageReady: {
|
||||
await this.activateCachedThemes()
|
||||
break
|
||||
}
|
||||
case ApplicationEvent.FeaturesUpdated: {
|
||||
this.handleFeaturesUpdated()
|
||||
break
|
||||
}
|
||||
case ApplicationEvent.Launched: {
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', this.colorSchemeEventHandler)
|
||||
break
|
||||
}
|
||||
case ApplicationEvent.PreferencesChanged: {
|
||||
this.handlePreferencesChangeEvent()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handlePreferencesChangeEvent(): void {
|
||||
const useDeviceThemeSettings = this.application.getPreference(PrefKey.UseSystemColorScheme, false)
|
||||
|
||||
if (useDeviceThemeSettings !== this.lastUseDeviceThemeSettings) {
|
||||
this.lastUseDeviceThemeSettings = useDeviceThemeSettings
|
||||
}
|
||||
|
||||
if (useDeviceThemeSettings) {
|
||||
const prefersDarkColorScheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
this.setThemeAsPerColorScheme(prefersDarkColorScheme.matches)
|
||||
}
|
||||
}
|
||||
|
||||
get webApplication() {
|
||||
return this.application as WebApplication
|
||||
}
|
||||
|
||||
override deinit() {
|
||||
this.activeThemes.length = 0
|
||||
|
||||
this.unregisterDesktop?.()
|
||||
this.unregisterStream()
|
||||
;(this.unregisterDesktop as unknown) = undefined
|
||||
;(this.unregisterStream as unknown) = undefined
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', this.colorSchemeEventHandler)
|
||||
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
private handleFeaturesUpdated(): void {
|
||||
let hasChange = false
|
||||
for (const themeUuid of this.activeThemes) {
|
||||
const theme = this.application.items.findItem(themeUuid) as SNTheme
|
||||
if (!theme) {
|
||||
this.deactivateTheme(themeUuid)
|
||||
hasChange = true
|
||||
} else {
|
||||
const status = this.application.features.getFeatureStatus(theme.identifier)
|
||||
if (status !== FeatureStatus.Entitled) {
|
||||
if (theme.active) {
|
||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||
} else {
|
||||
this.deactivateTheme(theme.uuid)
|
||||
}
|
||||
hasChange = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const activeThemes = (this.application.items.getItems(ContentType.Theme) as SNTheme[]).filter(
|
||||
(theme) => theme.active,
|
||||
)
|
||||
|
||||
for (const theme of activeThemes) {
|
||||
if (!this.activeThemes.includes(theme.uuid)) {
|
||||
this.activateTheme(theme)
|
||||
hasChange = true
|
||||
}
|
||||
}
|
||||
|
||||
if (hasChange) {
|
||||
this.cacheThemeState().catch(console.error)
|
||||
}
|
||||
}
|
||||
|
||||
private colorSchemeEventHandler(event: MediaQueryListEvent) {
|
||||
this.setThemeAsPerColorScheme(event.matches)
|
||||
}
|
||||
|
||||
private showColorSchemeToast(setThemeCallback: () => void) {
|
||||
const [toastId, intervalId] = addTimedToast(
|
||||
{
|
||||
type: ToastType.Regular,
|
||||
message: (timeRemaining) => `Applying system color scheme in ${timeRemaining}s...`,
|
||||
actions: [
|
||||
{
|
||||
label: 'Keep current theme',
|
||||
handler: () => {
|
||||
dismissToast(toastId)
|
||||
clearInterval(intervalId)
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Apply now',
|
||||
handler: () => {
|
||||
dismissToast(toastId)
|
||||
clearInterval(intervalId)
|
||||
setThemeCallback()
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
setThemeCallback,
|
||||
TimeBeforeApplyingColorScheme,
|
||||
)
|
||||
}
|
||||
|
||||
private setThemeAsPerColorScheme(prefersDarkColorScheme: boolean) {
|
||||
const preference = prefersDarkColorScheme ? PrefKey.AutoDarkThemeIdentifier : PrefKey.AutoLightThemeIdentifier
|
||||
|
||||
const themes = this.application.items
|
||||
.getDisplayableComponents()
|
||||
.filter((component) => component.isTheme()) as SNTheme[]
|
||||
|
||||
const activeTheme = themes.find((theme) => theme.active && !theme.isLayerable())
|
||||
const activeThemeIdentifier = activeTheme ? activeTheme.identifier : DefaultThemeIdentifier
|
||||
|
||||
const themeIdentifier = this.application.getPreference(preference, DefaultThemeIdentifier) as string
|
||||
|
||||
const setTheme = () => {
|
||||
if (themeIdentifier === DefaultThemeIdentifier && activeTheme) {
|
||||
this.application.mutator.toggleTheme(activeTheme).catch(console.error)
|
||||
} else {
|
||||
const theme = themes.find((theme) => theme.package_info.identifier === themeIdentifier)
|
||||
if (theme && !theme.active) {
|
||||
this.application.mutator.toggleTheme(theme).catch(console.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isPreferredThemeNotActive = activeThemeIdentifier !== themeIdentifier
|
||||
|
||||
if (isPreferredThemeNotActive) {
|
||||
this.showColorSchemeToast(setTheme)
|
||||
}
|
||||
}
|
||||
|
||||
private async activateCachedThemes() {
|
||||
const cachedThemes = await this.getCachedThemes()
|
||||
for (const theme of cachedThemes) {
|
||||
this.activateTheme(theme, true)
|
||||
}
|
||||
}
|
||||
|
||||
private registerObservers() {
|
||||
this.unregisterDesktop = this.webApplication.getDesktopService()?.registerUpdateObserver((component) => {
|
||||
if (component.active && component.isTheme()) {
|
||||
this.deactivateTheme(component.uuid)
|
||||
setTimeout(() => {
|
||||
this.activateTheme(component as SNTheme)
|
||||
this.cacheThemeState().catch(console.error)
|
||||
}, 10)
|
||||
}
|
||||
})
|
||||
|
||||
this.unregisterStream = this.application.streamItems(ContentType.Theme, ({ changed, inserted, source }) => {
|
||||
const items = changed.concat(inserted)
|
||||
const themes = items as SNTheme[]
|
||||
for (const theme of themes) {
|
||||
if (theme.active) {
|
||||
this.activateTheme(theme)
|
||||
} else {
|
||||
this.deactivateTheme(theme.uuid)
|
||||
}
|
||||
}
|
||||
if (source !== PayloadEmitSource.LocalRetrieved) {
|
||||
this.cacheThemeState().catch(console.error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public deactivateAllThemes() {
|
||||
const activeThemes = this.activeThemes.slice()
|
||||
|
||||
for (const uuid of activeThemes) {
|
||||
this.deactivateTheme(uuid)
|
||||
}
|
||||
}
|
||||
|
||||
private activateTheme(theme: SNTheme, skipEntitlementCheck = false) {
|
||||
if (this.activeThemes.find((uuid) => uuid === theme.uuid)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!skipEntitlementCheck &&
|
||||
this.application.features.getFeatureStatus(theme.identifier) !== FeatureStatus.Entitled
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const url = this.application.componentManager.urlForComponent(theme)
|
||||
if (!url) {
|
||||
return
|
||||
}
|
||||
|
||||
this.activeThemes.push(theme.uuid)
|
||||
|
||||
const link = document.createElement('link')
|
||||
link.href = url
|
||||
link.type = 'text/css'
|
||||
link.rel = 'stylesheet'
|
||||
link.media = 'screen,print'
|
||||
link.id = theme.uuid
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
}
|
||||
|
||||
private deactivateTheme(uuid: string) {
|
||||
const element = document.getElementById(uuid) as HTMLLinkElement
|
||||
if (element) {
|
||||
element.disabled = true
|
||||
element.parentNode?.removeChild(element)
|
||||
}
|
||||
|
||||
removeFromArray(this.activeThemes, uuid)
|
||||
}
|
||||
|
||||
private async cacheThemeState() {
|
||||
const themes = this.application.items.findItems(this.activeThemes) as SNTheme[]
|
||||
|
||||
const mapped = themes.map((theme) => {
|
||||
const payload = theme.payloadRepresentation()
|
||||
return CreateDecryptedLocalStorageContextPayload(payload)
|
||||
})
|
||||
|
||||
return this.application.setValue(CachedThemesKey, mapped, StorageValueModes.Nonwrapped)
|
||||
}
|
||||
|
||||
private async getCachedThemes() {
|
||||
const cachedThemes = (await this.application.getValue(
|
||||
CachedThemesKey,
|
||||
StorageValueModes.Nonwrapped,
|
||||
)) as LocalStorageDecryptedContextualPayload[]
|
||||
|
||||
if (cachedThemes) {
|
||||
const themes = []
|
||||
for (const cachedTheme of cachedThemes) {
|
||||
const payload = this.application.items.createPayloadFromObject(cachedTheme)
|
||||
const theme = this.application.items.createItemFromPayload(payload) as SNTheme
|
||||
themes.push(theme)
|
||||
}
|
||||
return themes
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user