mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
fix(editor): preserve markdown on clipboard copy
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { Editor } from '@tiptap/core'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { encodeRawMarkdownHtmlForRichEditor } from './raw-markdown-html'
|
||||
import { createRichMarkdownExtensions } from './rich-markdown-extensions'
|
||||
import { serializeRichMarkdownSliceAsMarkdown } from './rich-markdown-clipboard-serialization'
|
||||
import { createRichMarkdownEditorCodec } from './rich-markdown-source-transport'
|
||||
|
||||
describe('rich Markdown clipboard serialization', () => {
|
||||
it('serializes a selected slice as Markdown source', () => {
|
||||
const codec = createRichMarkdownEditorCodec()
|
||||
const editor = new Editor({
|
||||
element: null,
|
||||
extensions: createRichMarkdownExtensions({ codec }),
|
||||
content: encodeRawMarkdownHtmlForRichEditor(
|
||||
'# Heading\n\nA **bold** paragraph with a [link](https://example.com).\n',
|
||||
codec
|
||||
),
|
||||
contentType: 'markdown'
|
||||
})
|
||||
try {
|
||||
const slice = editor.state.doc.slice(0, editor.state.doc.content.size)
|
||||
const markdown = serializeRichMarkdownSliceAsMarkdown(slice, (content) =>
|
||||
editor.markdown!.serialize(content)
|
||||
)
|
||||
expect(markdown).toContain('# Heading')
|
||||
expect(markdown).toContain('**bold**')
|
||||
expect(markdown).toContain('[link](https://example.com)')
|
||||
expect(markdown).not.toContain('\n\n\n')
|
||||
} finally {
|
||||
editor.destroy()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,9 @@
|
||||
import { DOMSerializer, type Slice } from '@tiptap/pm/model'
|
||||
import type { JSONContent } from '@tiptap/core'
|
||||
import type { EditorView } from '@tiptap/pm/view'
|
||||
|
||||
export type MarkdownSliceSerializer = (content: JSONContent | JSONContent[]) => string
|
||||
|
||||
export function serializeRichMarkdownSliceForClipboard(
|
||||
view: EditorView,
|
||||
slice: Slice
|
||||
@@ -15,3 +18,11 @@ export function serializeRichMarkdownSliceForClipboard(
|
||||
container.appendChild(fragment)
|
||||
return { html: container.innerHTML }
|
||||
}
|
||||
|
||||
export function serializeRichMarkdownSliceAsMarkdown(
|
||||
slice: Slice,
|
||||
serialize: MarkdownSliceSerializer
|
||||
): string {
|
||||
const content = slice.content.toJSON()
|
||||
return Array.isArray(content) ? serialize(content) : ''
|
||||
}
|
||||
|
||||
@@ -21,14 +21,18 @@ export function writeRichMarkdownSliceToClipboard(
|
||||
return false
|
||||
}
|
||||
const serialized = serializeRichMarkdownSliceForClipboard(view, slice)
|
||||
const markdownText =
|
||||
(typeof view.someProp === 'function'
|
||||
? view.someProp('clipboardTextSerializer', (serializer) => serializer(slice, view))
|
||||
: undefined) || visibleText
|
||||
clipboardData.setData('text/html', serialized.html)
|
||||
clipboardData.setData('text/plain', visibleText)
|
||||
clipboardData.setData('text/plain', markdownText)
|
||||
// Why: if the clipboard rejected the write we must not delete, and we must
|
||||
// surface the same cut-limit feedback so the no-op is not silent.
|
||||
if (
|
||||
typeof clipboardData.getData === 'function' &&
|
||||
(clipboardData.getData('text/html') !== serialized.html ||
|
||||
clipboardData.getData('text/plain') !== visibleText)
|
||||
clipboardData.getData('text/plain') !== markdownText)
|
||||
) {
|
||||
showRichMarkdownSourceOwningCutLimitError()
|
||||
return false
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
getRichMarkdownSelectionLinkBubble,
|
||||
openSelectedHtmlSuperscriptLink
|
||||
} from './rich-markdown-selected-link-actions'
|
||||
import { serializeRichMarkdownSliceAsMarkdown } from './rich-markdown-clipboard-serialization'
|
||||
|
||||
export type EditorConfigParams = {
|
||||
codec: RichMarkdownEditorCodec
|
||||
@@ -138,6 +139,12 @@ export function createRichMarkdownEditorConfig(params: EditorConfigParams): UseE
|
||||
class: 'rich-markdown-editor',
|
||||
spellcheck: getRichMarkdownSpellcheckAttribute(richMarkdownSpellcheckEnabled)
|
||||
},
|
||||
clipboardTextSerializer: (slice) => {
|
||||
const markdown = editorRef.current?.markdown
|
||||
return markdown
|
||||
? serializeRichMarkdownSliceAsMarkdown(slice, (content) => markdown.serialize(content))
|
||||
: ''
|
||||
},
|
||||
handleDOMEvents: {
|
||||
cut: handleRichMarkdownCut
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user