Skip to content

Commit

Permalink
feat: super note import option in import modal
Browse files Browse the repository at this point in the history
  • Loading branch information
amanharwara committed Aug 17, 2023
1 parent 21b34a5 commit 4207348
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export interface SuperConverterServiceInterface {
isValidSuperString(superString: string): boolean
convertString: (superString: string, toFormat: 'txt' | 'md' | 'html' | 'json') => string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { GenerateUuid } from '@standardnotes/services'

export class HTMLConverter {
constructor(_generateUuid: GenerateUuid) {}

static isHTMLFile(file: File): boolean {
return file.type === 'text/html'
}
}
31 changes: 28 additions & 3 deletions packages/ui-services/src/Import/Importer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,38 @@ import { PlaintextConverter } from './PlaintextConverter/PlaintextConverter'
import { SimplenoteConverter } from './SimplenoteConverter/SimplenoteConverter'
import { readFileAsText } from './Utils'
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { HTMLConverter } from './HTMLConverter/HTMLConverter'
import { SuperConverterServiceInterface } from '@standardnotes/snjs/dist/@types'
import { SuperConverter } from './SuperConverter/SuperConverter'

export type NoteImportType = 'plaintext' | 'evernote' | 'google-keep' | 'simplenote' | 'aegis'
export type NoteImportType = 'plaintext' | 'evernote' | 'google-keep' | 'simplenote' | 'aegis' | 'html' | 'super'

export class Importer {
aegisConverter: AegisToAuthenticatorConverter
googleKeepConverter: GoogleKeepConverter
simplenoteConverter: SimplenoteConverter
plaintextConverter: PlaintextConverter
evernoteConverter: EvernoteConverter
htmlConverter: HTMLConverter
superConverter: SuperConverter

constructor(
private features: FeaturesClientInterface,
private mutator: MutatorClientInterface,
private items: ItemManagerInterface,
private superConverterService: SuperConverterServiceInterface,
_generateUuid: GenerateUuid,
) {
this.aegisConverter = new AegisToAuthenticatorConverter(_generateUuid)
this.googleKeepConverter = new GoogleKeepConverter(_generateUuid)
this.simplenoteConverter = new SimplenoteConverter(_generateUuid)
this.plaintextConverter = new PlaintextConverter(_generateUuid)
this.evernoteConverter = new EvernoteConverter(_generateUuid)
this.htmlConverter = new HTMLConverter(_generateUuid)
this.superConverter = new SuperConverter(this.superConverterService, _generateUuid)
}

static detectService = async (file: File): Promise<NoteImportType | null> => {
detectService = async (file: File): Promise<NoteImportType | null> => {
const content = await readFileAsText(file)

const { ext } = parseFileName(file.name)
Expand All @@ -46,6 +54,10 @@ export class Importer {
return 'evernote'
}

if (file.type === 'application/json' && this.superConverterService.isValidSuperString(content)) {
return 'super'
}

try {
const json = JSON.parse(content)

Expand All @@ -68,11 +80,24 @@ export class Importer {
return 'plaintext'
}

if (HTMLConverter.isHTMLFile(file)) {
return 'html'
}

return null
}

async getPayloadsFromFile(file: File, type: NoteImportType): Promise<DecryptedTransferPayload[]> {
if (type === 'aegis') {
if (type === 'super') {
const isEntitledToSuper =
this.features.getFeatureStatus(
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.SuperEditor).getValue(),
) === FeatureStatus.Entitled
if (!isEntitledToSuper) {
throw new Error('Importing Super notes requires a subscription.')
}
return [await this.superConverter.convertSuperFileToNote(file)]
} else if (type === 'aegis') {
const isEntitledToAuthenticator =
this.features.getFeatureStatus(
NativeFeatureIdentifier.create(NativeFeatureIdentifier.TYPES.TokenVaultEditor).getValue(),
Expand Down
43 changes: 43 additions & 0 deletions packages/ui-services/src/Import/SuperConverter/SuperConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { SuperConverterServiceInterface } from '@standardnotes/files'
import { DecryptedTransferPayload, NoteContent } from '@standardnotes/models'
import { GenerateUuid } from '@standardnotes/services'
import { readFileAsText } from '../Utils'
import { parseFileName } from '@standardnotes/filepicker'
import { ContentType } from '@standardnotes/domain-core'
import { NativeFeatureIdentifier, NoteType } from '@standardnotes/features'

export class SuperConverter {
constructor(
private converterService: SuperConverterServiceInterface,
private _generateUuid: GenerateUuid,
) {}

async convertSuperFileToNote(file: File): Promise<DecryptedTransferPayload<NoteContent>> {
const content = await readFileAsText(file)

if (!this.converterService.isValidSuperString(content)) {
throw new Error('Content is not valid Super JSON')
}

const { name } = parseFileName(file.name)

const createdAtDate = file.lastModified ? new Date(file.lastModified) : new Date()
const updatedAtDate = file.lastModified ? new Date(file.lastModified) : new Date()

return {
created_at: createdAtDate,
created_at_timestamp: createdAtDate.getTime(),
updated_at: updatedAtDate,
updated_at_timestamp: updatedAtDate.getTime(),
uuid: this._generateUuid.execute().getValue(),
content_type: ContentType.TYPES.Note,
content: {
title: name,
text: content,
references: [],
noteType: NoteType.Super,
editorIdentifier: NativeFeatureIdentifier.TYPES.SuperEditor,
},
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const Web_TYPES = {
AutolockService: Symbol.for('AutolockService'),
ChangelogService: Symbol.for('ChangelogService'),
DesktopManager: Symbol.for('DesktopManager'),
SuperConverter: Symbol.for('SuperConverter'),
Importer: Symbol.for('Importer'),
ItemGroupController: Symbol.for('ItemGroupController'),
KeyboardService: Symbol.for('KeyboardService'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,24 @@ import { PanesForLayout } from '../UseCase/PanesForLayout'
import { LoadPurchaseFlowUrl } from '../UseCase/LoadPurchaseFlowUrl'
import { GetPurchaseFlowUrl } from '../UseCase/GetPurchaseFlowUrl'
import { OpenSubscriptionDashboard } from '../UseCase/OpenSubscriptionDashboard'
import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter'

export class WebDependencies extends DependencyContainer {
constructor(private application: WebApplicationInterface) {
super()

this.bind(Web_TYPES.SuperConverter, () => {
return new HeadlessSuperConverter()
})

this.bind(Web_TYPES.Importer, () => {
return new Importer(application.features, application.mutator, application.items, application.generateUuid)
return new Importer(
application.features,
application.mutator,
application.items,
this.get<HeadlessSuperConverter>(Web_TYPES.SuperConverter),
application.generateUuid,
)
})

this.bind(Web_TYPES.IsNativeIOS, () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ const NoteImportTypeColors: Record<NoteImportType, string> = {
'google-keep': 'bg-[#fbbd00] text-[#000]',
aegis: 'bg-[#0d47a1] text-default',
plaintext: 'bg-default border border-border',
html: 'bg-accessory-tint-2',
super: 'bg-accessory-tint-1',
}

const NoteImportTypeIcons: Record<NoteImportType, string> = {
Expand All @@ -19,6 +21,8 @@ const NoteImportTypeIcons: Record<NoteImportType, string> = {
'google-keep': 'gkeep',
aegis: 'aegis',
plaintext: 'plain-text',
html: 'rich-text',
super: 'file-doc',
}

const ImportModalFileItem = ({
Expand Down Expand Up @@ -53,13 +57,13 @@ const ImportModalFileItem = ({

useEffect(() => {
const detect = async () => {
const detectedService = await Importer.detectService(file.file)
const detectedService = await importer.detectService(file.file)
void setFileService(detectedService)
}
if (file.service === undefined) {
void detect()
}
}, [file, setFileService])
}, [file, importer, setFileService])

const notePayloads =
file.status === 'ready' && file.payloads
Expand Down
46 changes: 19 additions & 27 deletions packages/web/src/javascripts/Components/ImportModal/InitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,41 +38,33 @@ const ImportModalInitialPage = ({ setFiles }: Props) => {
</button>
<div className="text-center my-4 w-full">or import from:</div>
<div className="flex flex-wrap items-center justify-center gap-4">
<Button
className="flex items-center bg-[#14cc45] !py-2 text-[#000]"
primary
onClick={() => selectFiles('evernote')}
>
<Icon type="evernote" className="mr-2" />
<Button className="flex items-center !py-2" onClick={() => selectFiles('evernote')}>
<Icon type="evernote" className="text-[#14cc45] mr-2" />
Evernote
</Button>
<Button
className="flex items-center bg-[#fbbd00] !py-2 text-[#000]"
primary
onClick={() => selectFiles('google-keep')}
>
<Icon type="gkeep" className="mr-2" />
<Button className="flex items-center !py-2" onClick={() => selectFiles('google-keep')}>
<Icon type="gkeep" className="text-[#fbbd00] mr-2" />
Google Keep
</Button>
<Button className="flex items-center bg-[#3360cc] !py-2" primary onClick={() => selectFiles('simplenote')}>
<Icon type="simplenote" className="mr-2" />
<Button className="flex items-center !py-2" onClick={() => selectFiles('simplenote')}>
<Icon type="simplenote" className="text-[#3360cc] mr-2" />
Simplenote
</Button>
<Button className="flex items-center bg-[#0d47a1] !py-2" primary onClick={() => selectFiles('aegis')}>
<Icon type="aegis" className="mr-2" />
Aegis Authenticator
<Button className="flex items-center !py-2" onClick={() => selectFiles('aegis')}>
<Icon type="aegis" className="bg-[#0d47a1] text-[#fff] rounded mr-2 p-1" size="normal" />
Aegis
</Button>
<Button className="flex items-center bg-info !py-2" onClick={() => selectFiles('plaintext')} primary>
<Icon type="plain-text" className="mr-2" />
Plaintext
<Button className="flex items-center !py-2" onClick={() => selectFiles('plaintext')}>
<Icon type="plain-text" className="text-info mr-2" />
Plaintext / Markdown
</Button>
<Button
className="flex items-center bg-accessory-tint-4 !py-2"
primary
onClick={() => selectFiles('plaintext')}
>
<Icon type="markdown" className="mr-2" />
Markdown
{/* <Button className="flex items-center !py-2" onClick={() => selectFiles('rich-text')}>
<Icon type="rich-text" className="text-accessory-tint-2 mr-2" />
HTML
</Button> */}
<Button className="flex items-center !py-2" onClick={() => selectFiles('super')}>
<Icon type="file-doc" className="text-accessory-tint-1 mr-2" />
Super (JSON)
</Button>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@ export class HeadlessSuperConverter implements SuperConverterServiceInterface {
})
}

isValidSuperString(superString: string): boolean {
try {
this.editor.parseEditorState(superString)
return true
} catch (error) {
return false
}
}

convertString(superString: string, format: 'txt' | 'md' | 'html' | 'json'): string {
if (superString.length === 0) {
return superString
Expand Down

0 comments on commit 4207348

Please sign in to comment.