diff --git a/packages/web/src/javascripts/Application/Device/DesktopManager.ts b/packages/web/src/javascripts/Application/Device/DesktopManager.ts index 365607cd3..e1cb86cf3 100644 --- a/packages/web/src/javascripts/Application/Device/DesktopManager.ts +++ b/packages/web/src/javascripts/Application/Device/DesktopManager.ts @@ -1,4 +1,4 @@ -import { InvisibleSuperConverter } from '@/Components/SuperEditor/Tools/InvisibleMarkdownConverter' +import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter' import { SNComponent, ComponentMutator, @@ -40,7 +40,7 @@ export class DesktopManager ) { super(application, new InternalEventBus()) - const markdownConverter = new InvisibleSuperConverter() + const markdownConverter = new HeadlessSuperConverter() backups.setSuperConverter(markdownConverter) } diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ExportPlugin/ExportPlugin.ts b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ExportPlugin/ExportPlugin.ts index 485278955..23d54763e 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/ExportPlugin/ExportPlugin.ts +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/ExportPlugin/ExportPlugin.ts @@ -9,17 +9,17 @@ import { SUPER_EXPORT_JSON, SUPER_EXPORT_MARKDOWN, } from '@standardnotes/ui-services' -import { useCallback, useEffect } from 'react' -import { $convertToMarkdownString } from '@lexical/markdown' -import { MarkdownTransformers } from '../../MarkdownTransformers' -import { $generateHtmlFromNodes } from '@lexical/html' +import { useCallback, useEffect, useRef } from 'react' import { useCommandService } from '@/Components/CommandProvider' +import { HeadlessSuperConverter } from '../../Tools/HeadlessSuperConverter' export const ExportPlugin = () => { const application = useApplication() const [editor] = useLexicalComposerContext() const commandService = useCommandService() + const converter = useRef(new HeadlessSuperConverter()) + const downloadData = useCallback( (data: Blob, fileName: string) => { if (!application.isNativeMobileWeb()) { @@ -38,7 +38,7 @@ export const ExportPlugin = () => { const exportJson = useCallback( (title: string) => { - const content = JSON.stringify(editor.toJSON()) + const content = converter.current.convertString(JSON.stringify(editor.getEditorState()), 'json') const blob = new Blob([content], { type: 'application/json' }) downloadData(blob, `${sanitizeFileName(title)}.json`) }, @@ -47,22 +47,18 @@ export const ExportPlugin = () => { const exportMarkdown = useCallback( (title: string) => { - editor.getEditorState().read(() => { - const content = $convertToMarkdownString(MarkdownTransformers) - const blob = new Blob([content], { type: 'text/markdown' }) - downloadData(blob, `${sanitizeFileName(title)}.md`) - }) + const content = converter.current.convertString(JSON.stringify(editor.getEditorState()), 'md') + const blob = new Blob([content], { type: 'text/markdown' }) + downloadData(blob, `${sanitizeFileName(title)}.md`) }, [downloadData, editor], ) const exportHtml = useCallback( (title: string) => { - editor.getEditorState().read(() => { - const content = $generateHtmlFromNodes(editor) - const blob = new Blob([content], { type: 'text/html' }) - downloadData(blob, `${sanitizeFileName(title)}.html`) - }) + const content = converter.current.convertString(JSON.stringify(editor.getEditorState()), 'html') + const blob = new Blob([content], { type: 'text/html' }) + downloadData(blob, `${sanitizeFileName(title)}.html`) }, [downloadData, editor], ) diff --git a/packages/web/src/javascripts/Components/SuperEditor/Plugins/MarkdownPreviewPlugin/MarkdownPreviewPlugin.tsx b/packages/web/src/javascripts/Components/SuperEditor/Plugins/MarkdownPreviewPlugin/MarkdownPreviewPlugin.tsx index f96c90487..b4fafadd8 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Plugins/MarkdownPreviewPlugin/MarkdownPreviewPlugin.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Plugins/MarkdownPreviewPlugin/MarkdownPreviewPlugin.tsx @@ -1,7 +1,7 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { useEffect } from 'react' import { $createCodeNode } from '@lexical/code' -import { $createTextNode, $getRoot } from 'lexical' +import { $createTextNode, $getRoot, $nodesOfType, ParagraphNode } from 'lexical' import { $convertToMarkdownString } from '@lexical/markdown' import { MarkdownTransformers } from '../../MarkdownTransformers' @@ -15,6 +15,12 @@ export default function MarkdownPreviewPlugin({ onMarkdown }: Props): JSX.Elemen useEffect(() => { editor.update(() => { const root = $getRoot() + const paragraphs = $nodesOfType(ParagraphNode) + for (const paragraph of paragraphs) { + if (paragraph.isEmpty()) { + paragraph.remove() + } + } const markdown = $convertToMarkdownString(MarkdownTransformers) root.clear().append($createCodeNode('markdown').append($createTextNode(markdown))) root.selectEnd() diff --git a/packages/web/src/javascripts/Components/SuperEditor/SuperNoteConverter.tsx b/packages/web/src/javascripts/Components/SuperEditor/SuperNoteConverter.tsx index ca0f9aa0c..77e382748 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/SuperNoteConverter.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/SuperNoteConverter.tsx @@ -6,7 +6,7 @@ import Icon from '../Icon/Icon' import Modal, { ModalAction } from '../Modal/Modal' import { EditorMenuItem } from '../NotesOptions/EditorMenuItem' import { NoteViewController } from '../NoteView/Controller/NoteViewController' -import { InvisibleSuperConverter } from './Tools/InvisibleMarkdownConverter' +import { HeadlessSuperConverter } from './Tools/HeadlessSuperConverter' const SuperNoteConverter = ({ note, @@ -47,7 +47,7 @@ const SuperNoteConverter = ({ return note.text } - return new InvisibleSuperConverter().convertString(note.text, format) + return new HeadlessSuperConverter().convertString(note.text, format) }, [format, note]) const componentViewer = useMemo(() => { diff --git a/packages/web/src/javascripts/Components/SuperEditor/Tools/InvisibleMarkdownConverter.tsx b/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx similarity index 78% rename from packages/web/src/javascripts/Components/SuperEditor/Tools/InvisibleMarkdownConverter.tsx rename to packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx index b12fee497..d3a840a3d 100644 --- a/packages/web/src/javascripts/Components/SuperEditor/Tools/InvisibleMarkdownConverter.tsx +++ b/packages/web/src/javascripts/Components/SuperEditor/Tools/HeadlessSuperConverter.tsx @@ -1,13 +1,13 @@ import { createHeadlessEditor } from '@lexical/headless' import { $convertToMarkdownString } from '@lexical/markdown' import { SuperConverterServiceInterface } from '@standardnotes/snjs' -import { LexicalEditor } from 'lexical' +import { $nodesOfType, LexicalEditor, ParagraphNode } from 'lexical' import BlocksEditorTheme from '../Lexical/Theme/Theme' import { BlockEditorNodes } from '../Lexical/Nodes/AllNodes' import { MarkdownTransformers } from '../MarkdownTransformers' import { $generateHtmlFromNodes } from '@lexical/html' -export class InvisibleSuperConverter implements SuperConverterServiceInterface { +export class HeadlessSuperConverter implements SuperConverterServiceInterface { private editor: LexicalEditor constructor() { @@ -33,9 +33,16 @@ export class InvisibleSuperConverter implements SuperConverterServiceInterface { () => { switch (format) { case 'txt': - case 'md': + case 'md': { + const paragraphs = $nodesOfType(ParagraphNode) + for (const paragraph of paragraphs) { + if (paragraph.isEmpty()) { + paragraph.remove() + } + } content = $convertToMarkdownString(MarkdownTransformers) break + } case 'html': content = $generateHtmlFromNodes(this.editor) break diff --git a/packages/web/src/javascripts/Utils/NoteExportUtils.ts b/packages/web/src/javascripts/Utils/NoteExportUtils.ts index d5b61e8be..67bb4fba6 100644 --- a/packages/web/src/javascripts/Utils/NoteExportUtils.ts +++ b/packages/web/src/javascripts/Utils/NoteExportUtils.ts @@ -1,5 +1,5 @@ import { WebApplication } from '@/Application/WebApplication' -import { InvisibleSuperConverter } from '@/Components/SuperEditor/Tools/InvisibleMarkdownConverter' +import { HeadlessSuperConverter } from '@/Components/SuperEditor/Tools/HeadlessSuperConverter' import { PrefDefaults } from '@/Constants/PrefDefaults' import { NoteType, PrefKey, SNNote } from '@standardnotes/snjs' @@ -43,7 +43,7 @@ export const getNoteBlob = (application: WebApplication, note: SNNote) => { break } const content = - note.noteType === NoteType.Super ? new InvisibleSuperConverter().convertString(note.text, format) : note.text + note.noteType === NoteType.Super ? new HeadlessSuperConverter().convertString(note.text, format) : note.text const blob = new Blob([content], { type, })