feat: convert Super notes to Markdown behind the scenes while publishing to Listed (#2064)

This commit is contained in:
Mo
2022-11-28 12:19:53 -06:00
committed by GitHub
parent 96e8dfdd31
commit cc2762a29d
5 changed files with 87 additions and 2 deletions

View File

@@ -160,7 +160,6 @@ jobs:
run: run:
working-directory: packages/desktop working-directory: packages/desktop
steps: steps:
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
@@ -169,6 +168,8 @@ jobs:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
- run: yarn install --immutable - run: yarn install --immutable
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3

View File

@@ -295,6 +295,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
return this.diskStorageService return this.diskStorageService
} }
public get actions(): InternalServices.SNActionsService {
return this.actionsManager
}
public computePrivateUsername(username: string): Promise<string | undefined> { public computePrivateUsername(username: string): Promise<string | undefined> {
return ComputePrivateUsername(this.options.crypto, username) return ComputePrivateUsername(this.options.crypto, username)
} }

View File

@@ -1,3 +1,4 @@
import { removeFromArray } from '@standardnotes/utils'
import { SNRootKey } from '@standardnotes/encryption' import { SNRootKey } from '@standardnotes/encryption'
import { ChallengeService } from '../Challenge' import { ChallengeService } from '../Challenge'
import { ListedService } from '../Listed/ListedService' import { ListedService } from '../Listed/ListedService'
@@ -18,6 +19,8 @@ import {
isErrorDecryptingPayload, isErrorDecryptingPayload,
CreateEncryptedBackupFileContextPayload, CreateEncryptedBackupFileContextPayload,
EncryptedTransferPayload, EncryptedTransferPayload,
TransferPayload,
ItemContent,
} from '@standardnotes/models' } from '@standardnotes/models'
import { SNSyncService } from '../Sync/SyncService' import { SNSyncService } from '../Sync/SyncService'
import { PayloadManager } from '../Payloads/PayloadManager' import { PayloadManager } from '../Payloads/PayloadManager'
@@ -34,6 +37,8 @@ import {
Challenge, Challenge,
} from '@standardnotes/services' } from '@standardnotes/services'
type PayloadRequestHandler = (uuid: string) => TransferPayload | undefined
/** /**
* The Actions Service allows clients to interact with action-based extensions. * The Actions Service allows clients to interact with action-based extensions.
* Action-based extensions are mostly RESTful actions that can push a local value or * Action-based extensions are mostly RESTful actions that can push a local value or
@@ -50,6 +55,7 @@ import {
*/ */
export class SNActionsService extends AbstractService { export class SNActionsService extends AbstractService {
private previousPasswords: string[] = [] private previousPasswords: string[] = []
private payloadRequestHandlers: PayloadRequestHandler[] = []
constructor( constructor(
private itemManager: ItemManager, private itemManager: ItemManager,
@@ -81,6 +87,14 @@ export class SNActionsService extends AbstractService {
super.deinit() super.deinit()
} }
public addPayloadRequestHandler(handler: PayloadRequestHandler) {
this.payloadRequestHandlers.push(handler)
return () => {
removeFromArray(this.payloadRequestHandlers, handler)
}
}
public getExtensions(): SNActionsExtension[] { public getExtensions(): SNActionsExtension[] {
const extensionItems = this.itemManager.getItems<SNActionsExtension>(ContentType.ActionsExtension) const extensionItems = this.itemManager.getItems<SNActionsExtension>(ContentType.ActionsExtension)
const excludingListed = extensionItems.filter((extension) => !extension.isListedExtension) const excludingListed = extensionItems.filter((extension) => !extension.isListedExtension)
@@ -312,7 +326,16 @@ export class SNActionsService extends AbstractService {
return {} as ActionResponse return {} as ActionResponse
} }
private async outgoingPayloadForItem(item: DecryptedItemInterface, decrypted = false) { private async outgoingPayloadForItem(
item: DecryptedItemInterface,
decrypted = false,
): Promise<TransferPayload<ItemContent>> {
const payloadFromHandler = this.getPayloadFromRequestHandlers(item.uuid)
if (payloadFromHandler) {
return payloadFromHandler
}
if (decrypted) { if (decrypted) {
return item.payload.ejected() return item.payload.ejected()
} }
@@ -323,4 +346,15 @@ export class SNActionsService extends AbstractService {
return CreateEncryptedBackupFileContextPayload(encrypted) return CreateEncryptedBackupFileContextPayload(encrypted)
} }
private getPayloadFromRequestHandlers(uuid: string): TransferPayload | undefined {
for (const handler of this.payloadRequestHandlers) {
const payload = handler(uuid)
if (payload) {
return payload
}
}
return undefined
}
} }

View File

@@ -0,0 +1,28 @@
import { forwardRef, useCallback, useImperativeHandle } from 'react'
import { $convertToMarkdownString } from '@lexical/markdown'
import { MarkdownTransformers } from '@standardnotes/blocks-editor'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
export type GetMarkdownPluginInterface = {
getMarkdown: () => string
}
const GetMarkdownPlugin = forwardRef<GetMarkdownPluginInterface>((_, ref) => {
const [editor] = useLexicalComposerContext()
useImperativeHandle(ref, () => ({
getMarkdown() {
return getMarkdown()
},
}))
const getMarkdown = useCallback(() => {
return editor.getEditorState().read(() => {
return $convertToMarkdownString(MarkdownTransformers)
})
}, [editor])
return null
})
export default GetMarkdownPlugin

View File

@@ -27,6 +27,7 @@ import { useCommandService } from '@/Components/ApplicationView/CommandProvider'
import { SUPER_SHOW_MARKDOWN_PREVIEW } from '@standardnotes/ui-services' import { SUPER_SHOW_MARKDOWN_PREVIEW } from '@standardnotes/ui-services'
import { SuperNoteMarkdownPreview } from './SuperNoteMarkdownPreview' import { SuperNoteMarkdownPreview } from './SuperNoteMarkdownPreview'
import { ExportPlugin } from './Plugins/ExportPlugin/ExportPlugin' import { ExportPlugin } from './Plugins/ExportPlugin/ExportPlugin'
import GetMarkdownPlugin, { GetMarkdownPluginInterface } from './Plugins/GetMarkdownPlugin/GetMarkdownPlugin'
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin' import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'
const NotePreviewCharLimit = 160 const NotePreviewCharLimit = 160
@@ -50,6 +51,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
const changeEditorFunction = useRef<ChangeEditorFunction>() const changeEditorFunction = useRef<ChangeEditorFunction>()
const ignoreNextChange = useRef(false) const ignoreNextChange = useRef(false)
const [showMarkdownPreview, setShowMarkdownPreview] = useState(false) const [showMarkdownPreview, setShowMarkdownPreview] = useState(false)
const getMarkdownPlugin = useRef<GetMarkdownPluginInterface | null>(null)
const commandService = useCommandService() const commandService = useCommandService()
@@ -64,6 +66,21 @@ export const SuperEditor: FunctionComponent<Props> = ({
setShowMarkdownPreview(false) setShowMarkdownPreview(false)
}, []) }, [])
useEffect(() => {
return application.actions.addPayloadRequestHandler((uuid) => {
if (uuid === note.current.uuid) {
const basePayload = note.current.payload.ejected()
return {
...basePayload,
content: {
...basePayload.content,
text: getMarkdownPlugin.current?.getMarkdown() ?? basePayload.content.text,
},
}
}
})
}, [application])
const [lineHeight, setLineHeight] = useState<EditorLineHeight>(PrefDefaults[PrefKey.EditorLineHeight]) const [lineHeight, setLineHeight] = useState<EditorLineHeight>(PrefDefaults[PrefKey.EditorLineHeight])
const handleChange = useCallback( const handleChange = useCallback(
@@ -149,6 +166,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
<FilePlugin /> <FilePlugin />
<ItemBubblePlugin /> <ItemBubblePlugin />
<BlockPickerMenuPlugin /> <BlockPickerMenuPlugin />
<GetMarkdownPlugin ref={getMarkdownPlugin} />
<DatetimePlugin /> <DatetimePlugin />
<PasswordPlugin /> <PasswordPlugin />
<AutoLinkPlugin /> <AutoLinkPlugin />