feat: convert Super notes to Markdown behind the scenes while publishing to Listed (#2064)
This commit is contained in:
3
.github/workflows/desktop.release.reuse.yml
vendored
3
.github/workflows/desktop.release.reuse.yml
vendored
@@ -160,7 +160,6 @@ jobs:
|
||||
run:
|
||||
working-directory: packages/desktop
|
||||
steps:
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
@@ -169,6 +168,8 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: echo APP_VERSION=$(node -p "require('./../web/package.json').version") >> $GITHUB_ENV
|
||||
|
||||
- run: yarn install --immutable
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
|
||||
@@ -295,6 +295,10 @@ export class SNApplication implements ApplicationInterface, AppGroupManagedAppli
|
||||
return this.diskStorageService
|
||||
}
|
||||
|
||||
public get actions(): InternalServices.SNActionsService {
|
||||
return this.actionsManager
|
||||
}
|
||||
|
||||
public computePrivateUsername(username: string): Promise<string | undefined> {
|
||||
return ComputePrivateUsername(this.options.crypto, username)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { removeFromArray } from '@standardnotes/utils'
|
||||
import { SNRootKey } from '@standardnotes/encryption'
|
||||
import { ChallengeService } from '../Challenge'
|
||||
import { ListedService } from '../Listed/ListedService'
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
isErrorDecryptingPayload,
|
||||
CreateEncryptedBackupFileContextPayload,
|
||||
EncryptedTransferPayload,
|
||||
TransferPayload,
|
||||
ItemContent,
|
||||
} from '@standardnotes/models'
|
||||
import { SNSyncService } from '../Sync/SyncService'
|
||||
import { PayloadManager } from '../Payloads/PayloadManager'
|
||||
@@ -34,6 +37,8 @@ import {
|
||||
Challenge,
|
||||
} from '@standardnotes/services'
|
||||
|
||||
type PayloadRequestHandler = (uuid: string) => TransferPayload | undefined
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -50,6 +55,7 @@ import {
|
||||
*/
|
||||
export class SNActionsService extends AbstractService {
|
||||
private previousPasswords: string[] = []
|
||||
private payloadRequestHandlers: PayloadRequestHandler[] = []
|
||||
|
||||
constructor(
|
||||
private itemManager: ItemManager,
|
||||
@@ -81,6 +87,14 @@ export class SNActionsService extends AbstractService {
|
||||
super.deinit()
|
||||
}
|
||||
|
||||
public addPayloadRequestHandler(handler: PayloadRequestHandler) {
|
||||
this.payloadRequestHandlers.push(handler)
|
||||
|
||||
return () => {
|
||||
removeFromArray(this.payloadRequestHandlers, handler)
|
||||
}
|
||||
}
|
||||
|
||||
public getExtensions(): SNActionsExtension[] {
|
||||
const extensionItems = this.itemManager.getItems<SNActionsExtension>(ContentType.ActionsExtension)
|
||||
const excludingListed = extensionItems.filter((extension) => !extension.isListedExtension)
|
||||
@@ -312,7 +326,16 @@ export class SNActionsService extends AbstractService {
|
||||
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) {
|
||||
return item.payload.ejected()
|
||||
}
|
||||
@@ -323,4 +346,15 @@ export class SNActionsService extends AbstractService {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -27,6 +27,7 @@ import { useCommandService } from '@/Components/ApplicationView/CommandProvider'
|
||||
import { SUPER_SHOW_MARKDOWN_PREVIEW } from '@standardnotes/ui-services'
|
||||
import { SuperNoteMarkdownPreview } from './SuperNoteMarkdownPreview'
|
||||
import { ExportPlugin } from './Plugins/ExportPlugin/ExportPlugin'
|
||||
import GetMarkdownPlugin, { GetMarkdownPluginInterface } from './Plugins/GetMarkdownPlugin/GetMarkdownPlugin'
|
||||
import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin'
|
||||
|
||||
const NotePreviewCharLimit = 160
|
||||
@@ -50,6 +51,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
const changeEditorFunction = useRef<ChangeEditorFunction>()
|
||||
const ignoreNextChange = useRef(false)
|
||||
const [showMarkdownPreview, setShowMarkdownPreview] = useState(false)
|
||||
const getMarkdownPlugin = useRef<GetMarkdownPluginInterface | null>(null)
|
||||
|
||||
const commandService = useCommandService()
|
||||
|
||||
@@ -64,6 +66,21 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
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 handleChange = useCallback(
|
||||
@@ -149,6 +166,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<FilePlugin />
|
||||
<ItemBubblePlugin />
|
||||
<BlockPickerMenuPlugin />
|
||||
<GetMarkdownPlugin ref={getMarkdownPlugin} />
|
||||
<DatetimePlugin />
|
||||
<PasswordPlugin />
|
||||
<AutoLinkPlugin />
|
||||
|
||||
Reference in New Issue
Block a user