Compare commits

...
Author SHA1 Message Date
StandardNotes CI 8c18feeffe chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].4
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].3
2023-04-18 16:50:07 +00:00
Aman Harwara 32c3b98293 chore: fix lockfile 2023-04-18 21:45:17 +05:30
Aman Harwara fe07c58e98 Merge branch 'main' of github.com:standardnotes/app 2023-04-18 21:41:10 +05:30
Aman Harwara 9e1bfc8c73 chore: update clipper when web changes 2023-04-18 21:41:02 +05:30
StandardNotes CI 3c563ae4c3 chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].3
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].2
2023-04-18 15:42:41 +00:00
Aman Harwara ac121265a9 Merge branch 'main' of github.com:standardnotes/app 2023-04-18 20:36:28 +05:30
Aman Harwara fc137a7e7b fix: Fixed issue where the Super note markdown preview modal could not be closed on mobile 2023-04-18 20:36:17 +05:30
StandardNotes CI 8f4ec469ba chore(release): publish
- @standardnotes/[email protected]
 - @standardnotes/[email protected].2
 - @standardnotes/[email protected]
 - @standardnotes/[email protected].1
2023-04-18 11:39:42 +00:00
Aman Harwara 904d8fd1e9 Merge branch 'main' of github.com:standardnotes/app 2023-04-18 16:42:52 +05:30
Aman Harwara 877d46d8eb refactor: import modal 2023-04-18 16:42:42 +05:30
Karol Sójko 1bed27562a chore(web): reset Android app cert fingerprint to production value 2023-04-18 12:47:20 +02:00
StandardNotes CI 0203cf50ea chore(release): publish
- @standardnotes/[email protected].1
 - @standardnotes/[email protected]
2023-04-18 09:43:36 +00:00
Karol Sójko 4adabab256 fix(mobile): Android U2F authenticator response encoding (#2313) 2023-04-18 11:13:40 +02:00
23 changed files with 360 additions and 272 deletions
+4
View File
@@ -3,6 +3,10 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.0.9](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/clipper
## [1.0.8](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-13)
**Note:** Version bump only for package @standardnotes/clipper
+2 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/clipper",
"description": "Web clipper browser extension for Standard Notes",
"version": "1.0.8",
"version": "1.0.9",
"private": true,
"scripts": {
"build-mv2": "yarn clean && webpack --config ./webpack.config.prod.js",
@@ -19,6 +19,7 @@
"clean": "rm -rf dist"
},
"devDependencies": {
"@standardnotes/web": "workspace:*",
"@types/webextension-polyfill": "^0.10.0",
"babel-loader": "^9.1.0",
"copy-webpack-plugin": "11.0.0",
+12
View File
@@ -3,6 +3,18 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.105.58](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.57](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.56](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/desktop
## [3.105.55](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
**Note:** Version bump only for package @standardnotes/desktop
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@standardnotes/desktop",
"main": "./app/dist/index.js",
"version": "3.105.55",
"version": "3.105.58",
"license": "AGPL-3.0-or-later",
"author": "Standard Notes.",
"private": true,
+18
View File
@@ -3,6 +3,24 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.52.4](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/mobile
## [3.52.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
### Bug Fixes
* **mobile:** Android U2F authenticator response encoding ([#2313](https://github.com/standardnotes/app/issues/2313)) ([4adabab](https://github.com/standardnotes/app/commit/4adabab256dd2efde4209d2321564206f6719c69))
# [3.52.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
### Features
@@ -74,27 +74,28 @@ public class Fido2ApiModule extends ReactContextBaseJavaModule {
(AuthenticatorAssertionResponse) publicKeyCredential.getResponse();
WritableMap signInResult = Arguments.createMap();
signInResult.putString("id", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE));
signInResult.putString("rawId", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE));
signInResult.putString("id", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
signInResult.putString("rawId", Base64.encodeToString(signedData.getKeyHandle(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
byte[] extensionOutputsBytes = null;
AuthenticationExtensionsClientOutputs extensionOutputs = publicKeyCredential.getClientExtensionResults();
if (extensionOutputs != null) {
extensionOutputsBytes = extensionOutputs.serializeToBytes();
if (extensionOutputsBytes != null) {
signInResult.putString("clientExtensionResults", Base64.encodeToString(extensionOutputsBytes, Base64.URL_SAFE));
signInResult.putString("clientExtensionResults", Base64.encodeToString(extensionOutputsBytes, Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
}
}
WritableMap response = Arguments.createMap();
response.putString("clientDataJSON", Base64.encodeToString(signedData.getClientDataJSON(), Base64.URL_SAFE));
response.putString("authenticatorData", Base64.encodeToString(signedData.getAuthenticatorData(), Base64.URL_SAFE));
response.putString("signature", Base64.encodeToString(signedData.getSignature(), Base64.URL_SAFE));
response.putString("clientDataJSON", Base64.encodeToString(signedData.getClientDataJSON(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
response.putString("authenticatorData", Base64.encodeToString(signedData.getAuthenticatorData(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
response.putString("signature", Base64.encodeToString(signedData.getSignature(), Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
byte[] userHandle = signedData.getUserHandle();
if (userHandle != null) {
response.putString("userHandle", Base64.encodeToString(userHandle, Base64.URL_SAFE));
response.putString("userHandle", Base64.encodeToString(userHandle, Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING));
}
signInResult.putMap("response", response);
signInResult.putString("type", PublicKeyCredentialType.PUBLIC_KEY.toString());
signInPromise.resolve(signInResult);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/mobile",
"version": "3.52.0",
"version": "3.52.4",
"author": "Standard Notes.",
"private": true,
"license": "AGPL-3.0-or-later",
+16
View File
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [1.4.235](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.234](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.233](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.232](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/releases
## [1.4.231](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
**Note:** Version bump only for package @standardnotes/releases
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/releases",
"version": "1.4.231",
"version": "1.4.235",
"license": "AGPL-3.0-or-later",
"main": "dist/releases.json",
"types": "dist/index.d.ts",
+14
View File
@@ -3,6 +3,20 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.151.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/web
## [3.151.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
### Bug Fixes
* Fixed issue where the Super note markdown preview modal could not be closed on mobile ([fc137a7](https://github.com/standardnotes/app/commit/fc137a7e7ba5bd9329b97c8197f9cf65a0bbad17))
## [3.151.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)
**Note:** Version bump only for package @standardnotes/web
# [3.151.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)
### Features
+36
View File
@@ -1,5 +1,41 @@
{
"versions": [
{
"version": "3.151.3",
"title": "[3.151.3](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.151.2",
"title": "[3.151.2](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
"date": null,
"body": "### Bug Fixes\n\n* Fixed issue where the Super note markdown preview modal could not be closed on mobile ([fc137a7](https://github.com/standardnotes/app/commit/fc137a7e7ba5bd9329b97c8197f9cf65a0bbad17))",
"parsed": {
"_": [
"Fixed issue where the Super note markdown preview modal could not be closed on mobile (fc137a7)"
],
"Bug Fixes": [
"Fixed issue where the Super note markdown preview modal could not be closed on mobile (fc137a7)"
]
}
},
{
"version": "3.151.1",
"title": "[3.151.1](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-18)",
"date": null,
"body": "**Note:** Version bump only for package @standardnotes/web",
"parsed": {
"_": [
"Note: Version bump only for package @standardnotes/web"
]
}
},
{
"version": "3.151.0",
"title": "[3.151.0](https://github.com/standardnotes/app/compare/@standardnotes/[email protected]...@standardnotes/[email protected]) (2023-04-17)",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@standardnotes/web",
"version": "3.151.0",
"version": "3.151.3",
"license": "AGPL-3.0-or-later",
"main": "dist/app.js",
"author": "Standard Notes.",
+2 -2
View File
@@ -10,9 +10,9 @@
"relation": ["delegate_permission/common.handle_all_urls", "delegate_permission/common.get_login_creds"],
"target": {
"namespace": "android_app",
"package_name": "com.standardnotes.dev",
"package_name": "com.standardnotes",
"sha256_cert_fingerprints": [
"FA:C6:17:45:DC:09:03:78:6F:B9:ED:E6:2A:96:2B:39:9F:73:48:F0:BB:6F:89:9B:83:32:66:75:91:03:3B:9C"
"58:3F:C4:1B:49:0C:38:0B:56:FB:E9:EE:47:38:1E:B4:EF:53:E0:37:19:A5:50:3F:C1:F2:9D:CD:8E:28:90:2A"
]
}
}
@@ -165,7 +165,12 @@ const GeneralAccountMenu: FunctionComponent<Props> = ({
</MenuItem>
</>
)}
<MenuItem onClick={() => viewControllerManager.isImportModalVisible.set(true)}>
<MenuItem
onClick={() => {
viewControllerManager.importModalController.setIsVisible(true)
viewControllerManager.accountMenuController.closeAccountMenu()
}}
>
<Icon type="archive" className={iconClassName} />
Import
</MenuItem>
@@ -272,7 +272,7 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
application={application}
viewControllerManager={viewControllerManager}
/>
<ImportModal viewControllerManager={viewControllerManager} />
<ImportModal importModalController={viewControllerManager.importModalController} />
</>
{application.routeService.isDotOrg && <DotOrgNotice />}
{isIOS() && <IosKeyboardClose />}
@@ -1,191 +1,13 @@
import { ViewControllerManager } from '@/Controllers/ViewControllerManager'
import { ContentType, DecryptedTransferPayload, pluralize, SNTag, TagContent, UuidGenerator } from '@standardnotes/snjs'
import { Importer } from '@standardnotes/ui-services'
import { observer } from 'mobx-react-lite'
import { useCallback, useMemo, useReducer, useState } from 'react'
import { useApplication } from '../ApplicationProvider'
import { useStateRef } from '@/Hooks/useStateRef'
import { ImportModalFileItem } from './ImportModalFileItem'
import { useMemo } from 'react'
import ImportModalFileItem from './ImportModalFileItem'
import ImportModalInitialPage from './InitialPage'
import { ImportModalAction, ImportModalFile, ImportModalState } from './Types'
import Modal, { ModalAction } from '../Modal/Modal'
import ModalOverlay from '../Modal/ModalOverlay'
import { ImportModalController } from '@/Controllers/ImportModalController'
const reducer = (state: ImportModalState, action: ImportModalAction): ImportModalState => {
switch (action.type) {
case 'setFiles':
return {
...state,
files: action.files.map((file) => ({
id: UuidGenerator.GenerateUuid(),
file,
status: action.service ? 'ready' : 'pending',
service: action.service,
})),
}
case 'updateFile':
return {
...state,
files: state.files.map((file) => {
if (file.file.name === action.file.file.name) {
return action.file
}
return file
}),
}
case 'removeFile':
return {
...state,
files: state.files.filter((file) => file.id !== action.id),
}
case 'setImportTag':
return {
...state,
importTag: action.tag,
}
case 'clearState':
return {
files: [],
}
}
}
const initialState: ImportModalState = {
files: [],
}
const ImportModal = ({ viewControllerManager }: { viewControllerManager: ViewControllerManager }) => {
const application = useApplication()
const [importer] = useState(() => new Importer(application))
const [state, dispatch] = useReducer(reducer, initialState)
const { files } = state
const filesRef = useStateRef(files)
const importFromPayloads = useCallback(
async (file: ImportModalFile, payloads: DecryptedTransferPayload[]) => {
dispatch({
type: 'updateFile',
file: {
...file,
status: 'importing',
},
})
try {
await importer.importFromTransferPayloads(payloads)
const notesImported = payloads.filter((payload) => payload.content_type === ContentType.Note)
const tagsImported = payloads.filter((payload) => payload.content_type === ContentType.Tag)
const successMessage =
`Successfully imported ${notesImported.length} ` +
pluralize(notesImported.length, 'note', 'notes') +
(tagsImported.length > 0
? ` and ${tagsImported.length} ${pluralize(tagsImported.length, 'tag', 'tags')}`
: '')
dispatch({
type: 'updateFile',
file: {
...file,
status: 'success',
successMessage,
},
})
} catch (error) {
dispatch({
type: 'updateFile',
file: {
...file,
status: 'error',
error: error instanceof Error ? error : new Error('Could not import file'),
},
})
}
},
[importer],
)
const parseAndImport = useCallback(async () => {
const files = filesRef.current
if (files.length === 0) {
return
}
const importedPayloads: DecryptedTransferPayload[] = []
for (const file of files) {
if (!file.service) {
return
}
if (file.status === 'ready' && file.payloads) {
await importFromPayloads(file, file.payloads)
importedPayloads.push(...file.payloads)
continue
}
dispatch({
type: 'updateFile',
file: {
...file,
status: 'parsing',
},
})
try {
const payloads = await importer.getPayloadsFromFile(file.file, file.service)
await importFromPayloads(file, payloads)
importedPayloads.push(...payloads)
} catch (error) {
dispatch({
type: 'updateFile',
file: {
...file,
status: 'error',
error: error instanceof Error ? error : new Error('Could not import file'),
},
})
}
}
const currentDate = new Date()
const importTagPayload: DecryptedTransferPayload<TagContent> = {
uuid: UuidGenerator.GenerateUuid(),
created_at: currentDate,
created_at_timestamp: currentDate.getTime(),
updated_at: currentDate,
updated_at_timestamp: currentDate.getTime(),
content_type: ContentType.Tag,
content: {
title: `Imported on ${currentDate.toLocaleString()}`,
expanded: false,
iconString: '',
references: importedPayloads
.filter((payload) => payload.content_type === ContentType.Note)
.map((payload) => ({
content_type: ContentType.Note,
uuid: payload.uuid,
})),
},
}
const [importTag] = await importer.importFromTransferPayloads([importTagPayload])
if (importTag) {
dispatch({
type: 'setImportTag',
tag: importTag as SNTag,
})
}
}, [filesRef, importFromPayloads, importer])
const closeDialog = useCallback(() => {
viewControllerManager.isImportModalVisible.set(false)
if (state.importTag) {
void viewControllerManager.navigationController.setSelectedTag(state.importTag, 'all', {
userTriggered: true,
})
}
dispatch({
type: 'clearState',
})
}, [state.importTag, viewControllerManager.isImportModalVisible, viewControllerManager.navigationController])
const ImportModal = ({ importModalController }: { importModalController: ImportModalController }) => {
const { files, setFiles, updateFile, removeFile, importer, parseAndImport, isVisible, close } = importModalController
const isReadyToImport = files.length > 0 && files.every((file) => file.status === 'ready')
const importSuccessOrError =
@@ -203,22 +25,28 @@ const ImportModal = ({ viewControllerManager }: { viewControllerManager: ViewCon
{
label: importSuccessOrError ? 'Close' : 'Cancel',
type: 'cancel',
onClick: closeDialog,
onClick: close,
mobileSlot: 'left',
},
],
[closeDialog, importSuccessOrError, isReadyToImport, parseAndImport],
[close, importSuccessOrError, isReadyToImport, parseAndImport],
)
return (
<ModalOverlay isOpen={viewControllerManager.isImportModalVisible.get()} onDismiss={closeDialog}>
<Modal title="Import" close={closeDialog} actions={modalActions}>
<ModalOverlay isOpen={isVisible} onDismiss={close}>
<Modal title="Import" close={close} actions={modalActions}>
<div className="px-4 py-4">
{!files.length && <ImportModalInitialPage dispatch={dispatch} />}
{!files.length && <ImportModalInitialPage setFiles={setFiles} />}
{files.length > 0 && (
<div className="divide-y divide-border">
{files.map((file) => (
<ImportModalFileItem file={file} key={file.id} dispatch={dispatch} importer={importer} />
<ImportModalFileItem
file={file}
key={file.id}
updateFile={updateFile}
removeFile={removeFile}
importer={importer}
/>
))}
</div>
)}
@@ -1,8 +1,9 @@
import { ImportModalController, ImportModalFile } from '@/Controllers/ImportModalController'
import { classNames, ContentType, DecryptedTransferPayload, pluralize } from '@standardnotes/snjs'
import { Importer, NoteImportType } from '@standardnotes/ui-services'
import { Dispatch, useCallback, useEffect } from 'react'
import { observer } from 'mobx-react-lite'
import { useCallback, useEffect } from 'react'
import Icon from '../Icon/Icon'
import { ImportModalAction, ImportModalFile } from './Types'
const NoteImportTypeColors: Record<NoteImportType, string> = {
evernote: 'bg-[#14cc45] text-[#000]',
@@ -20,13 +21,15 @@ const NoteImportTypeIcons: Record<NoteImportType, string> = {
plaintext: 'plain-text',
}
export const ImportModalFileItem = ({
const ImportModalFileItem = ({
file,
dispatch,
updateFile,
removeFile,
importer,
}: {
file: ImportModalFile
dispatch: Dispatch<ImportModalAction>
updateFile: ImportModalController['updateFile']
removeFile: ImportModalController['removeFile']
importer: Importer
}) => {
const setFileService = useCallback(
@@ -34,21 +37,18 @@ export const ImportModalFileItem = ({
let payloads: DecryptedTransferPayload[] | undefined
try {
payloads = service ? await importer.getPayloadsFromFile(file.file, service) : undefined
} catch {
//
} catch (error) {
console.error(error)
}
dispatch({
type: 'updateFile',
file: {
...file,
service,
status: service ? 'ready' : 'pending',
payloads,
},
updateFile({
...file,
service,
status: service ? 'ready' : 'pending',
payloads,
})
},
[dispatch, file, importer],
[file, importer, updateFile],
)
useEffect(() => {
@@ -59,7 +59,7 @@ export const ImportModalFileItem = ({
if (file.service === undefined) {
void detect()
}
}, [dispatch, file, setFileService])
}, [file, setFileService])
const notePayloads =
file.status === 'ready' && file.payloads
@@ -129,10 +129,7 @@ export const ImportModalFileItem = ({
<button
className="ml-2 rounded border border-border bg-default p-1.5 hover:bg-contrast"
onClick={() => {
dispatch({
type: 'removeFile',
id: file.id,
})
removeFile(file.id)
}}
>
<Icon type="close" size="medium" />
@@ -144,3 +141,5 @@ export const ImportModalFileItem = ({
</div>
)
}
export default observer(ImportModalFileItem)
@@ -1,26 +1,23 @@
import { ImportModalController } from '@/Controllers/ImportModalController'
import { ClassicFileReader } from '@standardnotes/filepicker'
import { NoteImportType } from '@standardnotes/ui-services'
import { observer } from 'mobx-react-lite'
import { useCallback } from 'react'
import Button from '../Button/Button'
import Icon from '../Icon/Icon'
import { ImportModalAction } from './Types'
type Props = {
dispatch: React.Dispatch<ImportModalAction>
setFiles: ImportModalController['setFiles']
}
const ImportModalInitialPage = ({ dispatch }: Props) => {
const ImportModalInitialPage = ({ setFiles }: Props) => {
const selectFiles = useCallback(
async (service?: NoteImportType) => {
const files = await ClassicFileReader.selectFiles()
dispatch({
type: 'setFiles',
files,
service,
})
setFiles(files, service)
},
[dispatch],
[setFiles],
)
return (
@@ -33,10 +30,7 @@ const ImportModalInitialPage = ({ dispatch }: Props) => {
onDrop={(e) => {
e.preventDefault()
const files = Array.from(e.dataTransfer.files)
dispatch({
type: 'setFiles',
files,
})
setFiles(files)
}}
>
<div className="text-lg font-semibold">Drag and drop files to auto-detect and import</div>
@@ -90,4 +84,4 @@ const ImportModalInitialPage = ({ dispatch }: Props) => {
)
}
export default ImportModalInitialPage
export default observer(ImportModalInitialPage)
@@ -1,30 +0,0 @@
import { DecryptedTransferPayload, SNTag } from '@standardnotes/models'
import { NoteImportType } from '@standardnotes/ui-services'
type ImportModalFileCommon = {
id: string
file: File
service: NoteImportType | null | undefined
}
export type ImportModalFile = (
| { status: 'pending' }
| { status: 'ready'; payloads?: DecryptedTransferPayload[] }
| { status: 'parsing' }
| { status: 'importing' }
| { status: 'success'; successMessage: string }
| { status: 'error'; error: Error }
) &
ImportModalFileCommon
export type ImportModalState = {
files: ImportModalFile[]
importTag?: SNTag
}
export type ImportModalAction =
| { type: 'setFiles'; files: File[]; service?: NoteImportType }
| { type: 'updateFile'; file: ImportModalFile }
| { type: 'removeFile'; id: ImportModalFile['id'] }
| { type: 'setImportTag'; tag: SNTag }
| { type: 'clearState' }
@@ -6,6 +6,7 @@ import { copyTextToClipboard } from '../../Utils/copyTextToClipboard'
import Modal, { ModalAction } from '@/Components/Modal/Modal'
import { BlocksEditor } from './BlocksEditor'
import { BlocksEditorComposer } from './BlocksEditorComposer'
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
type Props = {
note: SNNote
@@ -28,6 +29,8 @@ export const SuperNoteMarkdownPreview: FunctionComponent<Props> = ({ note, close
setMarkdown(markdown)
}, [])
const isMobileScreen = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
const modalActions: ModalAction[] = useMemo(
() => [
{
@@ -36,8 +39,15 @@ export const SuperNoteMarkdownPreview: FunctionComponent<Props> = ({ note, close
onClick: copy,
mobileSlot: 'left',
},
{
label: 'Done',
type: 'cancel',
onClick: closeDialog,
mobileSlot: 'right',
hidden: !isMobileScreen,
},
],
[copy, didCopy],
[closeDialog, copy, didCopy, isMobileScreen],
)
return (
@@ -0,0 +1,176 @@
import { WebApplication } from '@/Application/Application'
import { DecryptedTransferPayload, SNTag, TagContent } from '@standardnotes/models'
import { ContentType, pluralize, UuidGenerator } from '@standardnotes/snjs'
import { Importer, NoteImportType } from '@standardnotes/ui-services'
import { action, makeObservable, observable } from 'mobx'
import { NavigationController } from './Navigation/NavigationController'
type ImportModalFileCommon = {
id: string
file: File
service: NoteImportType | null | undefined
}
export type ImportModalFile = (
| { status: 'pending' }
| { status: 'ready'; payloads?: DecryptedTransferPayload[] }
| { status: 'parsing' }
| { status: 'importing' }
| { status: 'success'; successMessage: string }
| { status: 'error'; error: Error }
) &
ImportModalFileCommon
export class ImportModalController {
isVisible = false
importer: Importer
files: ImportModalFile[] = []
importTag: SNTag | undefined = undefined
constructor(application: WebApplication, private navigationController: NavigationController) {
makeObservable(this, {
isVisible: observable,
setIsVisible: action,
files: observable,
setFiles: action,
updateFile: action,
removeFile: action,
importTag: observable,
setImportTag: action,
})
this.importer = new Importer(application)
}
setIsVisible = (isVisible: boolean) => {
this.isVisible = isVisible
}
setFiles = (files: File[], service?: NoteImportType) => {
this.files = files.map((file) => ({
id: UuidGenerator.GenerateUuid(),
file,
service,
status: service ? 'ready' : 'pending',
}))
}
updateFile = (file: ImportModalFile) => {
this.files = this.files.map((f) => (f.id === file.id ? file : f))
}
removeFile = (id: ImportModalFile['id']) => {
this.files = this.files.filter((f) => f.id !== id)
}
setImportTag = (tag: SNTag | undefined) => {
this.importTag = tag
}
close = () => {
this.setIsVisible(false)
if (this.importTag) {
this.navigationController
.setSelectedTag(this.importTag, 'all', {
userTriggered: true,
})
.catch(console.error)
}
this.setFiles([])
this.setImportTag(undefined)
}
importFromPayloads = async (file: ImportModalFile, payloads: DecryptedTransferPayload[]) => {
this.updateFile({
...file,
status: 'importing',
})
try {
await this.importer.importFromTransferPayloads(payloads)
const notesImported = payloads.filter((payload) => payload.content_type === ContentType.Note)
const tagsImported = payloads.filter((payload) => payload.content_type === ContentType.Tag)
const successMessage =
`Successfully imported ${notesImported.length} ` +
pluralize(notesImported.length, 'note', 'notes') +
(tagsImported.length > 0 ? ` and ${tagsImported.length} ${pluralize(tagsImported.length, 'tag', 'tags')}` : '')
this.updateFile({
...file,
status: 'success',
successMessage,
})
} catch (error) {
this.updateFile({
...file,
status: 'error',
error: error instanceof Error ? error : new Error('Could not import file'),
})
console.error(error)
}
}
parseAndImport = async () => {
if (this.files.length === 0) {
return
}
const importedPayloads: DecryptedTransferPayload[] = []
for (const file of this.files) {
if (!file.service) {
return
}
if (file.status === 'ready' && file.payloads) {
await this.importFromPayloads(file, file.payloads)
importedPayloads.push(...file.payloads)
continue
}
this.updateFile({
...file,
status: 'parsing',
})
try {
const payloads = await this.importer.getPayloadsFromFile(file.file, file.service)
await this.importFromPayloads(file, payloads)
importedPayloads.push(...payloads)
} catch (error) {
this.updateFile({
...file,
status: 'error',
error: error instanceof Error ? error : new Error('Could not import file'),
})
console.error(error)
}
}
const currentDate = new Date()
const importTagPayload: DecryptedTransferPayload<TagContent> = {
uuid: UuidGenerator.GenerateUuid(),
created_at: currentDate,
created_at_timestamp: currentDate.getTime(),
updated_at: currentDate,
updated_at_timestamp: currentDate.getTime(),
content_type: ContentType.Tag,
content: {
title: `Imported on ${currentDate.toLocaleString()}`,
expanded: false,
iconString: '',
references: importedPayloads
.filter((payload) => payload.content_type === ContentType.Note)
.map((payload) => ({
content_type: ContentType.Note,
uuid: payload.uuid,
})),
},
}
const [importTag] = await this.importer.importFromTransferPayloads([importTagPayload])
if (importTag) {
this.setImportTag(importTag as SNTag)
}
}
}
@@ -40,6 +40,7 @@ import { PersistenceService } from './Abstract/PersistenceService'
import { CrossControllerEvent } from './CrossControllerEvent'
import { EventObserverInterface } from '@/Event/EventObserverInterface'
import { ApplicationEventObserver } from '@/Event/ApplicationEventObserver'
import { ImportModalController } from './ImportModalController'
export class ViewControllerManager implements InternalEventHandlerInterface {
readonly enableUnfinishedFeatures: boolean = window?.enabledUnfinishedFeatures
@@ -67,9 +68,9 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
readonly historyModalController: HistoryModalController
readonly linkingController: LinkingController
readonly paneController: PaneController
readonly importModalController: ImportModalController
public isSessionsModalVisible = false
public isImportModalVisible = observable.box(false)
private appEventObserverRemovers: (() => void)[] = []
private eventBus: InternalEventBus
@@ -154,6 +155,8 @@ export class ViewControllerManager implements InternalEventHandlerInterface {
this.historyModalController = new HistoryModalController(this.application, this.eventBus, this.notesController)
this.importModalController = new ImportModalController(this.application, this.navigationController)
this.toastService = new ToastService()
this.applicationEventObserver = new ApplicationEventObserver(
+1
View File
@@ -4877,6 +4877,7 @@ __metadata:
resolution: "@standardnotes/clipper@workspace:packages/clipper"
dependencies:
"@mozilla/readability": ^0.4.2
"@standardnotes/web": "workspace:*"
"@types/webextension-polyfill": ^0.10.0
babel-loader: ^9.1.0
copy-webpack-plugin: 11.0.0