feat(web): extract ui-services package

This commit is contained in:
Karol Sójko
2022-08-04 15:13:30 +02:00
parent c72a407095
commit 7e251262d7
161 changed files with 1105 additions and 824 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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,
}

View File

@@ -1,9 +0,0 @@
export enum WebAppEvent {
NewUpdateAvailable = 'NewUpdateAvailable',
EditorFocused = 'EditorFocused',
BeganBackupDownload = 'BeganBackupDownload',
EndedBackupDownload = 'EndedBackupDownload',
PanelResized = 'PanelResized',
WindowDidFocus = 'WindowDidFocus',
WindowDidBlur = 'WindowDidBlur',
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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,

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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 = {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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
}

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 = {

View File

@@ -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'

View File

@@ -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 = {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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,

View File

@@ -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))
}

View File

@@ -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 = {

View File

@@ -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'

View File

@@ -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 = {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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

View File

@@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
export interface PreferencesViewWrapperProps {
viewControllerManager: ViewControllerManager

View File

@@ -1,4 +1,4 @@
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import Button from '@/Components/Button/Button'
type Props = {

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -1,5 +1,5 @@
import { WebApplication } from '@/Application/Application'
import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
export type PurchaseFlowWrapperProps = {
viewControllerManager: ViewControllerManager

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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

View File

@@ -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 {

View File

@@ -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'

View File

@@ -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,

View File

@@ -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'

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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()
}
}
}

View File

@@ -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')
}
}

View File

@@ -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
}
}

View File

@@ -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)
}
}
}

View File

@@ -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]
}

View File

@@ -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 []
}
}
}