diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-04 15:09:38 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-11-04 15:09:38 +0300 |
commit | f4c0eed6e59f393302ccc38468063cb5c4698f79 (patch) | |
tree | c7b97ac78c073ccc052d611abdd294b6c879a996 /app | |
parent | 88741f7df96173e80dea5e67ea9946e766935849 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
4 files changed, 73 insertions, 25 deletions
diff --git a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js index 8917417e55e..da5ac7eb158 100644 --- a/app/assets/javascripts/content_editor/extensions/code_block_highlight.js +++ b/app/assets/javascripts/content_editor/extensions/code_block_highlight.js @@ -81,4 +81,13 @@ export default CodeBlockLowlight.extend({ addNodeView() { return new VueNodeViewRenderer(CodeBlockWrapper); }, + + addProseMirrorPlugins() { + const parentPlugins = this.parent?.() ?? []; + // We don't want TipTap's VSCode paste plugin to be loaded since + // it conflicts with our CopyPaste plugin. + const i = parentPlugins.findIndex((plugin) => plugin.key.includes('VSCode')); + if (i >= 0) parentPlugins.splice(i, 1); + return parentPlugins; + }, }).configure({ lowlight }); diff --git a/app/assets/javascripts/content_editor/extensions/copy_paste.js b/app/assets/javascripts/content_editor/extensions/copy_paste.js index ab9e5619600..3ae5ef0025d 100644 --- a/app/assets/javascripts/content_editor/extensions/copy_paste.js +++ b/app/assets/javascripts/content_editor/extensions/copy_paste.js @@ -11,6 +11,7 @@ import CodeBlockHighlight from './code_block_highlight'; import CodeSuggestion from './code_suggestion'; import Diagram from './diagram'; import Frontmatter from './frontmatter'; +import { loadingPlugin, findLoader } from './loading'; const TEXT_FORMAT = 'text/plain'; const GFM_FORMAT = 'text/x-gfm'; @@ -31,21 +32,6 @@ 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: 'copyPaste', priority: EXTENSION_PRIORITY_HIGHEST, @@ -74,13 +60,20 @@ export default Extension.create({ Promise.resolve() .then(() => { - editor.commands.insertContent({ type: 'loading', attrs: { id: loaderId } }); + editor + .chain() + .deleteSelection() + .setMeta(loadingPlugin, { + add: { loaderId, pos: editor.state.selection.from }, + }) + .run(); + return promise; }) .then(async ({ document }) => { if (!document) return; - const pos = findLoader(editor, loaderId); + const pos = findLoader(editor.state, loaderId); if (!pos) return; const { firstChild, childCount } = document.content; @@ -91,7 +84,7 @@ export default Extension.create({ editor .chain() - .deleteRange({ from: pos, to: pos + 1 }) + .setMeta(loadingPlugin, { remove: { loaderId } }) .insertContentAt(pos, toPaste.toJSON(), { updateSelection: false, }) @@ -114,6 +107,7 @@ export default Extension.create({ const handleCutAndCopy = (view, event) => { const slice = view.state.selection.content(); const gfmContent = this.options.serializer.serialize({ doc: slice.content }); + const documentFragment = DOMSerializer.fromSchema(view.state.schema).serializeFragment( slice.content, ); diff --git a/app/assets/javascripts/content_editor/extensions/html_marks.js b/app/assets/javascripts/content_editor/extensions/html_marks.js index 79fc0eea2c7..58fa2655e25 100644 --- a/app/assets/javascripts/content_editor/extensions/html_marks.js +++ b/app/assets/javascripts/content_editor/extensions/html_marks.js @@ -50,7 +50,8 @@ export default marks.map((name) => }, parseHTML() { - return [{ tag: name, priority: PARSE_HTML_PRIORITY_LOWEST }]; + const tag = name === 'span' ? `${name}:not([data-escaped-char])` : name; + return [{ tag, priority: PARSE_HTML_PRIORITY_LOWEST }]; }, renderHTML({ HTMLAttributes }) { diff --git a/app/assets/javascripts/content_editor/extensions/loading.js b/app/assets/javascripts/content_editor/extensions/loading.js index 0115fb10d5d..942ac650925 100644 --- a/app/assets/javascripts/content_editor/extensions/loading.js +++ b/app/assets/javascripts/content_editor/extensions/loading.js @@ -1,4 +1,52 @@ import { Node } from '@tiptap/core'; +import { Decoration, DecorationSet } from '@tiptap/pm/view'; +import { Plugin } from '@tiptap/pm/state'; + +const createDotsLoader = () => { + const root = document.createElement('span'); + root.classList.add('gl-display-inline-flex', 'gl-align-items-center'); + root.innerHTML = '<span class="gl-dots-loader gl-mx-2"><span></span></span>'; + return root; +}; + +export const loadingPlugin = new Plugin({ + state: { + init() { + return DecorationSet.empty; + }, + apply(tr, set) { + let transformedSet = set.map(tr.mapping, tr.doc); + const action = tr.getMeta(this); + + if (action?.add) { + const deco = Decoration.widget(action.add.pos, createDotsLoader(), { + id: action.add.loaderId, + side: -1, + }); + transformedSet = transformedSet.add(tr.doc, [deco]); + } else if (action?.remove) { + transformedSet = transformedSet.remove( + transformedSet.find(null, null, (spec) => spec.id === action.remove.loaderId), + ); + } + return transformedSet; + }, + }, + props: { + decorations(state) { + return this.getState(state); + }, + }, +}); + +export const findLoader = (state, loaderId) => { + const decos = loadingPlugin.getState(state); + const found = decos.find(null, null, (spec) => spec.id === loaderId); + + return found.length ? found[0].from : null; +}; + +export const findAllLoaders = (state) => loadingPlugin.getState(state).find(); export default Node.create({ name: 'loading', @@ -13,11 +61,7 @@ export default Node.create({ }; }, - renderHTML() { - return [ - 'span', - { class: 'gl-display-inline-flex gl-align-items-center' }, - ['span', { class: 'gl-dots-loader gl-mx-2' }, ['span']], - ]; + addProseMirrorPlugins() { + return [loadingPlugin]; }, }); |