diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/paste_markdown.js index e50732bd869478882baf6f805cab0574dc4b29ec..f87e4d8d1dda2f699bca9ffd819522b8c310310e 100644 --- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js +++ b/app/assets/javascripts/content_editor/extensions/paste_markdown.js @@ -2,7 +2,7 @@ import { Extension } from '@tiptap/core'; import { Plugin, PluginKey } from 'prosemirror-state'; import { __ } from '~/locale'; import { VARIANT_DANGER } from '~/flash'; -import createMarkdownDeserializer from '../services/markdown_deserializer'; +import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer'; import { ALERT_EVENT, LOADING_CONTENT_EVENT, diff --git a/app/assets/javascripts/content_editor/services/code_block_language_loader.js b/app/assets/javascripts/content_editor/services/code_block_language_loader.js index c967dd899de246e3189fe2bc7dcbd61ecd4cb663..74018d7e1e305ae6fbbad72d5c8b31a6cd8847dc 100644 --- a/app/assets/javascripts/content_editor/services/code_block_language_loader.js +++ b/app/assets/javascripts/content_editor/services/code_block_language_loader.js @@ -36,16 +36,6 @@ const codeBlockLanguageLoader = { return this.lowlight.registered(language); }, - loadLanguagesFromDOM(domTree) { - const languages = []; - - domTree.querySelectorAll('pre').forEach((preElement) => { - languages.push(preElement.getAttribute('lang')); - }); - - return this.loadLanguages(languages); - }, - loadLanguageFromInputRule(match) { const { syntax } = this.findLanguageBySyntax(match[1]); diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js index 56badf965ee9a2425b4b689830461867c2e0c59a..21843c482a8c13c5f59740620ba51e9bd7dd46db 100644 --- a/app/assets/javascripts/content_editor/services/content_editor.js +++ b/app/assets/javascripts/content_editor/services/content_editor.js @@ -52,9 +52,9 @@ export class ContentEditor { }); if (Object.keys(result).length !== 0) { - const { document, dom } = result; + const { document, languages } = result; - await languageLoader.loadLanguagesFromDOM(dom); + await languageLoader.loadLanguages(languages); tr.setSelection(selection) .replaceSelectionWith(document, false) diff --git a/app/assets/javascripts/content_editor/services/create_content_editor.js b/app/assets/javascripts/content_editor/services/create_content_editor.js index af19a0ab0e4558c7c0c1ec59498b1be72a099a1b..a7e6bb8d5a20fc3d45de5e9154e72ed212c34932 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -58,7 +58,7 @@ import Video from '../extensions/video'; import WordBreak from '../extensions/word_break'; import { ContentEditor } from './content_editor'; import createMarkdownSerializer from './markdown_serializer'; -import createMarkdownDeserializer from './markdown_deserializer'; +import createGlApiMarkdownDeserializer from './gl_api_markdown_deserializer'; import trackInputRulesAndShortcuts from './track_input_rules_and_shortcuts'; import languageLoader from './code_block_language_loader'; @@ -146,7 +146,7 @@ export const createContentEditor = ({ const trackedExtensions = allExtensions.map(trackInputRulesAndShortcuts); const tiptapEditor = createTiptapEditor({ extensions: trackedExtensions, ...tiptapOptions }); const serializer = createMarkdownSerializer({ serializerConfig }); - const deserializer = createMarkdownDeserializer({ render: renderMarkdown }); + const deserializer = createGlApiMarkdownDeserializer({ render: renderMarkdown }); return new ContentEditor({ tiptapEditor, serializer, eventHub, deserializer, languageLoader }); }; diff --git a/app/assets/javascripts/content_editor/services/markdown_deserializer.js b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js similarity index 87% rename from app/assets/javascripts/content_editor/services/markdown_deserializer.js rename to app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js index cd4863d8eacac6ab7f83d1edcb8a67d648de8475..3742d14bfd152c004d20b29027b18870b8c34068 100644 --- a/app/assets/javascripts/content_editor/services/markdown_deserializer.js +++ b/app/assets/javascripts/content_editor/services/gl_api_markdown_deserializer.js @@ -18,6 +18,7 @@ export default ({ render }) => { return { deserialize: async ({ schema, content }) => { const html = await render(content); + const languages = []; if (!html) return {}; @@ -27,7 +28,11 @@ export default ({ render }) => { // append original source as a comment that nodes can access body.append(document.createComment(content)); - return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), dom: body }; + body.querySelectorAll('pre').forEach((preElement) => { + languages.push(preElement.getAttribute('lang')); + }); + + return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body), languages }; }, }; }; diff --git a/spec/frontend/content_editor/services/code_block_language_loader_spec.js b/spec/frontend/content_editor/services/code_block_language_loader_spec.js index 905c1685b94d17c9cdb2f62ab36b96312266ef32..55f4e0ed4e3b256269b3309be544833042fc20a4 100644 --- a/spec/frontend/content_editor/services/code_block_language_loader_spec.js +++ b/spec/frontend/content_editor/services/code_block_language_loader_spec.js @@ -76,24 +76,6 @@ describe('content_editor/services/code_block_language_loader', () => { }); }); - describe('loadLanguagesFromDOM', () => { - it('loads highlight.js language packages identified by pre tags in a DOM fragment', async () => { - const parser = new DOMParser(); - const { body } = parser.parseFromString( - ` - <pre lang="javascript"></pre> - <pre lang="ruby"></pre> - `, - 'text/html', - ); - - await languageLoader.loadLanguagesFromDOM(body); - - expect(lowlight.registerLanguage).toHaveBeenCalledWith('javascript', expect.any(Function)); - expect(lowlight.registerLanguage).toHaveBeenCalledWith('ruby', expect.any(Function)); - }); - }); - describe('loadLanguageFromInputRule', () => { it('loads highlight.js language packages identified from the input rule', async () => { const match = new RegExp(backtickInputRegex).exec('```js '); diff --git a/spec/frontend/content_editor/services/content_editor_spec.js b/spec/frontend/content_editor/services/content_editor_spec.js index 5b7a27b501dbd73f8c435281da8122db06d3b71a..18c5d89e391733107e983c08cbac3e9d100e4e04 100644 --- a/spec/frontend/content_editor/services/content_editor_spec.js +++ b/spec/frontend/content_editor/services/content_editor_spec.js @@ -28,7 +28,7 @@ describe('content_editor/services/content_editor', () => { serializer = { deserialize: jest.fn() }; deserializer = { deserialize: jest.fn() }; - languageLoader = { loadLanguagesFromDOM: jest.fn() }; + languageLoader = { loadLanguages: jest.fn() }; eventHub = eventHubFactory(); contentEditor = new ContentEditor({ tiptapEditor, @@ -51,12 +51,12 @@ describe('content_editor/services/content_editor', () => { describe('when setSerializedContent succeeds', () => { let document; - const dom = {}; + const languages = ['javascript']; const testMarkdown = '**bold text**'; beforeEach(() => { document = doc(p('document')); - deserializer.deserialize.mockResolvedValueOnce({ document, dom }); + deserializer.deserialize.mockResolvedValueOnce({ document, languages }); }); it('emits loadingContent and loadingSuccess event in the eventHub', () => { @@ -81,7 +81,7 @@ describe('content_editor/services/content_editor', () => { it('passes deserialized DOM document to language loader', async () => { await contentEditor.setSerializedContent(testMarkdown); - expect(languageLoader.loadLanguagesFromDOM).toHaveBeenCalledWith(dom); + expect(languageLoader.loadLanguages).toHaveBeenCalledWith(languages); }); }); diff --git a/spec/frontend/content_editor/services/markdown_deserializer_spec.js b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js similarity index 74% rename from spec/frontend/content_editor/services/markdown_deserializer_spec.js rename to spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js index bea43a0effcc4b15e69a32dd55e03d0852fa4f52..e399c1f6beada1f6affa55805123a04cd84b402d 100644 --- a/spec/frontend/content_editor/services/markdown_deserializer_spec.js +++ b/spec/frontend/content_editor/services/gl_api_markdown_deserializer_spec.js @@ -1,8 +1,8 @@ -import createMarkdownDeserializer from '~/content_editor/services/markdown_deserializer'; +import createMarkdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer'; import Bold from '~/content_editor/extensions/bold'; import { createTestEditor, createDocBuilder } from '../test_utils'; -describe('content_editor/services/markdown_deserializer', () => { +describe('content_editor/services/gl_api_markdown_deserializer', () => { let renderMarkdown; let doc; let p; @@ -32,7 +32,9 @@ describe('content_editor/services/markdown_deserializer', () => { beforeEach(async () => { const deserializer = createMarkdownDeserializer({ render: renderMarkdown }); - renderMarkdown.mockResolvedValueOnce(`<p><strong>${text}</strong></p>`); + renderMarkdown.mockResolvedValueOnce( + `<p><strong>${text}</strong></p><pre lang="javascript"></pre>`, + ); result = await deserializer.deserialize({ content: 'content', @@ -40,13 +42,13 @@ describe('content_editor/services/markdown_deserializer', () => { }); }); it('transforms HTML returned by render function to a ProseMirror document', async () => { - const expectedDoc = doc(p(bold(text))); + const document = doc(p(bold(text))); - expect(result.document.toJSON()).toEqual(expectedDoc.toJSON()); + expect(result.document.toJSON()).toEqual(document.toJSON()); }); - it('returns parsed HTML as a DOM object', () => { - expect(result.dom.innerHTML).toEqual(`<p><strong>${text}</strong></p><!--content-->`); + it('returns languages of code blocks found in the document', () => { + expect(result.languages).toEqual(['javascript']); }); }); diff --git a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js index abd9588daff3e44bc692ff49646dfdc11f2d9081..8a304c7316389aaabb0d45cc6bf8e99897258869 100644 --- a/spec/frontend/content_editor/services/markdown_sourcemap_spec.js +++ b/spec/frontend/content_editor/services/markdown_sourcemap_spec.js @@ -2,7 +2,7 @@ import { Extension } from '@tiptap/core'; import BulletList from '~/content_editor/extensions/bullet_list'; import ListItem from '~/content_editor/extensions/list_item'; import Paragraph from '~/content_editor/extensions/paragraph'; -import markdownDeserializer from '~/content_editor/services/markdown_deserializer'; +import markdownDeserializer from '~/content_editor/services/gl_api_markdown_deserializer'; import { getMarkdownSource, getFullSource } from '~/content_editor/services/markdown_sourcemap'; import { createTestEditor, createDocBuilder } from '../test_utils';