Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 02:50:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 02:50:22 +0300
commit9dc93a4519d9d5d7be48ff274127136236a3adb3 (patch)
tree70467ae3692a0e35e5ea56bcb803eb512a10bedb /app/assets/javascripts/content_editor
parent4b0f34b6d759d6299322b3a54453e930c6121ff0 (diff)
Add latest changes from gitlab-org/gitlab@13-11-stable-eev13.11.0-rc43
Diffstat (limited to 'app/assets/javascripts/content_editor')
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue18
-rw-r--r--app/assets/javascripts/content_editor/constants.js5
-rw-r--r--app/assets/javascripts/content_editor/extensions/code_block_highlight.js38
-rw-r--r--app/assets/javascripts/content_editor/index.js2
-rw-r--r--app/assets/javascripts/content_editor/services/create_editor.js60
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js73
6 files changed, 196 insertions, 0 deletions
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue
new file mode 100644
index 00000000000..839d4de912d
--- /dev/null
+++ b/app/assets/javascripts/content_editor/components/content_editor.vue
@@ -0,0 +1,18 @@
+<script>
+import { EditorContent } from 'tiptap';
+import createEditor from '../services/create_editor';
+
+export default {
+ components: {
+ EditorContent,
+ },
+ data() {
+ return {
+ editor: createEditor(),
+ };
+ },
+};
+</script>
+<template>
+ <editor-content :editor="editor" />
+</template>
diff --git a/app/assets/javascripts/content_editor/constants.js b/app/assets/javascripts/content_editor/constants.js
new file mode 100644
index 00000000000..eb6deff434d
--- /dev/null
+++ b/app/assets/javascripts/content_editor/constants.js
@@ -0,0 +1,5 @@
+import { s__ } from '~/locale';
+
+export const PROVIDE_SERIALIZER_OR_RENDERER_ERROR = s__(
+ 'ContentEditor|You have to provide a renderMarkdown function or a custom serializer',
+);
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
new file mode 100644
index 00000000000..1d050ed208b
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js
@@ -0,0 +1,38 @@
+import { CodeBlockHighlight as BaseCodeBlockHighlight } from 'tiptap-extensions';
+
+export default class GlCodeBlockHighlight extends BaseCodeBlockHighlight {
+ get schema() {
+ const baseSchema = super.schema;
+
+ return {
+ ...baseSchema,
+ attrs: {
+ params: {
+ default: null,
+ },
+ },
+ parseDOM: [
+ {
+ tag: 'pre',
+ preserveWhitespace: 'full',
+ getAttrs: (node) => {
+ const code = node.querySelector('code');
+
+ if (!code) {
+ return null;
+ }
+
+ return {
+ /* `params` is the name of the attribute that
+ prosemirror-markdown uses to extract the language
+ of a codeblock.
+ https://github.com/ProseMirror/prosemirror-markdown/blob/master/src/to_markdown.js#L62
+ */
+ params: code.getAttribute('lang'),
+ };
+ },
+ },
+ ],
+ };
+ }
+}
diff --git a/app/assets/javascripts/content_editor/index.js b/app/assets/javascripts/content_editor/index.js
new file mode 100644
index 00000000000..e6ef3965da1
--- /dev/null
+++ b/app/assets/javascripts/content_editor/index.js
@@ -0,0 +1,2 @@
+export { default as createEditor } from './services/create_editor';
+export { default as ContentEditor } from './components/content_editor.vue';
diff --git a/app/assets/javascripts/content_editor/services/create_editor.js b/app/assets/javascripts/content_editor/services/create_editor.js
new file mode 100644
index 00000000000..128d332b0a2
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/create_editor.js
@@ -0,0 +1,60 @@
+import { isFunction, isString } from 'lodash';
+import { Editor } from 'tiptap';
+import {
+ Bold,
+ Italic,
+ Code,
+ Link,
+ Image,
+ Heading,
+ Blockquote,
+ HorizontalRule,
+ BulletList,
+ OrderedList,
+ ListItem,
+} from 'tiptap-extensions';
+import { PROVIDE_SERIALIZER_OR_RENDERER_ERROR } from '../constants';
+import CodeBlockHighlight from '../extensions/code_block_highlight';
+import createMarkdownSerializer from './markdown_serializer';
+
+const createEditor = async ({ content, renderMarkdown, serializer: customSerializer } = {}) => {
+ if (!customSerializer && !isFunction(renderMarkdown)) {
+ throw new Error(PROVIDE_SERIALIZER_OR_RENDERER_ERROR);
+ }
+
+ const editor = new Editor({
+ extensions: [
+ new Bold(),
+ new Italic(),
+ new Code(),
+ new Link(),
+ new Image(),
+ new Heading({ levels: [1, 2, 3, 4, 5, 6] }),
+ new Blockquote(),
+ new HorizontalRule(),
+ new BulletList(),
+ new ListItem(),
+ new OrderedList(),
+ new CodeBlockHighlight(),
+ ],
+ });
+ const serializer = customSerializer || createMarkdownSerializer({ render: renderMarkdown });
+
+ editor.setSerializedContent = async (serializedContent) => {
+ editor.setContent(
+ await serializer.deserialize({ schema: editor.schema, content: serializedContent }),
+ );
+ };
+
+ editor.getSerializedContent = () => {
+ return serializer.serialize({ schema: editor.schema, content: editor.getJSON() });
+ };
+
+ if (isString(content)) {
+ await editor.setSerializedContent(content);
+ }
+
+ return editor;
+};
+
+export default createEditor;
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
new file mode 100644
index 00000000000..e3b5775e320
--- /dev/null
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -0,0 +1,73 @@
+import {
+ MarkdownSerializer as ProseMirrorMarkdownSerializer,
+ defaultMarkdownSerializer,
+} from 'prosemirror-markdown';
+import { DOMParser as ProseMirrorDOMParser } from 'prosemirror-model';
+
+const wrapHtmlPayload = (payload) => `<div>${payload}</div>`;
+
+/**
+ * A markdown serializer converts arbitrary Markdown content
+ * into a ProseMirror document and viceversa. To convert Markdown
+ * into a ProseMirror document, the Markdown should be rendered.
+ *
+ * The client should provide a render function to allow flexibility
+ * on the desired rendering approach.
+ *
+ * @param {Function} params.render Render function
+ * that parses the Markdown and converts it into HTML.
+ * @returns a markdown serializer
+ */
+const create = ({ render = () => null }) => {
+ return {
+ /**
+ * Converts a Markdown string into a ProseMirror JSONDocument based
+ * on a ProseMirror schema.
+ * @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
+ */
+ deserialize: async ({ schema, content }) => {
+ const html = await render(content);
+
+ if (!html) {
+ return null;
+ }
+
+ const parser = new DOMParser();
+ const {
+ body: { firstElementChild },
+ } = parser.parseFromString(wrapHtmlPayload(html), 'text/html');
+ const state = ProseMirrorDOMParser.fromSchema(schema).parse(firstElementChild);
+
+ return state.toJSON();
+ },
+
+ /**
+ * Converts a ProseMirror JSONDocument based
+ * on a ProseMirror schema into Markdown
+ * @param {ProseMirror.Schema} params.schema A ProseMirror schema that defines
+ * the types of content supported in the document
+ * @param {String} params.content A ProseMirror JSONDocument
+ * @returns A Markdown string
+ */
+ serialize: ({ schema, content }) => {
+ const document = schema.nodeFromJSON(content);
+ const serializer = new ProseMirrorMarkdownSerializer(defaultMarkdownSerializer.nodes, {
+ ...defaultMarkdownSerializer.marks,
+ bold: {
+ // creates a bold alias for the strong mark converter
+ ...defaultMarkdownSerializer.marks.strong,
+ },
+ italic: { open: '_', close: '_', mixable: true, expelEnclosingWhitespace: true },
+ });
+
+ return serializer.serialize(document, {
+ tightLists: true,
+ });
+ },
+ };
+};
+
+export default create;