feat: file backups (#1024)
This commit is contained in:
@@ -23,8 +23,20 @@ type Props = {
|
|||||||
onClickPreprocessing?: () => Promise<void>
|
onClickPreprocessing?: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
const isHandlingFileDrag = (event: DragEvent) =>
|
const isHandlingFileDrag = (event: DragEvent, application: WebApplication) => {
|
||||||
event.dataTransfer?.items && Array.from(event.dataTransfer.items).some((item) => item.kind === 'file')
|
const items = event.dataTransfer?.items
|
||||||
|
|
||||||
|
if (!items) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(items).some((item) => {
|
||||||
|
const isFile = item.kind === 'file'
|
||||||
|
const fileName = item.getAsFile()?.name || ''
|
||||||
|
const isBackupMetadataFile = application.files.isFileNameFileBackupMetadataFile(fileName)
|
||||||
|
return isFile && !isBackupMetadataFile
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
||||||
({ application, appState, onClickPreprocessing }) => {
|
({ application, appState, onClickPreprocessing }) => {
|
||||||
@@ -234,16 +246,19 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
const [isDraggingFiles, setIsDraggingFiles] = useState(false)
|
const [isDraggingFiles, setIsDraggingFiles] = useState(false)
|
||||||
const dragCounter = useRef(0)
|
const dragCounter = useRef(0)
|
||||||
|
|
||||||
const handleDrag = (event: DragEvent) => {
|
const handleDrag = useCallback(
|
||||||
if (isHandlingFileDrag(event)) {
|
(event: DragEvent) => {
|
||||||
event.preventDefault()
|
if (isHandlingFileDrag(event, application)) {
|
||||||
event.stopPropagation()
|
event.preventDefault()
|
||||||
}
|
event.stopPropagation()
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
const handleDragIn = useCallback(
|
const handleDragIn = useCallback(
|
||||||
(event: DragEvent) => {
|
(event: DragEvent) => {
|
||||||
if (!isHandlingFileDrag(event)) {
|
if (!isHandlingFileDrag(event, application)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,29 +283,32 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[open, toggleAttachedFilesMenu],
|
[open, toggleAttachedFilesMenu, application],
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleDragOut = (event: DragEvent) => {
|
const handleDragOut = useCallback(
|
||||||
if (!isHandlingFileDrag(event)) {
|
(event: DragEvent) => {
|
||||||
return
|
if (!isHandlingFileDrag(event, application)) {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
dragCounter.current = dragCounter.current - 1
|
dragCounter.current = dragCounter.current - 1
|
||||||
|
|
||||||
if (dragCounter.current > 0) {
|
if (dragCounter.current > 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsDraggingFiles(false)
|
setIsDraggingFiles(false)
|
||||||
}
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
const handleDrop = useCallback(
|
const handleDrop = useCallback(
|
||||||
(event: DragEvent) => {
|
(event: DragEvent) => {
|
||||||
if (!isHandlingFileDrag(event)) {
|
if (!isHandlingFileDrag(event, application)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +348,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
dragCounter.current = 0
|
dragCounter.current = 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[appState.files, appState.features.hasFiles, attachFileToNote, currentTab],
|
[appState.files, appState.features.hasFiles, attachFileToNote, currentTab, application],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -345,7 +363,7 @@ export const AttachedFilesButton: FunctionComponent<Props> = observer(
|
|||||||
window.removeEventListener('dragover', handleDrag)
|
window.removeEventListener('dragover', handleDrag)
|
||||||
window.removeEventListener('drop', handleDrop)
|
window.removeEventListener('drop', handleDrop)
|
||||||
}
|
}
|
||||||
}, [handleDragIn, handleDrop])
|
}, [handleDragIn, handleDrop, handleDrag, handleDragOut])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<div ref={containerRef}>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export const ChallengeModal: FunctionComponent<Props> = ({
|
|||||||
const valuesToProcess: ChallengeValue[] = []
|
const valuesToProcess: ChallengeValue[] = []
|
||||||
for (const inputValue of Object.values(validatedValues)) {
|
for (const inputValue of Object.values(validatedValues)) {
|
||||||
const rawValue = inputValue.value
|
const rawValue = inputValue.value
|
||||||
const value = new ChallengeValue(inputValue.prompt, rawValue)
|
const value = { prompt: inputValue.prompt, value: rawValue }
|
||||||
valuesToProcess.push(value)
|
valuesToProcess.push(value)
|
||||||
}
|
}
|
||||||
const processingPrompts = valuesToProcess.map((v) => v.prompt)
|
const processingPrompts = valuesToProcess.map((v) => v.prompt)
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
override componentDidMount(): void {
|
override componentDidMount(): void {
|
||||||
super.componentDidMount()
|
super.componentDidMount()
|
||||||
this.application.getStatusManager().onStatusChange((message) => {
|
this.application.status.addEventObserver((_event, message) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
arbitraryStatusMessage: message,
|
arbitraryStatusMessage: message,
|
||||||
})
|
})
|
||||||
@@ -124,7 +124,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override onAppStateEvent(eventName: AppStateEvent, data: any) {
|
override onAppStateEvent(eventName: AppStateEvent, data: any) {
|
||||||
const statusService = this.application.getStatusManager()
|
const statusService = this.application.status
|
||||||
switch (eventName) {
|
switch (eventName) {
|
||||||
case AppStateEvent.EditorFocused:
|
case AppStateEvent.EditorFocused:
|
||||||
if (data.eventSource === EventSource.UserInteraction) {
|
if (data.eventSource === EventSource.UserInteraction) {
|
||||||
@@ -172,7 +172,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
break
|
break
|
||||||
case ApplicationEvent.CompletedFullSync:
|
case ApplicationEvent.CompletedFullSync:
|
||||||
if (!this.completedInitialSync) {
|
if (!this.completedInitialSync) {
|
||||||
this.application.getStatusManager().setMessage('')
|
this.application.status.setMessage('')
|
||||||
this.completedInitialSync = true
|
this.completedInitialSync = true
|
||||||
}
|
}
|
||||||
if (!this.didCheckForOffline) {
|
if (!this.didCheckForOffline) {
|
||||||
@@ -202,7 +202,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
break
|
break
|
||||||
case ApplicationEvent.WillSync:
|
case ApplicationEvent.WillSync:
|
||||||
if (!this.completedInitialSync) {
|
if (!this.completedInitialSync) {
|
||||||
this.application.getStatusManager().setMessage('Syncing…')
|
this.application.status.setMessage('Syncing…')
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateSyncStatus() {
|
updateSyncStatus() {
|
||||||
const statusManager = this.application.getStatusManager()
|
const statusManager = this.application.status
|
||||||
const syncStatus = this.application.sync.getSyncStatus()
|
const syncStatus = this.application.sync.getSyncStatus()
|
||||||
const stats = syncStatus.getStats()
|
const stats = syncStatus.getStats()
|
||||||
if (syncStatus.hasError()) {
|
if (syncStatus.hasError()) {
|
||||||
@@ -243,7 +243,7 @@ export class Footer extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateLocalDataStatus() {
|
updateLocalDataStatus() {
|
||||||
const statusManager = this.application.getStatusManager()
|
const statusManager = this.application.status
|
||||||
const syncStatus = this.application.sync.getSyncStatus()
|
const syncStatus = this.application.sync.getSyncStatus()
|
||||||
const stats = syncStatus.getStats()
|
const stats = syncStatus.getStats()
|
||||||
const encryption = this.application.isEncryptionAvailable()
|
const encryption = this.application.isEncryptionAvailable()
|
||||||
|
|||||||
@@ -0,0 +1,355 @@
|
|||||||
|
import { WebApplication } from '@/UIModels/Application'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import {
|
||||||
|
PreferencesGroup,
|
||||||
|
PreferencesSegment,
|
||||||
|
Title,
|
||||||
|
Text,
|
||||||
|
Subtitle,
|
||||||
|
} from '@/Components/Preferences/PreferencesComponents'
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||||
|
import { Button } from '@/Components/Button/Button'
|
||||||
|
import { FileBackupMetadataFile, FileContent, FileHandleRead } from '@standardnotes/snjs'
|
||||||
|
import { Switch } from '@/Components/Switch'
|
||||||
|
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||||
|
import { EncryptionStatusItem } from '../Security/Encryption'
|
||||||
|
import { Icon } from '@/Components/Icon'
|
||||||
|
import { StreamingFileApi } from '@standardnotes/filepicker'
|
||||||
|
import { FunctionComponent } from 'preact'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
application: WebApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
export const FileBackups = observer(({ application }: Props) => {
|
||||||
|
const [backupsEnabled, setBackupsEnabled] = useState(false)
|
||||||
|
const [backupsLocation, setBackupsLocation] = useState('')
|
||||||
|
const backupsService = useMemo(() => application.fileBackups, [application.fileBackups])
|
||||||
|
|
||||||
|
if (!backupsService) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>File Backups</Title>
|
||||||
|
<Subtitle>Automatically save encrypted backups of files uploaded to any device to this computer.</Subtitle>
|
||||||
|
<Text className="mt-3">To enable file backups, use the Standard Notes desktop application.</Text>
|
||||||
|
</PreferencesSegment>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<BackupsDropZone application={application} />
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void backupsService.isFilesBackupsEnabled().then(setBackupsEnabled)
|
||||||
|
}, [backupsService])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (backupsEnabled) {
|
||||||
|
void backupsService.getFilesBackupsLocation().then(setBackupsLocation)
|
||||||
|
}
|
||||||
|
}, [backupsService, backupsEnabled])
|
||||||
|
|
||||||
|
const changeBackupsLocation = useCallback(async () => {
|
||||||
|
await backupsService.changeFilesBackupsLocation()
|
||||||
|
|
||||||
|
setBackupsLocation(await backupsService.getFilesBackupsLocation())
|
||||||
|
}, [backupsService])
|
||||||
|
|
||||||
|
const openBackupsLocation = useCallback(async () => {
|
||||||
|
await backupsService.openFilesBackupsLocation()
|
||||||
|
}, [backupsService])
|
||||||
|
|
||||||
|
const toggleBackups = useCallback(async () => {
|
||||||
|
if (backupsEnabled) {
|
||||||
|
await backupsService.disableFilesBackups()
|
||||||
|
} else {
|
||||||
|
await backupsService.enableFilesBackups()
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackupsEnabled(await backupsService.isFilesBackupsEnabled())
|
||||||
|
}, [backupsService, backupsEnabled])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesGroup>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<Title>File Backups</Title>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex flex-col mr-10">
|
||||||
|
<Subtitle>
|
||||||
|
Automatically save encrypted backups of files uploaded on any device to this computer.
|
||||||
|
</Subtitle>
|
||||||
|
</div>
|
||||||
|
<Switch onChange={toggleBackups} checked={backupsEnabled} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!backupsEnabled && (
|
||||||
|
<>
|
||||||
|
<HorizontalSeparator classes="mt-5 mb-4" />
|
||||||
|
<Text>File backups are not enabled. Enable to choose where your files are backed up.</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PreferencesSegment>
|
||||||
|
|
||||||
|
{backupsEnabled && (
|
||||||
|
<>
|
||||||
|
<PreferencesSegment>
|
||||||
|
<>
|
||||||
|
<Text className="mb-3">
|
||||||
|
Files backups are enabled. When you upload a new file on any device and open this application, files
|
||||||
|
will be downloaded in encrypted form to:
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<EncryptionStatusItem
|
||||||
|
status={backupsLocation}
|
||||||
|
icon={[<Icon type="attachment-file" className="min-w-5 min-h-5" />]}
|
||||||
|
checkmark={false}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-row mt-5">
|
||||||
|
<Button
|
||||||
|
variant="normal"
|
||||||
|
label="Open Backups Location"
|
||||||
|
className={'mr-3 text-xs'}
|
||||||
|
onClick={openBackupsLocation}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="normal"
|
||||||
|
label="Change Backups Location"
|
||||||
|
className={'mr-3 text-xs'}
|
||||||
|
onClick={changeBackupsLocation}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</PreferencesSegment>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<PreferencesSegment>
|
||||||
|
<BackupsDropZone application={application} />
|
||||||
|
</PreferencesSegment>
|
||||||
|
</PreferencesGroup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const isHandlingBackupDrag = (event: DragEvent, application: WebApplication) => {
|
||||||
|
const items = event.dataTransfer?.items
|
||||||
|
|
||||||
|
if (!items) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(items).every((item) => {
|
||||||
|
const isFile = item.kind === 'file'
|
||||||
|
const fileName = item.getAsFile()?.name || ''
|
||||||
|
const isBackupMetadataFile = application.files.isFileNameFileBackupMetadataFile(fileName)
|
||||||
|
return isFile && isBackupMetadataFile
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BackupsDropZone: FunctionComponent<Props> = ({ application }) => {
|
||||||
|
const [droppedFile, setDroppedFile] = useState<FileBackupMetadataFile | undefined>(undefined)
|
||||||
|
const [decryptedFileContent, setDecryptedFileContent] = useState<FileContent | undefined>(undefined)
|
||||||
|
const [binaryFile, setBinaryFile] = useState<FileHandleRead | undefined>(undefined)
|
||||||
|
const [isSavingAsDecrypted, setIsSavingAsDecrypted] = useState(false)
|
||||||
|
|
||||||
|
const fileSystem = useMemo(() => new StreamingFileApi(), [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (droppedFile) {
|
||||||
|
void application.files.decryptBackupMetadataFile(droppedFile).then(setDecryptedFileContent)
|
||||||
|
} else {
|
||||||
|
setDecryptedFileContent(undefined)
|
||||||
|
}
|
||||||
|
}, [droppedFile, application])
|
||||||
|
|
||||||
|
const chooseRelatedBinaryFile = useCallback(async () => {
|
||||||
|
const selection = await application.files.selectFile(fileSystem)
|
||||||
|
|
||||||
|
if (selection === 'aborted' || selection === 'failed') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setBinaryFile(selection)
|
||||||
|
}, [application, fileSystem])
|
||||||
|
|
||||||
|
const downloadBinaryFileAsDecrypted = useCallback(async () => {
|
||||||
|
if (!decryptedFileContent || !binaryFile) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSavingAsDecrypted(true)
|
||||||
|
|
||||||
|
const result = await application.files.readBackupFileAndSaveDecrypted(binaryFile, decryptedFileContent, fileSystem)
|
||||||
|
|
||||||
|
if (result === 'success') {
|
||||||
|
void application.alertService.alert(
|
||||||
|
`<strong>${decryptedFileContent.name}</strong> has been successfully decrypted and saved to your chosen directory.`,
|
||||||
|
)
|
||||||
|
setBinaryFile(undefined)
|
||||||
|
setDecryptedFileContent(undefined)
|
||||||
|
setDroppedFile(undefined)
|
||||||
|
} else if (result === 'failed') {
|
||||||
|
void application.alertService.alert(
|
||||||
|
'Unable to save file to local directory. This may be caused by failure to decrypt, or failure to save the file locally.',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSavingAsDecrypted(false)
|
||||||
|
}, [decryptedFileContent, application, binaryFile, fileSystem])
|
||||||
|
|
||||||
|
const handleDrag = useCallback(
|
||||||
|
(event: DragEvent) => {
|
||||||
|
if (isHandlingBackupDrag(event, application)) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDragIn = useCallback(
|
||||||
|
(event: DragEvent) => {
|
||||||
|
if (!isHandlingBackupDrag(event, application)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDragOut = useCallback(
|
||||||
|
(event: DragEvent) => {
|
||||||
|
if (!isHandlingBackupDrag(event, application)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleDrop = useCallback(
|
||||||
|
async (event: DragEvent) => {
|
||||||
|
if (!isHandlingBackupDrag(event, application)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const items = event.dataTransfer?.items
|
||||||
|
|
||||||
|
if (!items || items.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = items[0]
|
||||||
|
const file = item.getAsFile()
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = await file.text()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const metadata = JSON.parse(text) as FileBackupMetadataFile
|
||||||
|
setDroppedFile(metadata)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
event.dataTransfer.clearData()
|
||||||
|
},
|
||||||
|
[application],
|
||||||
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('dragenter', handleDragIn)
|
||||||
|
window.addEventListener('dragleave', handleDragOut)
|
||||||
|
window.addEventListener('dragover', handleDrag)
|
||||||
|
window.addEventListener('drop', handleDrop)
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('dragenter', handleDragIn)
|
||||||
|
window.removeEventListener('dragleave', handleDragOut)
|
||||||
|
window.removeEventListener('dragover', handleDrag)
|
||||||
|
window.removeEventListener('drop', handleDrop)
|
||||||
|
}
|
||||||
|
}, [handleDragIn, handleDrop, handleDrag, handleDragOut])
|
||||||
|
|
||||||
|
if (!droppedFile) {
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
To decrypt a backup file, drag and drop the file's respective <i>metadata.sn.json</i> file here.
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<PreferencesSegment>
|
||||||
|
{!decryptedFileContent && <Text>Attempting to decrypt metadata file...</Text>}
|
||||||
|
|
||||||
|
{decryptedFileContent && (
|
||||||
|
<>
|
||||||
|
<Title>Backup Decryption</Title>
|
||||||
|
|
||||||
|
<Subtitle className="success">
|
||||||
|
Successfully decrypted metadata file for <i>{decryptedFileContent.name}.</i>
|
||||||
|
</Subtitle>
|
||||||
|
|
||||||
|
<HorizontalSeparator classes={'mt-3 mb-3'} />
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div>
|
||||||
|
<Subtitle>1. Choose related data file</Subtitle>
|
||||||
|
<Text>{droppedFile.file.uuid}</Text>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="normal"
|
||||||
|
label="Choose"
|
||||||
|
className={'px-1 text-xs min-w-40'}
|
||||||
|
onClick={chooseRelatedBinaryFile}
|
||||||
|
disabled={!!binaryFile}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<HorizontalSeparator classes={'mt-3 mb-3'} />
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<Subtitle>2. Decrypt and save file to your computer</Subtitle>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
variant="normal"
|
||||||
|
label={isSavingAsDecrypted ? undefined : 'Save'}
|
||||||
|
className={'px-1 text-xs min-w-40'}
|
||||||
|
onClick={downloadBinaryFileAsDecrypted}
|
||||||
|
disabled={isSavingAsDecrypted || !binaryFile}
|
||||||
|
>
|
||||||
|
{isSavingAsDecrypted && (
|
||||||
|
<div className="flex justify-center w-full">
|
||||||
|
<div className="sk-spinner w-5 h-5 spinner-info"></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</PreferencesSegment>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import { PreferencesPane } from '@/Components/Preferences/PreferencesComponents'
|
|||||||
import { CloudLink } from './CloudBackups'
|
import { CloudLink } from './CloudBackups'
|
||||||
import { DataBackups } from './DataBackups'
|
import { DataBackups } from './DataBackups'
|
||||||
import { EmailBackups } from './EmailBackups'
|
import { EmailBackups } from './EmailBackups'
|
||||||
|
import { FileBackups } from './FileBackups'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
appState: AppState
|
appState: AppState
|
||||||
@@ -15,6 +16,7 @@ export const Backups: FunctionComponent<Props> = ({ application, appState }) =>
|
|||||||
return (
|
return (
|
||||||
<PreferencesPane>
|
<PreferencesPane>
|
||||||
<DataBackups application={application} appState={appState} />
|
<DataBackups application={application} appState={appState} />
|
||||||
|
<FileBackups application={application} />
|
||||||
<EmailBackups application={application} />
|
<EmailBackups application={application} />
|
||||||
<CloudLink application={application} />
|
<CloudLink application={application} />
|
||||||
</PreferencesPane>
|
</PreferencesPane>
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/
|
|||||||
|
|
||||||
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||||
|
|
||||||
const EncryptionStatusItem: FunctionComponent<{
|
export const EncryptionStatusItem: FunctionComponent<{
|
||||||
icon: ComponentChild
|
icon: ComponentChild
|
||||||
status: string
|
status: string
|
||||||
}> = ({ icon, status }) => (
|
checkmark?: boolean
|
||||||
|
}> = ({ icon, status, checkmark = true }) => (
|
||||||
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
<div className="w-full rounded py-1.5 px-3 text-input my-1 min-h-8 flex flex-row items-center bg-contrast no-border focus-within:ring-info">
|
||||||
{icon}
|
{icon}
|
||||||
<div className="min-w-3 min-h-1" />
|
<div className="min-w-3 min-h-1" />
|
||||||
<div className="flex-grow color-text text-sm">{status}</div>
|
<div className="flex-grow color-text text-sm">{status}</div>
|
||||||
<div className="min-w-3 min-h-1" />
|
<div className="min-w-3 min-h-1" />
|
||||||
<Icon className="success min-w-4 min-h-4" type="check-bold" />
|
{checkmark && <Icon className="success min-w-4 min-h-4" type="check-bold" />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export class Database {
|
|||||||
|
|
||||||
constructor(public databaseName: string, private alertService: AlertService) {}
|
constructor(public databaseName: string, private alertService: AlertService) {}
|
||||||
|
|
||||||
public deinit() {
|
public deinit(): void {
|
||||||
;(this.alertService as any) = undefined
|
;(this.alertService as any) = undefined
|
||||||
this.db = undefined
|
this.db = undefined
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ export class Database {
|
|||||||
/**
|
/**
|
||||||
* Relinquishes the lock and allows db operations to proceed
|
* Relinquishes the lock and allows db operations to proceed
|
||||||
*/
|
*/
|
||||||
public unlock() {
|
public unlock(): void {
|
||||||
this.locked = false
|
this.locked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
import { DeviceInterface, Environment } from '@standardnotes/snjs'
|
|
||||||
import { WebClientRequiresDesktopMethods } from './DesktopWebCommunication'
|
|
||||||
import { WebOrDesktopDeviceInterface } from './WebOrDesktopDeviceInterface'
|
|
||||||
|
|
||||||
export function isDesktopDevice(x: DeviceInterface): x is DesktopDeviceInterface {
|
|
||||||
return x.environment === Environment.Desktop
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DesktopDeviceInterface extends WebOrDesktopDeviceInterface, WebClientRequiresDesktopMethods {
|
|
||||||
environment: Environment.Desktop
|
|
||||||
}
|
|
||||||
@@ -1 +1,9 @@
|
|||||||
export { Environment, RawKeychainValue } from '@standardnotes/snjs'
|
export {
|
||||||
|
Environment,
|
||||||
|
RawKeychainValue,
|
||||||
|
DesktopDeviceInterface,
|
||||||
|
WebOrDesktopDeviceInterface,
|
||||||
|
DesktopClientRequiresWebMethods,
|
||||||
|
FileBackupsMapping,
|
||||||
|
FileBackupsDevice,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { DecryptedTransferPayload } from '@standardnotes/snjs'
|
|
||||||
|
|
||||||
export interface WebClientRequiresDesktopMethods {
|
|
||||||
localBackupsCount(): Promise<number>
|
|
||||||
|
|
||||||
viewlocalBackups(): void
|
|
||||||
|
|
||||||
deleteLocalBackups(): Promise<void>
|
|
||||||
|
|
||||||
syncComponents(payloads: unknown[]): void
|
|
||||||
|
|
||||||
onMajorDataChange(): void
|
|
||||||
|
|
||||||
onInitialDataLoad(): void
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys all sensitive storage data, such as localStorage, IndexedDB, and other log files.
|
|
||||||
*/
|
|
||||||
destroyAllData(): void
|
|
||||||
|
|
||||||
onSearch(text?: string): void
|
|
||||||
|
|
||||||
downloadBackup(): void | Promise<void>
|
|
||||||
|
|
||||||
get extensionsServerHost(): string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DesktopClientRequiresWebMethods {
|
|
||||||
updateAvailable(): void
|
|
||||||
|
|
||||||
windowGainedFocus(): void
|
|
||||||
|
|
||||||
windowLostFocus(): void
|
|
||||||
|
|
||||||
onComponentInstallationComplete(componentData: DecryptedTransferPayload, error: unknown): Promise<void>
|
|
||||||
|
|
||||||
requestBackupFile(): Promise<string | undefined>
|
|
||||||
|
|
||||||
didBeginBackup(): void
|
|
||||||
|
|
||||||
didFinishBackup(success: boolean): void
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Environment, RawKeychainValue } from '@standardnotes/snjs'
|
import { Environment, RawKeychainValue } from '@standardnotes/snjs'
|
||||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
import { WebOrDesktopDevice } from './WebOrDesktopDevice'
|
||||||
|
|
||||||
const KEYCHAIN_STORAGE_KEY = 'keychain'
|
const KEYCHAIN_STORAGE_KEY = 'keychain'
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import {
|
|||||||
TransferPayload,
|
TransferPayload,
|
||||||
NamespacedRootKeyInKeychain,
|
NamespacedRootKeyInKeychain,
|
||||||
extendArray,
|
extendArray,
|
||||||
|
WebOrDesktopDeviceInterface,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { Database } from '../Database'
|
import { Database } from '../Database'
|
||||||
import { WebOrDesktopDeviceInterface } from './WebOrDesktopDeviceInterface'
|
|
||||||
|
|
||||||
export abstract class WebOrDesktopDevice implements WebOrDesktopDeviceInterface {
|
export abstract class WebOrDesktopDevice implements WebOrDesktopDeviceInterface {
|
||||||
constructor(public appVersion: string) {}
|
constructor(public appVersion: string) {}
|
||||||
@@ -18,7 +18,7 @@ export abstract class WebOrDesktopDevice implements WebOrDesktopDeviceInterface
|
|||||||
|
|
||||||
abstract environment: Environment
|
abstract environment: Environment
|
||||||
|
|
||||||
setApplication(application: SNApplication) {
|
setApplication(application: SNApplication): void {
|
||||||
const database = new Database(application.identifier, application.alertService)
|
const database = new Database(application.identifier, application.alertService)
|
||||||
|
|
||||||
this.databases.push(database)
|
this.databases.push(database)
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
import { DeviceInterface, RawKeychainValue } from '@standardnotes/snjs'
|
|
||||||
|
|
||||||
export interface WebOrDesktopDeviceInterface extends DeviceInterface {
|
|
||||||
readonly appVersion: string
|
|
||||||
|
|
||||||
getKeychainValue(): Promise<RawKeychainValue>
|
|
||||||
|
|
||||||
setKeychainValue(value: RawKeychainValue): Promise<void>
|
|
||||||
}
|
|
||||||
@@ -10,10 +10,10 @@ import {
|
|||||||
DecryptedTransferPayload,
|
DecryptedTransferPayload,
|
||||||
ComponentContent,
|
ComponentContent,
|
||||||
assert,
|
assert,
|
||||||
|
DesktopClientRequiresWebMethods,
|
||||||
|
DesktopDeviceInterface,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { WebAppEvent, WebApplication } from '@/UIModels/Application'
|
import { WebAppEvent, WebApplication } from '@/UIModels/Application'
|
||||||
import { DesktopDeviceInterface } from '../Device/DesktopDeviceInterface'
|
|
||||||
import { DesktopClientRequiresWebMethods } from '@/Device/DesktopWebCommunication'
|
|
||||||
|
|
||||||
export class DesktopManager
|
export class DesktopManager
|
||||||
extends ApplicationService
|
extends ApplicationService
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { removeFromArray } from '@standardnotes/snjs'
|
|
||||||
|
|
||||||
type StatusCallback = (string: string) => void
|
|
||||||
|
|
||||||
export class StatusManager {
|
|
||||||
private _message = ''
|
|
||||||
private observers: StatusCallback[] = []
|
|
||||||
|
|
||||||
get message(): string {
|
|
||||||
return this._message
|
|
||||||
}
|
|
||||||
|
|
||||||
setMessage(message: string) {
|
|
||||||
this._message = message
|
|
||||||
this.notifyObservers()
|
|
||||||
}
|
|
||||||
|
|
||||||
onStatusChange(callback: StatusCallback) {
|
|
||||||
this.observers.push(callback)
|
|
||||||
return () => {
|
|
||||||
removeFromArray(this.observers, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private notifyObservers() {
|
|
||||||
for (const observer of this.observers) {
|
|
||||||
observer(this._message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
removeFromArray,
|
removeFromArray,
|
||||||
Uuid,
|
Uuid,
|
||||||
PayloadEmitSource,
|
PayloadEmitSource,
|
||||||
|
WebOrDesktopDeviceInterface,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'
|
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'
|
||||||
import { ActionsMenuState } from './ActionsMenuState'
|
import { ActionsMenuState } from './ActionsMenuState'
|
||||||
@@ -31,7 +32,6 @@ import { SearchOptionsState } from './SearchOptionsState'
|
|||||||
import { SubscriptionState } from './SubscriptionState'
|
import { SubscriptionState } from './SubscriptionState'
|
||||||
import { SyncState } from './SyncState'
|
import { SyncState } from './SyncState'
|
||||||
import { TagsState } from './TagsState'
|
import { TagsState } from './TagsState'
|
||||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
|
||||||
import { FilePreviewModalState } from './FilePreviewModalState'
|
import { FilePreviewModalState } from './FilePreviewModalState'
|
||||||
|
|
||||||
export enum AppStateEvent {
|
export enum AppStateEvent {
|
||||||
@@ -93,7 +93,7 @@ export class AppState {
|
|||||||
|
|
||||||
private readonly tagChangedDisposer: IReactionDisposer
|
private readonly tagChangedDisposer: IReactionDisposer
|
||||||
|
|
||||||
constructor(application: WebApplication, private device: WebOrDesktopDevice) {
|
constructor(application: WebApplication, private device: WebOrDesktopDeviceInterface) {
|
||||||
this.application = application
|
this.application = application
|
||||||
this.notes = new NotesState(
|
this.notes = new NotesState(
|
||||||
application,
|
application,
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ import { WebCrypto } from '@/Crypto'
|
|||||||
import { WebAlertService } from '@/Services/AlertService'
|
import { WebAlertService } from '@/Services/AlertService'
|
||||||
import { ArchiveManager } from '@/Services/ArchiveManager'
|
import { ArchiveManager } from '@/Services/ArchiveManager'
|
||||||
import { AutolockService } from '@/Services/AutolockService'
|
import { AutolockService } from '@/Services/AutolockService'
|
||||||
import { DesktopDeviceInterface, isDesktopDevice } from '@/Device/DesktopDeviceInterface'
|
|
||||||
import { DesktopManager } from '@/Services/DesktopManager'
|
import { DesktopManager } from '@/Services/DesktopManager'
|
||||||
import { IOService } from '@/Services/IOService'
|
import { IOService } from '@/Services/IOService'
|
||||||
import { StatusManager } from '@/Services/StatusManager'
|
|
||||||
import { ThemeManager } from '@/Services/ThemeManager'
|
import { ThemeManager } from '@/Services/ThemeManager'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
||||||
@@ -17,6 +15,8 @@ import {
|
|||||||
removeFromArray,
|
removeFromArray,
|
||||||
IconsController,
|
IconsController,
|
||||||
Runtime,
|
Runtime,
|
||||||
|
DesktopDeviceInterface,
|
||||||
|
isDesktopDevice,
|
||||||
} from '@standardnotes/snjs'
|
} from '@standardnotes/snjs'
|
||||||
|
|
||||||
type WebServices = {
|
type WebServices = {
|
||||||
@@ -24,7 +24,6 @@ type WebServices = {
|
|||||||
desktopService?: DesktopManager
|
desktopService?: DesktopManager
|
||||||
autolockService: AutolockService
|
autolockService: AutolockService
|
||||||
archiveService: ArchiveManager
|
archiveService: ArchiveManager
|
||||||
statusManager: StatusManager
|
|
||||||
themeService: ThemeManager
|
themeService: ThemeManager
|
||||||
io: IOService
|
io: IOService
|
||||||
}
|
}
|
||||||
@@ -137,10 +136,6 @@ export class WebApplication extends SNApplication {
|
|||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
getStatusManager() {
|
|
||||||
return this.webServices.statusManager
|
|
||||||
}
|
|
||||||
|
|
||||||
public getThemeService() {
|
public getThemeService() {
|
||||||
return this.webServices.themeService
|
return this.webServices.themeService
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { WebApplication } from './Application'
|
import { WebApplication } from './Application'
|
||||||
import { ApplicationDescriptor, SNApplicationGroup, Platform, Runtime, InternalEventBus } from '@standardnotes/snjs'
|
import {
|
||||||
|
ApplicationDescriptor,
|
||||||
|
SNApplicationGroup,
|
||||||
|
Platform,
|
||||||
|
Runtime,
|
||||||
|
InternalEventBus,
|
||||||
|
isDesktopDevice,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
import { AppState } from '@/UIModels/AppState'
|
import { AppState } from '@/UIModels/AppState'
|
||||||
import { getPlatform, isDesktopApplication } from '@/Utils'
|
import { getPlatform, isDesktopApplication } from '@/Utils'
|
||||||
import { ArchiveManager } from '@/Services/ArchiveManager'
|
import { ArchiveManager } from '@/Services/ArchiveManager'
|
||||||
import { DesktopManager } from '@/Services/DesktopManager'
|
import { DesktopManager } from '@/Services/DesktopManager'
|
||||||
import { IOService } from '@/Services/IOService'
|
import { IOService } from '@/Services/IOService'
|
||||||
import { AutolockService } from '@/Services/AutolockService'
|
import { AutolockService } from '@/Services/AutolockService'
|
||||||
import { StatusManager } from '@/Services/StatusManager'
|
|
||||||
import { ThemeManager } from '@/Services/ThemeManager'
|
import { ThemeManager } from '@/Services/ThemeManager'
|
||||||
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
import { WebOrDesktopDevice } from '@/Device/WebOrDesktopDevice'
|
||||||
import { isDesktopDevice } from '@/Device/DesktopDeviceInterface'
|
|
||||||
|
|
||||||
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -53,7 +58,6 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
|||||||
const archiveService = new ArchiveManager(application)
|
const archiveService = new ArchiveManager(application)
|
||||||
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
const io = new IOService(platform === Platform.MacWeb || platform === Platform.MacDesktop)
|
||||||
const autolockService = new AutolockService(application, new InternalEventBus())
|
const autolockService = new AutolockService(application, new InternalEventBus())
|
||||||
const statusManager = new StatusManager()
|
|
||||||
const themeService = new ThemeManager(application)
|
const themeService = new ThemeManager(application)
|
||||||
|
|
||||||
application.setWebServices({
|
application.setWebServices({
|
||||||
@@ -62,7 +66,6 @@ export class ApplicationGroup extends SNApplicationGroup<WebOrDesktopDevice> {
|
|||||||
desktopService: isDesktopDevice(this.device) ? new DesktopManager(application, this.device) : undefined,
|
desktopService: isDesktopDevice(this.device) ? new DesktopManager(application, this.device) : undefined,
|
||||||
io,
|
io,
|
||||||
autolockService,
|
autolockService,
|
||||||
statusManager,
|
|
||||||
themeService,
|
themeService,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -70,9 +70,9 @@
|
|||||||
"@reach/tooltip": "^0.16.2",
|
"@reach/tooltip": "^0.16.2",
|
||||||
"@reach/visually-hidden": "^0.16.0",
|
"@reach/visually-hidden": "^0.16.0",
|
||||||
"@standardnotes/components": "1.8.0",
|
"@standardnotes/components": "1.8.0",
|
||||||
"@standardnotes/filepicker": "1.13.7",
|
"@standardnotes/filepicker": "1.14.0",
|
||||||
"@standardnotes/sncrypto-web": "1.9.2",
|
"@standardnotes/sncrypto-web": "1.9.2",
|
||||||
"@standardnotes/snjs": "2.106.9",
|
"@standardnotes/snjs": "2.107.0",
|
||||||
"@standardnotes/stylekit": "5.26.0",
|
"@standardnotes/stylekit": "5.26.0",
|
||||||
"@zip.js/zip.js": "^2.4.10",
|
"@zip.js/zip.js": "^2.4.10",
|
||||||
"mobx": "^6.5.0",
|
"mobx": "^6.5.0",
|
||||||
|
|||||||
114
yarn.lock
114
yarn.lock
@@ -2453,76 +2453,78 @@
|
|||||||
eslint-plugin-prettier "^4.0.0"
|
eslint-plugin-prettier "^4.0.0"
|
||||||
prettier "^2.6.2"
|
prettier "^2.6.2"
|
||||||
|
|
||||||
"@standardnotes/domain-events@^2.28.1":
|
"@standardnotes/domain-events@^2.28.3":
|
||||||
version "2.28.1"
|
version "2.28.3"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.28.1.tgz#473350cf867b5af9f18d19cdf5e772f37f11a621"
|
resolved "https://registry.yarnpkg.com/@standardnotes/domain-events/-/domain-events-2.28.3.tgz#96e56009faf6626bd0cf967c7db9969fd08e7dd7"
|
||||||
integrity sha512-1IjE0HA30AwzZ/7RZkVYUQNgWbkRSB8h3xFsbuCLvBUfl/lXrDlHvD2CZMUGKPXlMHBjRivW/r8f7FjDlXNJ9w==
|
integrity sha512-ehWn+6Ii1R0BcNX+ro02eIUZ6j8kiu9hR4Rvd59NS7NNsnAJCTyUmuDxURchwLNZTT4OSeqDBuvp98PiWn5Adg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.13"
|
"@standardnotes/auth" "^3.18.13"
|
||||||
"@standardnotes/features" "^1.42.0"
|
"@standardnotes/features" "^1.42.1"
|
||||||
|
|
||||||
"@standardnotes/encryption@^1.6.10":
|
"@standardnotes/encryption@^1.7.0":
|
||||||
version "1.6.10"
|
version "1.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/encryption/-/encryption-1.6.10.tgz#054bb07e2d49e8b6ec690219c71e4d567838d8aa"
|
resolved "https://registry.yarnpkg.com/@standardnotes/encryption/-/encryption-1.7.0.tgz#3012bed5283e23ddeaae01e9fbaa80c24e8af42d"
|
||||||
integrity sha512-S89WpBqEPnruFV4vMn8etfxKUAGL3StN+I70ujz61OI5ReZhOEf/DobQBFm2u/lSTtnvleEdThLsRp5QZZH0kA==
|
integrity sha512-gBVcTCZ53Eg3/2eZkVrBrS98BU95CR2h23ofnNGhCcy0FlWwv6fAqupSsPufOdce8gh27gF0mhRbi80ljYxCUA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/models" "^1.6.10"
|
"@standardnotes/models" "^1.8.0"
|
||||||
"@standardnotes/responses" "^1.6.18"
|
"@standardnotes/responses" "^1.6.19"
|
||||||
"@standardnotes/services" "^1.10.9"
|
"@standardnotes/services" "^1.11.0"
|
||||||
|
|
||||||
"@standardnotes/features@^1.42.0":
|
"@standardnotes/features@^1.42.1":
|
||||||
version "1.42.0"
|
version "1.42.1"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.42.0.tgz#154171a8436f68bef1f9e797415feea04f60b0c2"
|
resolved "https://registry.yarnpkg.com/@standardnotes/features/-/features-1.42.1.tgz#29d1f753f445328c485ccc0ea95d557b9299c131"
|
||||||
integrity sha512-W79WVV68jWwG0CtiYcT/51N7/IIOCPbkL5UBjKLcZ3XNUoptVTFYvHd+UDhdkc3DuMPghjDghD7pQS7W4KdbVA==
|
integrity sha512-wMm0N112HeIa+VskpmPMfL5eHh3U3s+aCpxiq0HvITD+3BOoQgbbAQ+O+FjpwwhvN8mfBAM+LvNjksXFHhqoJg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.13"
|
"@standardnotes/auth" "^3.18.13"
|
||||||
"@standardnotes/common" "^1.19.8"
|
"@standardnotes/common" "^1.19.8"
|
||||||
|
|
||||||
"@standardnotes/filepicker@1.13.7", "@standardnotes/filepicker@^1.13.7":
|
"@standardnotes/filepicker@1.14.0", "@standardnotes/filepicker@^1.14.0":
|
||||||
version "1.13.7"
|
version "1.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.13.7.tgz#8e9f0736defa6630e8c3fb541d02a9286d8d8875"
|
resolved "https://registry.yarnpkg.com/@standardnotes/filepicker/-/filepicker-1.14.0.tgz#f1ea5e66a453cfc2131bbf665f676b430f649cf2"
|
||||||
integrity sha512-SOqgYqzerhCg2d47j+QkzOBISN9KHU5uxsV7+Heaxag/jJFfyplxWYOskNJzFm5DtgSwV57Y753hIQN8S7YTKA==
|
integrity sha512-E/frksUJhHbMVlLNu4UdHLIdFNuQe9Co8dq/WQgeK/X+Ic+5t5Jm91c4GA1LvCUVF1vhYl/Vo76LY8gE1J2mfg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/common" "^1.19.8"
|
"@standardnotes/common" "^1.19.8"
|
||||||
|
"@standardnotes/services" "^1.11.0"
|
||||||
"@standardnotes/utils" "^1.6.6"
|
"@standardnotes/utils" "^1.6.6"
|
||||||
|
|
||||||
"@standardnotes/files@^1.0.13":
|
"@standardnotes/files@^1.1.0":
|
||||||
version "1.0.13"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/files/-/files-1.0.13.tgz#45feff6f34c49f6a3a845c3b6e71cb25e0132fad"
|
resolved "https://registry.yarnpkg.com/@standardnotes/files/-/files-1.1.0.tgz#c2499f50fdc31addf3b0307388d4c78420415dfe"
|
||||||
integrity sha512-MISbGjXnjAIYIVUqalw3pKMoxXh5sVBbXYrWcpB8okiq/EcLHiVPhGkedxqjkELqzlM/fa3DkougmJBKWh43fQ==
|
integrity sha512-O4cB8nxBTSLPUCoLyglzbj5oshaJ6hKuBleAWPaC+treaiwpWVyfxn3TF7G4MejlNUMIrGfb566YAZ3R5YVfGg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/models" "^1.6.10"
|
"@standardnotes/encryption" "^1.7.0"
|
||||||
"@standardnotes/responses" "^1.6.18"
|
"@standardnotes/models" "^1.8.0"
|
||||||
"@standardnotes/services" "^1.10.9"
|
"@standardnotes/responses" "^1.6.19"
|
||||||
|
"@standardnotes/services" "^1.11.0"
|
||||||
"@standardnotes/utils" "^1.6.6"
|
"@standardnotes/utils" "^1.6.6"
|
||||||
|
|
||||||
"@standardnotes/models@^1.6.10":
|
"@standardnotes/models@^1.8.0":
|
||||||
version "1.6.10"
|
version "1.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/models/-/models-1.6.10.tgz#3a6142b849ed1d7973611011be44dcbefa6df461"
|
resolved "https://registry.yarnpkg.com/@standardnotes/models/-/models-1.8.0.tgz#b295ba16f0392549eb79cd02d67b7e2455f2d9be"
|
||||||
integrity sha512-EpxDvB7GVN+suPYCQ5QmDq/1NezPBVeAzfg56O1I5I8yhl1cyaqLmtSHcPGdgfgnQiio2kbTA16cta3YxtnG/g==
|
integrity sha512-Tl1EZNk/8axTSTb6p7kU9qfS6Fyqxyc+/MxRGj0th+Ry70/UHvRV7iDGT6FKBzBUQR8/5hDK0JG1pDzUSRAhhQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/features" "^1.42.0"
|
"@standardnotes/features" "^1.42.1"
|
||||||
"@standardnotes/responses" "^1.6.18"
|
"@standardnotes/responses" "^1.6.19"
|
||||||
"@standardnotes/utils" "^1.6.6"
|
"@standardnotes/utils" "^1.6.6"
|
||||||
|
|
||||||
"@standardnotes/responses@^1.6.18":
|
"@standardnotes/responses@^1.6.19":
|
||||||
version "1.6.18"
|
version "1.6.19"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.18.tgz#ad448cfe78449f7cef27b9ff169635ef6dca3476"
|
resolved "https://registry.yarnpkg.com/@standardnotes/responses/-/responses-1.6.19.tgz#ac8bf70f35cd7a5649e52b280f53a2bd97f39b76"
|
||||||
integrity sha512-Onmhxd8zKbFGJ3ECbD1yuGqiY58JV/FE7PymNT8WL6P1CuThzqQ3V6hS10tswJgbu50A570i1uBg7i+WjyaHiA==
|
integrity sha512-Oj9CS52UCJhAZCqCN6GReqQ9zosgCV2HZ1uaJLXEDxNeNTxDnPOU9C7CH7tXmA3zoQ44pFpQbeAuee8ZnlH3XQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.13"
|
"@standardnotes/auth" "^3.18.13"
|
||||||
"@standardnotes/common" "^1.19.8"
|
"@standardnotes/common" "^1.19.8"
|
||||||
"@standardnotes/features" "^1.42.0"
|
"@standardnotes/features" "^1.42.1"
|
||||||
|
|
||||||
"@standardnotes/services@^1.10.9":
|
"@standardnotes/services@^1.11.0":
|
||||||
version "1.10.9"
|
version "1.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.10.9.tgz#44a79c8235bc7eaf55d66fc1e8df90ce1ecbceb7"
|
resolved "https://registry.yarnpkg.com/@standardnotes/services/-/services-1.11.0.tgz#7b214093a4d5ef540ce2b6a40cf0f83517c1a2da"
|
||||||
integrity sha512-009VuEWbSV9ULaabFodXIUnzLkOxuy8QTNNrXusQg30kY02dzxLqhh0F+t5YLc1SbVkk+n2/IahPTlUUaCKHQQ==
|
integrity sha512-bR084UoR7EzTC6GD5QxFi7FmcoqhOhZmdeWIwByEOQEDeWG9h0i/lNZo4pAyYykDDv8Z/U3+kNjify4wI+ksZw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.13"
|
"@standardnotes/auth" "^3.18.13"
|
||||||
"@standardnotes/common" "^1.19.8"
|
"@standardnotes/common" "^1.19.8"
|
||||||
"@standardnotes/models" "^1.6.10"
|
"@standardnotes/models" "^1.8.0"
|
||||||
"@standardnotes/responses" "^1.6.18"
|
"@standardnotes/responses" "^1.6.19"
|
||||||
"@standardnotes/utils" "^1.6.6"
|
"@standardnotes/utils" "^1.6.6"
|
||||||
|
|
||||||
"@standardnotes/settings@^1.14.3":
|
"@standardnotes/settings@^1.14.3":
|
||||||
@@ -2544,21 +2546,21 @@
|
|||||||
buffer "^6.0.3"
|
buffer "^6.0.3"
|
||||||
libsodium-wrappers "^0.7.9"
|
libsodium-wrappers "^0.7.9"
|
||||||
|
|
||||||
"@standardnotes/snjs@2.106.9":
|
"@standardnotes/snjs@2.107.0":
|
||||||
version "2.106.9"
|
version "2.107.0"
|
||||||
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.106.9.tgz#34197275ec2ebfd4522c63c091b47b5c974fcaac"
|
resolved "https://registry.yarnpkg.com/@standardnotes/snjs/-/snjs-2.107.0.tgz#1254f8ae439023e6dab60dae73b4c8435bf117d2"
|
||||||
integrity sha512-B/kujDluQNs/xYpRaWYvEWpfflYnLhl4ugW3r15WgcLJSjSLreBOVLaRWXhALl8I4e63rkRwMKeCiwrL7auXWg==
|
integrity sha512-3FOxSDlRADoQoSr3gSvAA9eFQbvo7vOaeeLXtoN7ElUUtG78DadPORtwyQtsUee9EfxQnlaWBXVImPAoy2LUew==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@standardnotes/auth" "^3.18.13"
|
"@standardnotes/auth" "^3.18.13"
|
||||||
"@standardnotes/common" "^1.19.8"
|
"@standardnotes/common" "^1.19.8"
|
||||||
"@standardnotes/domain-events" "^2.28.1"
|
"@standardnotes/domain-events" "^2.28.3"
|
||||||
"@standardnotes/encryption" "^1.6.10"
|
"@standardnotes/encryption" "^1.7.0"
|
||||||
"@standardnotes/features" "^1.42.0"
|
"@standardnotes/features" "^1.42.1"
|
||||||
"@standardnotes/filepicker" "^1.13.7"
|
"@standardnotes/filepicker" "^1.14.0"
|
||||||
"@standardnotes/files" "^1.0.13"
|
"@standardnotes/files" "^1.1.0"
|
||||||
"@standardnotes/models" "^1.6.10"
|
"@standardnotes/models" "^1.8.0"
|
||||||
"@standardnotes/responses" "^1.6.18"
|
"@standardnotes/responses" "^1.6.19"
|
||||||
"@standardnotes/services" "^1.10.9"
|
"@standardnotes/services" "^1.11.0"
|
||||||
"@standardnotes/settings" "^1.14.3"
|
"@standardnotes/settings" "^1.14.3"
|
||||||
"@standardnotes/sncrypto-common" "^1.8.2"
|
"@standardnotes/sncrypto-common" "^1.8.2"
|
||||||
"@standardnotes/utils" "^1.6.6"
|
"@standardnotes/utils" "^1.6.6"
|
||||||
|
|||||||
Reference in New Issue
Block a user