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>2023-07-19 17:16:28 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-07-19 17:16:28 +0300
commite4384360a16dd9a19d4d2d25d0ef1f2b862ed2a6 (patch)
tree2fcdfa7dcdb9db8f5208b2562f4b4e803d671243 /app/assets/javascripts/content_editor/extensions
parentffda4e7bcac36987f936b4ba515995a6698698f0 (diff)
Add latest changes from gitlab-org/gitlab@16-2-stable-eev16.2.0-rc42
Diffstat (limited to 'app/assets/javascripts/content_editor/extensions')
-rw-r--r--app/assets/javascripts/content_editor/extensions/code_suggestion.js81
-rw-r--r--app/assets/javascripts/content_editor/extensions/comment.js49
-rw-r--r--app/assets/javascripts/content_editor/extensions/copy_paste.js (renamed from app/assets/javascripts/content_editor/extensions/paste_markdown.js)62
-rw-r--r--app/assets/javascripts/content_editor/extensions/hard_break.js4
-rw-r--r--app/assets/javascripts/content_editor/extensions/image.js5
-rw-r--r--app/assets/javascripts/content_editor/extensions/loading.js23
-rw-r--r--app/assets/javascripts/content_editor/extensions/paragraph.js10
-rw-r--r--app/assets/javascripts/content_editor/extensions/reference.js6
8 files changed, 175 insertions, 65 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/code_suggestion.js b/app/assets/javascripts/content_editor/extensions/code_suggestion.js
new file mode 100644
index 00000000000..c70a96769fb
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/code_suggestion.js
@@ -0,0 +1,81 @@
+import { lowlight } from 'lowlight/lib/core';
+import { textblockTypeInputRule } from '@tiptap/core';
+import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants';
+import { memoizedGet } from '../services/utils';
+import CodeBlockHighlight from './code_block_highlight';
+
+const backtickInputRegex = /^```suggestion[\s\n]$/;
+
+export default CodeBlockHighlight.extend({
+ name: 'codeSuggestion',
+
+ isolating: true,
+
+ addOptions() {
+ return {
+ lowlight,
+ config: {},
+ };
+ },
+
+ addAttributes() {
+ return {
+ ...this.parent?.(),
+ language: {
+ default: 'suggestion',
+ },
+ isCodeSuggestion: {
+ default: true,
+ },
+ };
+ },
+
+ addCommands() {
+ const ext = this;
+
+ return {
+ insertCodeSuggestion: (attributes) => async ({ editor }) => {
+ // do not insert a new suggestion if already inside a suggestion
+ if (editor.isActive('codeSuggestion')) return false;
+
+ const rawPath = ext.options.config.diffFile.view_path.replace('/blob/', '/raw/');
+ const allLines = (await memoizedGet(rawPath)).split('\n');
+ const { line } = ext.options.config;
+ let { lines } = ext.options.config;
+
+ if (!lines.length) lines = [line];
+
+ const content = lines.map((l) => allLines[l.new_line - 1]).join('\n');
+ const lineNumbers = `-${lines.length - 1}+0`;
+
+ editor.commands.insertContent({
+ type: 'codeSuggestion',
+ attrs: { langParams: lineNumbers, ...attributes },
+ // empty strings are not allowed in text nodes
+ content: [{ type: 'text', text: content || ' ' }],
+ });
+
+ return true;
+ },
+ };
+ },
+
+ parseHTML() {
+ return [
+ {
+ priority: PARSE_HTML_PRIORITY_HIGHEST,
+ tag: 'pre[lang="suggestion"]',
+ },
+ ];
+ },
+
+ addInputRules() {
+ return [
+ textblockTypeInputRule({
+ find: backtickInputRegex,
+ type: this.type,
+ getAttributes: () => ({ language: 'suggestion', langParams: '-0+0' }),
+ }),
+ ];
+ },
+});
diff --git a/app/assets/javascripts/content_editor/extensions/comment.js b/app/assets/javascripts/content_editor/extensions/comment.js
deleted file mode 100644
index 8e247e552a3..00000000000
--- a/app/assets/javascripts/content_editor/extensions/comment.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Node, textblockTypeInputRule } from '@tiptap/core';
-
-export const commentInputRegex = /^<!--[\s\n]$/;
-
-export default Node.create({
- name: 'comment',
- content: 'text*',
- marks: '',
- group: 'block',
- code: true,
- isolating: true,
- defining: true,
-
- parseHTML() {
- return [
- {
- tag: 'comment',
- preserveWhitespace: 'full',
- getContent(element, schema) {
- const node = schema.node('paragraph', {}, [
- schema.text(
- element.textContent.replace(/&#x([0-9A-F]{2,4});/gi, (_, code) =>
- String.fromCharCode(parseInt(code, 16)),
- ) || ' ',
- ),
- ]);
- return node.content;
- },
- },
- ];
- },
-
- renderHTML() {
- return [
- 'pre',
- { class: 'gl-p-0 gl-border-0 gl-bg-transparent gl-text-gray-300' },
- ['span', { class: 'content-editor-comment' }, 0],
- ];
- },
-
- addInputRules() {
- return [
- textblockTypeInputRule({
- find: commentInputRegex,
- type: this.type,
- }),
- ];
- },
-});
diff --git a/app/assets/javascripts/content_editor/extensions/paste_markdown.js b/app/assets/javascripts/content_editor/extensions/copy_paste.js
index db13438de5e..f484ce98e90 100644
--- a/app/assets/javascripts/content_editor/extensions/paste_markdown.js
+++ b/app/assets/javascripts/content_editor/extensions/copy_paste.js
@@ -2,11 +2,13 @@ import OrderedMap from 'orderedmap';
import { Extension } from '@tiptap/core';
import { Plugin, PluginKey } from '@tiptap/pm/state';
import { Schema, DOMParser as ProseMirrorDOMParser, DOMSerializer } from '@tiptap/pm/model';
+import { uniqueId } from 'lodash';
import { __ } from '~/locale';
import { VARIANT_DANGER } from '~/alert';
import createMarkdownDeserializer from '../services/gl_api_markdown_deserializer';
import { ALERT_EVENT, EXTENSION_PRIORITY_HIGHEST } from '../constants';
import CodeBlockHighlight from './code_block_highlight';
+import CodeSuggestion from './code_suggestion';
import Diagram from './diagram';
import Frontmatter from './frontmatter';
@@ -14,7 +16,12 @@ const TEXT_FORMAT = 'text/plain';
const GFM_FORMAT = 'text/x-gfm';
const HTML_FORMAT = 'text/html';
const VS_CODE_FORMAT = 'vscode-editor-data';
-const CODE_BLOCK_NODE_TYPES = [CodeBlockHighlight.name, Diagram.name, Frontmatter.name];
+const CODE_BLOCK_NODE_TYPES = [
+ CodeBlockHighlight.name,
+ CodeSuggestion.name,
+ Diagram.name,
+ Frontmatter.name,
+];
function parseHTML(schema, html) {
const parser = new DOMParser();
@@ -24,8 +31,23 @@ function parseHTML(schema, html) {
return { document: ProseMirrorDOMParser.fromSchema(schema).parse(body) };
}
+const findLoader = (editor, loaderId) => {
+ let position;
+
+ editor.view.state.doc.descendants((descendant, pos) => {
+ if (descendant.type.name === 'loading' && descendant.attrs.id === loaderId) {
+ position = pos;
+ return false;
+ }
+
+ return true;
+ });
+
+ return position;
+};
+
export default Extension.create({
- name: 'pasteMarkdown',
+ name: 'copyPaste',
priority: EXTENSION_PRIORITY_HIGHEST,
addOptions() {
return {
@@ -35,7 +57,7 @@ export default Extension.create({
},
addCommands() {
return {
- pasteContent: (content = '', processMarkdown = true) => async () => {
+ pasteContent: (content = '', processMarkdown = true) => () => {
const { editor, options } = this;
const { renderMarkdown, eventHub } = options;
const deserializer = createMarkdownDeserializer({ render: renderMarkdown });
@@ -43,23 +65,37 @@ export default Extension.create({
const pasteSchemaSpec = { ...editor.schema.spec };
pasteSchemaSpec.marks = OrderedMap.from(pasteSchemaSpec.marks).remove('span');
pasteSchemaSpec.nodes = OrderedMap.from(pasteSchemaSpec.nodes).remove('div').remove('pre');
- const schema = new Schema(pasteSchemaSpec);
+ const pasteSchema = new Schema(pasteSchemaSpec);
const promise = processMarkdown
- ? deserializer.deserialize({ schema, markdown: content })
- : Promise.resolve(parseHTML(schema, content));
-
- promise
- .then(({ document }) => {
+ ? deserializer.deserialize({ schema: pasteSchema, markdown: content })
+ : Promise.resolve(parseHTML(pasteSchema, content));
+ const loaderId = uniqueId('loading');
+
+ Promise.resolve()
+ .then(() => {
+ editor.commands.insertContent({ type: 'loading', attrs: { id: loaderId } });
+ return promise;
+ })
+ .then(async ({ document }) => {
if (!document) return;
- const { firstChild } = document.content;
+ const pos = findLoader(editor, loaderId);
+ if (!pos) return;
+
+ const { firstChild, childCount } = document.content;
const toPaste =
- document.content.childCount === 1 && firstChild.type.name === 'paragraph'
+ childCount === 1 && firstChild.type.name === 'paragraph'
? firstChild.content
: document.content;
- editor.commands.insertContent(toPaste.toJSON());
+ editor
+ .chain()
+ .deleteRange({ from: pos, to: pos + 1 })
+ .insertContentAt(pos, toPaste.toJSON(), {
+ updateSelection: false,
+ })
+ .run();
})
.catch(() => {
eventHub.$emit(ALERT_EVENT, {
@@ -94,7 +130,7 @@ export default Extension.create({
return [
new Plugin({
- key: new PluginKey('pasteMarkdown'),
+ key: new PluginKey('copyPaste'),
props: {
handleDOMEvents: {
copy: handleCutAndCopy,
diff --git a/app/assets/javascripts/content_editor/extensions/hard_break.js b/app/assets/javascripts/content_editor/extensions/hard_break.js
index fb81c6b79b6..6d7ff92e64b 100644
--- a/app/assets/javascripts/content_editor/extensions/hard_break.js
+++ b/app/assets/javascripts/content_editor/extensions/hard_break.js
@@ -2,8 +2,6 @@ import { HardBreak } from '@tiptap/extension-hard-break';
export default HardBreak.extend({
addKeyboardShortcuts() {
- return {
- 'Shift-Enter': () => this.editor.commands.setHardBreak(),
- };
+ return {};
},
});
diff --git a/app/assets/javascripts/content_editor/extensions/image.js b/app/assets/javascripts/content_editor/extensions/image.js
index 58c16297886..d245b86543f 100644
--- a/app/assets/javascripts/content_editor/extensions/image.js
+++ b/app/assets/javascripts/content_editor/extensions/image.js
@@ -1,5 +1,7 @@
import { Image } from '@tiptap/extension-image';
+import { VueNodeViewRenderer } from '@tiptap/vue-2';
import { PARSE_HTML_PRIORITY_HIGH } from '../constants';
+import ImageWrapper from '../components/wrappers/image.vue';
const resolveImageEl = (element) =>
element.nodeName === 'IMG' ? element : element.querySelector('img');
@@ -97,4 +99,7 @@ export default Image.extend({
},
];
},
+ addNodeView() {
+ return VueNodeViewRenderer(ImageWrapper);
+ },
});
diff --git a/app/assets/javascripts/content_editor/extensions/loading.js b/app/assets/javascripts/content_editor/extensions/loading.js
new file mode 100644
index 00000000000..0115fb10d5d
--- /dev/null
+++ b/app/assets/javascripts/content_editor/extensions/loading.js
@@ -0,0 +1,23 @@
+import { Node } from '@tiptap/core';
+
+export default Node.create({
+ name: 'loading',
+ inline: true,
+ group: 'inline',
+
+ addAttributes() {
+ return {
+ id: {
+ default: null,
+ },
+ };
+ },
+
+ renderHTML() {
+ return [
+ 'span',
+ { class: 'gl-display-inline-flex gl-align-items-center' },
+ ['span', { class: 'gl-dots-loader gl-mx-2' }, ['span']],
+ ];
+ },
+});
diff --git a/app/assets/javascripts/content_editor/extensions/paragraph.js b/app/assets/javascripts/content_editor/extensions/paragraph.js
index c63b64fd784..bddd8b38b06 100644
--- a/app/assets/javascripts/content_editor/extensions/paragraph.js
+++ b/app/assets/javascripts/content_editor/extensions/paragraph.js
@@ -9,4 +9,14 @@ export default Paragraph.extend({
},
};
},
+
+ addKeyboardShortcuts() {
+ return {
+ 'Shift-Enter': async () => {
+ // can only delegate one shortcut to another async
+ await Promise.resolve();
+ this.editor.commands.enter();
+ },
+ };
+ },
});
diff --git a/app/assets/javascripts/content_editor/extensions/reference.js b/app/assets/javascripts/content_editor/extensions/reference.js
index ef69b9bbda6..fd248709b5a 100644
--- a/app/assets/javascripts/content_editor/extensions/reference.js
+++ b/app/assets/javascripts/content_editor/extensions/reference.js
@@ -63,6 +63,12 @@ export default Node.create({
};
},
+ addCommands() {
+ return {
+ insertQuickAction: () => ({ commands }) => commands.insertContent('<p>/</p>'),
+ };
+ },
+
addInputRules() {
const { editor } = this;
const { assetResolver } = this.options;