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:
|
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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { 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 />
|
||||||
|
|||||||
Reference in New Issue
Block a user