Commit ca64c46a authored by Frédéric Caplette's avatar Frédéric Caplette

Merge branch 'return-dom-fragment-markdown-deserializer' into 'master'

Return DOM fragment in Markdown Serializer

See merge request gitlab-org/gitlab!82733
parents 077339d3 3107464d
......@@ -34,15 +34,15 @@ export default Extension.create({
deserializer
.deserialize({ schema: editor.schema, content: markdown })
.then((doc) => {
if (!doc) {
.then(({ document }) => {
if (!document) {
return;
}
const { state, view } = editor;
const { tr, selection } = state;
tr.replaceWith(selection.from - 1, selection.to, doc.content);
tr.replaceWith(selection.from - 1, selection.to, document.content);
view.dispatch(tr);
eventHub.$emit(LOADING_SUCCESS_EVENT);
})
......
......@@ -40,13 +40,14 @@ export class ContentEditor {
try {
eventHub.$emit(LOADING_CONTENT_EVENT);
const newDoc = await deserializer.deserialize({
const { document } = await deserializer.deserialize({
schema: editor.schema,
content: serializedContent,
});
if (newDoc) {
if (document) {
tr.setSelection(selection)
.replaceSelectionWith(newDoc, false)
.replaceSelectionWith(document, false)
.setMeta('preventUpdate', true);
editor.view.dispatch(tr);
}
......
......@@ -4,16 +4,22 @@ export default ({ render }) => {
/**
* Converts a Markdown string into a ProseMirror JSONDocument based
* on a ProseMirror schema.
*
* @param {Object} options — The schema and content for deserialization
* @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines
* the types of content supported in the document
* @param {String} params.content An arbitrary markdown string
* @returns A ProseMirror JSONDocument
*
* @returns An object with the following properties:
* - document: A ProseMirror document object generated from the deserialized Markdown
* - dom: The Markdown Deserializer renders Markdown as HTML to generate the ProseMirror
* document. The dom property contains the HTML generated from the Markdown Source.
*/
return {
deserialize: async ({ schema, content }) => {
const html = await render(content);
if (!html) return null;
if (!html) return {};
const parser = new DOMParser();
const { body } = parser.parseFromString(html, 'text/html');
......@@ -21,7 +27,7 @@ export default ({ render }) => {
// append original source as a comment that nodes can access
body.append(document.createComment(content));
return ProseMirrorDOMParser.fromSchema(schema).parse(body);
return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), dom: body };
},
};
};
......@@ -5,18 +5,26 @@ import {
} from '~/content_editor/constants';
import { ContentEditor } from '~/content_editor/services/content_editor';
import eventHubFactory from '~/helpers/event_hub_factory';
import { createTestEditor } from '../test_utils';
import { createTestEditor, createDocBuilder } from '../test_utils';
describe('content_editor/services/content_editor', () => {
let contentEditor;
let serializer;
let deserializer;
let eventHub;
let doc;
let p;
beforeEach(() => {
const tiptapEditor = createTestEditor();
jest.spyOn(tiptapEditor, 'destroy');
({
builders: { doc, p },
} = createDocBuilder({
tiptapEditor,
}));
serializer = { deserialize: jest.fn() };
deserializer = { deserialize: jest.fn() };
eventHub = eventHubFactory();
......@@ -34,8 +42,11 @@ describe('content_editor/services/content_editor', () => {
});
describe('when setSerializedContent succeeds', () => {
let document;
beforeEach(() => {
deserializer.deserialize.mockResolvedValueOnce('');
document = doc(p('document'));
deserializer.deserialize.mockResolvedValueOnce({ document });
});
it('emits loadingContent and loadingSuccess event in the eventHub', () => {
......@@ -50,6 +61,12 @@ describe('content_editor/services/content_editor', () => {
contentEditor.setSerializedContent('**bold text**');
});
it('sets the deserialized document in the tiptap editor object', async () => {
await contentEditor.setSerializedContent('**bold text**');
expect(contentEditor.tiptapEditor.state.doc.toJSON()).toEqual(document.toJSON());
});
});
describe('when setSerializedContent fails', () => {
......
......@@ -25,27 +25,38 @@ describe('content_editor/services/markdown_deserializer', () => {
renderMarkdown = jest.fn();
});
it('transforms HTML returned by render function to a ProseMirror document', async () => {
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
const expectedDoc = doc(p(bold('Bold text')));
describe('when deserializing', () => {
let result;
const text = 'Bold text';
renderMarkdown.mockResolvedValueOnce('<p><strong>Bold text</strong></p>');
beforeEach(async () => {
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
renderMarkdown.mockResolvedValueOnce(`<p><strong>${text}</strong></p>`);
const result = await deserializer.deserialize({
content: 'content',
schema: tiptapEditor.schema,
result = await deserializer.deserialize({
content: 'content',
schema: tiptapEditor.schema,
});
});
it('transforms HTML returned by render function to a ProseMirror document', async () => {
const expectedDoc = doc(p(bold(text)));
expect(result.toJSON()).toEqual(expectedDoc.toJSON());
expect(result.document.toJSON()).toEqual(expectedDoc.toJSON());
});
it('returns parsed HTML as a DOM object', () => {
expect(result.dom.innerHTML).toEqual(`<p><strong>${text}</strong></p><!--content-->`);
});
});
describe('when the render function returns an empty value', () => {
it('also returns null', async () => {
it('returns an empty object', async () => {
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
renderMarkdown.mockResolvedValueOnce(null);
expect(await deserializer.deserialize({ content: 'content' })).toBe(null);
expect(await deserializer.deserialize({ content: 'content' })).toEqual({});
});
});
});
......@@ -73,7 +73,7 @@ describe('content_editor/services/markdown_sourcemap', () => {
});
it('gets markdown source for a rendered HTML element', async () => {
const deserialized = await markdownDeserializer({
const { document } = await markdownDeserializer({
render: () => BULLET_LIST_HTML,
}).deserialize({
schema: tiptapEditor.schema,
......@@ -95,6 +95,6 @@ describe('content_editor/services/markdown_sourcemap', () => {
),
);
expect(deserialized.toJSON()).toEqual(expected.toJSON());
expect(document.toJSON()).toEqual(expected.toJSON());
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment