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>2022-05-11 03:08:02 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-05-11 03:08:02 +0300
commitcb2494484e33a0d3c750625908e8b4dda69ab7b4 (patch)
treed3a4748bdb44b5ad7c1952c508dbb18230c45a7b /app/assets/javascripts/content_editor/services
parent8cd9a013792172f8d64eba8f327e2dadd71efffb (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor/services')
-rw-r--r--app/assets/javascripts/content_editor/services/content_editor.js17
-rw-r--r--app/assets/javascripts/content_editor/services/create_content_editor.js2
-rw-r--r--app/assets/javascripts/content_editor/services/markdown_serializer.js87
-rw-r--r--app/assets/javascripts/content_editor/services/serialization_helpers.js14
4 files changed, 88 insertions, 32 deletions
diff --git a/app/assets/javascripts/content_editor/services/content_editor.js b/app/assets/javascripts/content_editor/services/content_editor.js
index 6eac5a63439..52dacb84153 100644
--- a/app/assets/javascripts/content_editor/services/content_editor.js
+++ b/app/assets/javascripts/content_editor/services/content_editor.js
@@ -9,6 +9,7 @@ export class ContentEditor {
this._deserializer = deserializer;
this._eventHub = eventHub;
this._assetResolver = assetResolver;
+ this._pristineDoc = null;
}
get tiptapEditor() {
@@ -19,6 +20,10 @@ export class ContentEditor {
return this._eventHub;
}
+ get changed() {
+ return this._pristineDoc?.eq(this.tiptapEditor.state.doc);
+ }
+
get empty() {
const doc = this.tiptapEditor?.state.doc;
@@ -54,11 +59,12 @@ export class ContentEditor {
try {
eventHub.$emit(LOADING_CONTENT_EVENT);
- const result = await this.deserialize(serializedContent);
+ const { document } = await this.deserialize(serializedContent);
- if (Object.keys(result).length !== 0) {
+ if (document) {
+ this._pristineDoc = document;
tr.setSelection(selection)
- .replaceSelectionWith(result.document, false)
+ .replaceSelectionWith(document, false)
.setMeta('preventUpdate', true);
editor.view.dispatch(tr);
}
@@ -71,8 +77,9 @@ export class ContentEditor {
}
getSerializedContent() {
- const { _tiptapEditor: editor, _serializer: serializer } = this;
+ const { _tiptapEditor: editor, _serializer: serializer, _pristineDoc: pristineDoc } = this;
+ const { doc } = editor.state;
- return serializer.serialize({ schema: editor.schema, content: editor.getJSON() });
+ return serializer.serialize({ doc, pristineDoc });
}
}
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 db76073510e..15aac3d86e5 100644
--- a/app/assets/javascripts/content_editor/services/create_content_editor.js
+++ b/app/assets/javascripts/content_editor/services/create_content_editor.js
@@ -43,6 +43,7 @@ import OrderedList from '../extensions/ordered_list';
import Paragraph from '../extensions/paragraph';
import PasteMarkdown from '../extensions/paste_markdown';
import Reference from '../extensions/reference';
+import Sourcemap from '../extensions/sourcemap';
import Strike from '../extensions/strike';
import Subscript from '../extensions/subscript';
import Superscript from '../extensions/superscript';
@@ -128,6 +129,7 @@ export const createContentEditor = ({
Paragraph,
PasteMarkdown.configure({ renderMarkdown, eventHub }),
Reference,
+ Sourcemap,
Strike,
Subscript,
Superscript,
diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js
index c2be7bc9195..6c8c562af8e 100644
--- a/app/assets/javascripts/content_editor/services/markdown_serializer.js
+++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js
@@ -61,6 +61,7 @@ import {
renderPlayable,
renderHTMLNode,
renderContent,
+ preserveUnchanged,
} from './serialization_helpers';
const defaultSerializerConfig = {
@@ -119,7 +120,7 @@ const defaultSerializerConfig = {
nodes: {
[Audio.name]: renderPlayable,
- [Blockquote.name]: (state, node) => {
+ [Blockquote.name]: preserveUnchanged((state, node) => {
if (node.attrs.multiline) {
state.write('>>>');
state.ensureNewLine();
@@ -130,9 +131,9 @@ const defaultSerializerConfig = {
} else {
state.wrapBlock('> ', null, node, () => state.renderContent(node));
}
- },
- [BulletList.name]: defaultMarkdownSerializer.nodes.bullet_list,
- [CodeBlockHighlight.name]: renderCodeBlock,
+ }),
+ [BulletList.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.bullet_list),
+ [CodeBlockHighlight.name]: preserveUnchanged(renderCodeBlock),
[Diagram.name]: renderCodeBlock,
[Division.name]: (state, node) => {
if (node.attrs.className?.includes('js-markdown-code')) {
@@ -189,13 +190,13 @@ const defaultSerializerConfig = {
},
[Figure.name]: renderHTMLNode('figure'),
[FigureCaption.name]: renderHTMLNode('figcaption'),
- [HardBreak.name]: renderHardBreak,
- [Heading.name]: defaultMarkdownSerializer.nodes.heading,
- [HorizontalRule.name]: defaultMarkdownSerializer.nodes.horizontal_rule,
- [Image.name]: renderImage,
- [ListItem.name]: defaultMarkdownSerializer.nodes.list_item,
- [OrderedList.name]: renderOrderedList,
- [Paragraph.name]: defaultMarkdownSerializer.nodes.paragraph,
+ [HardBreak.name]: preserveUnchanged(renderHardBreak),
+ [Heading.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.heading),
+ [HorizontalRule.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.horizontal_rule),
+ [Image.name]: preserveUnchanged(renderImage),
+ [ListItem.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.list_item),
+ [OrderedList.name]: preserveUnchanged(renderOrderedList),
+ [Paragraph.name]: preserveUnchanged(defaultMarkdownSerializer.nodes.paragraph),
[Reference.name]: (state, node) => {
state.write(node.attrs.originalText || node.attrs.text);
},
@@ -221,29 +222,60 @@ const defaultSerializerConfig = {
},
};
+const createChangeTracker = (doc, pristineDoc) => {
+ const changeTracker = new WeakMap();
+ const pristineSourceMarkdownMap = new Map();
+
+ if (doc && pristineDoc) {
+ pristineDoc.descendants((node) => {
+ if (node.attrs.sourceMapKey) {
+ pristineSourceMarkdownMap.set(`${node.attrs.sourceMapKey}${node.type.name}`, node);
+ }
+ });
+ doc.descendants((node) => {
+ const pristineNode = pristineSourceMarkdownMap.get(
+ `${node.attrs.sourceMapKey}${node.type.name}`,
+ );
+
+ if (pristineNode) {
+ changeTracker.set(node, node.eq(pristineNode));
+ }
+ });
+ }
+
+ return changeTracker;
+};
+
/**
- * 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.
+ * Converts a ProseMirror document to Markdown. See the
+ * following documentation to learn how to implement
+ * custom node and mark serializer functions.
+ *
+ * https://github.com/prosemirror/prosemirror-markdown
*
- * The client should provide a render function to allow flexibility
- * on the desired rendering approach.
+ * @param {Object} params.nodes ProseMirror node serializer functions
+ * @param {Object} params.marks ProseMirror marks serializer config
*
- * @param {Function} params.render Render function
- * that parses the Markdown and converts it into HTML.
* @returns a markdown serializer
*/
export default ({ serializerConfig = {} } = {}) => ({
/**
- * 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
+ * Serializes a ProseMirror document as Markdown. If a node contains
+ * sourcemap metadata, the serializer is capable of restoring the
+ * Markdown from which the node was generated using a Markdown
+ * deserializer.
+ *
+ * See the Sourcemap metadata extension and the remark_markdown_deserializer
+ * service for more information.
+ *
+ * @param {ProseMirror.Node} params.doc ProseMirror document to convert into Markdown
+ * @param {ProseMirror.Node} params.pristineDoc Pristine version of the document that
+ * should be converted into Markdown. This is used to detect which nodes in the document
+ * changed.
+ * @returns A String that represents the serialized document as Markdown
*/
- serialize: ({ schema, content }) => {
- const proseMirrorDocument = schema.nodeFromJSON(content);
+ serialize: ({ doc, pristineDoc }) => {
+ const changeTracker = createChangeTracker(doc, pristineDoc);
const serializer = new ProseMirrorMarkdownSerializer(
{
...defaultSerializerConfig.nodes,
@@ -255,8 +287,9 @@ export default ({ serializerConfig = {} } = {}) => ({
},
);
- return serializer.serialize(proseMirrorDocument, {
+ return serializer.serialize(doc, {
tightLists: true,
+ changeTracker,
});
},
});
diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js
index 3e48434c6f9..99aaee8f312 100644
--- a/app/assets/javascripts/content_editor/services/serialization_helpers.js
+++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js
@@ -349,3 +349,17 @@ export function renderCodeBlock(state, node) {
state.write('```');
state.closeBlock(node);
}
+
+export function preserveUnchanged(render) {
+ return (state, node, parent, index) => {
+ const { sourceMarkdown } = node.attrs;
+ const same = state.options.changeTracker.get(node);
+
+ if (same) {
+ state.write(sourceMarkdown);
+ state.closeBlock(node);
+ } else {
+ render(state, node, parent, index);
+ }
+ };
+}