diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-17 18:10:28 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-17 18:10:28 +0300 |
commit | a331169e6e84f93fd9b841b56465ac113b6d03f9 (patch) | |
tree | 1e823b05f484f55a5f7663b9aa580b64f6c6e0fd /app/assets/javascripts/content_editor | |
parent | c609c898ff7f42c3d72a1c6d619e1ff786f5e18a (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
13 files changed, 112 insertions, 68 deletions
diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 02ab34447ca..a8405fe37c7 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -3,7 +3,7 @@ import { GlLoadingIcon } from '@gitlab/ui'; import { EditorContent as TiptapEditorContent } from '@tiptap/vue-2'; import { LOADING_CONTENT_EVENT, LOADING_SUCCESS_EVENT, LOADING_ERROR_EVENT } from '../constants'; import { createContentEditor } from '../services/create_content_editor'; -import ContentEditorError from './content_editor_error.vue'; +import ContentEditorAlert from './content_editor_alert.vue'; import ContentEditorProvider from './content_editor_provider.vue'; import EditorStateObserver from './editor_state_observer.vue'; import FormattingBubbleMenu from './formatting_bubble_menu.vue'; @@ -12,7 +12,7 @@ import TopToolbar from './top_toolbar.vue'; export default { components: { GlLoadingIcon, - ContentEditorError, + ContentEditorAlert, ContentEditorProvider, TiptapEditorContent, TopToolbar, @@ -92,7 +92,7 @@ export default { <content-editor-provider :content-editor="contentEditor"> <div> <editor-state-observer @docUpdate="notifyChange" @focus="focus" @blur="blur" /> - <content-editor-error /> + <content-editor-alert /> <div data-testid="content-editor" data-qa-selector="content_editor_container" diff --git a/app/assets/javascripts/content_editor/components/content_editor_alert.vue b/app/assets/javascripts/content_editor/components/content_editor_alert.vue new file mode 100644 index 00000000000..c6737da1d77 --- /dev/null +++ b/app/assets/javascripts/content_editor/components/content_editor_alert.vue @@ -0,0 +1,33 @@ +<script> +import { GlAlert } from '@gitlab/ui'; +import EditorStateObserver from './editor_state_observer.vue'; + +export default { + components: { + GlAlert, + EditorStateObserver, + }, + data() { + return { + message: null, + variant: 'danger', + }; + }, + methods: { + displayAlert({ message, variant }) { + this.message = message; + this.variant = variant; + }, + dismissAlert() { + this.message = null; + }, + }, +}; +</script> +<template> + <editor-state-observer @alert="displayAlert"> + <gl-alert v-if="message" class="gl-mb-6" :variant="variant" @dismiss="dismissAlert"> + {{ message }} + </gl-alert> + </editor-state-observer> +</template> diff --git a/app/assets/javascripts/content_editor/components/content_editor_error.vue b/app/assets/javascripts/content_editor/components/content_editor_error.vue deleted file mode 100644 index 031ea92a7e9..00000000000 --- a/app/assets/javascripts/content_editor/components/content_editor_error.vue +++ /dev/null @@ -1,31 +0,0 @@ -<script> -import { GlAlert } from '@gitlab/ui'; -import EditorStateObserver from './editor_state_observer.vue'; - -export default { - components: { - GlAlert, - EditorStateObserver, - }, - data() { - return { - error: null, - }; - }, - methods: { - displayError({ error }) { - this.error = error; - }, - dismissError() { - this.error = null; - }, - }, -}; -</script> -<template> - <editor-state-observer @error="displayError"> - <gl-alert v-if="error" class="gl-mb-6" variant="danger" @dismiss="dismissError"> - {{ error }} - </gl-alert> - </editor-state-observer> -</template> diff --git a/app/assets/javascripts/content_editor/components/editor_state_observer.vue b/app/assets/javascripts/content_editor/components/editor_state_observer.vue index 2eeb0719096..0604047a953 100644 --- a/app/assets/javascripts/content_editor/components/editor_state_observer.vue +++ b/app/assets/javascripts/content_editor/components/editor_state_observer.vue @@ -7,7 +7,7 @@ export const tiptapToComponentMap = { transaction: 'transaction', focus: 'focus', blur: 'blur', - error: 'error', + alert: 'alert', }; const getComponentEventName = (tiptapEventName) => tiptapToComponentMap[tiptapEventName]; diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue index c44e8145982..41c083111c5 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_base.vue @@ -26,8 +26,8 @@ export default { type: Object, required: true, }, - getPos: { - type: Function, + node: { + type: Object, required: true, }, }, @@ -61,7 +61,17 @@ export default { const { state } = this.editor; const { $cursor } = state.selection; - this.displayActionsDropdown = $cursor?.pos - $cursor?.parentOffset - 1 === this.getPos(); + if (!$cursor) return; + + this.displayActionsDropdown = false; + + for (let level = 0; level < $cursor.depth; level += 1) { + if ($cursor.node(level) === this.node) { + this.displayActionsDropdown = true; + break; + } + } + if (this.displayActionsDropdown) { this.selectedRect = getSelectedRect(state); } @@ -99,7 +109,11 @@ export default { :as="cellType" @click="hideDropdown" > - <span v-if="displayActionsDropdown" class="gl-absolute gl-right-0 gl-top-0"> + <span + v-if="displayActionsDropdown" + contenteditable="false" + class="gl-absolute gl-right-0 gl-top-0" + > <gl-dropdown ref="dropdown" dropup diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_body.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_body.vue index 6b4343dd5b8..47cd837d060 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_body.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_body.vue @@ -11,8 +11,8 @@ export default { type: Object, required: true, }, - getPos: { - type: Function, + node: { + type: Object, required: true, }, }, diff --git a/app/assets/javascripts/content_editor/components/wrappers/table_cell_header.vue b/app/assets/javascripts/content_editor/components/wrappers/table_cell_header.vue index 5f9889374f6..150f78bc84f 100644 --- a/app/assets/javascripts/content_editor/components/wrappers/table_cell_header.vue +++ b/app/assets/javascripts/content_editor/components/wrappers/table_cell_header.vue @@ -11,8 +11,8 @@ export default { type: Object, required: true, }, - getPos: { - type: Function, + node: { + type: Object, required: true, }, }, diff --git a/app/assets/javascripts/content_editor/extensions/table.js b/app/assets/javascripts/content_editor/extensions/table.js index 0f0477cba2e..004bb8b815c 100644 --- a/app/assets/javascripts/content_editor/extensions/table.js +++ b/app/assets/javascripts/content_editor/extensions/table.js @@ -1 +1,42 @@ -export { Table as default } from '@tiptap/extension-table'; +import { Table } from '@tiptap/extension-table'; +import { debounce } from 'lodash'; +import { __ } from '~/locale'; +import { getMarkdownSource } from '../services/markdown_sourcemap'; +import { shouldRenderHTMLTable } from '../services/serialization_helpers'; + +let alertShown = false; +const onUpdate = debounce((editor) => { + if (alertShown) return; + + editor.state.doc.descendants((node) => { + if (node.type.name === 'table' && node.attrs.isMarkdown && shouldRenderHTMLTable(node)) { + editor.emit('alert', { + message: __( + 'The content editor may change the markdown formatting style of the document, which may not match your original markdown style.', + ), + variant: 'warning', + }); + + alertShown = true; + + return false; + } + + return true; + }); +}, 1000); + +export default Table.extend({ + addAttributes() { + return { + isMarkdown: { + default: null, + parseHTML: (element) => Boolean(getMarkdownSource(element)), + }, + }; + }, + + onUpdate({ editor }) { + onUpdate(editor); + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/table_cell.js b/app/assets/javascripts/content_editor/extensions/table_cell.js index befc33e669f..9f437ce066c 100644 --- a/app/assets/javascripts/content_editor/extensions/table_cell.js +++ b/app/assets/javascripts/content_editor/extensions/table_cell.js @@ -1,10 +1,9 @@ import { TableCell } from '@tiptap/extension-table-cell'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; import TableCellBodyWrapper from '../components/wrappers/table_cell_body.vue'; -import { isBlockTablesFeatureEnabled } from '../services/feature_flags'; export default TableCell.extend({ - content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*', + content: 'block+', addNodeView() { return VueNodeViewRenderer(TableCellBodyWrapper); diff --git a/app/assets/javascripts/content_editor/extensions/table_header.js b/app/assets/javascripts/content_editor/extensions/table_header.js index 829b06fc14b..045fd03199b 100644 --- a/app/assets/javascripts/content_editor/extensions/table_header.js +++ b/app/assets/javascripts/content_editor/extensions/table_header.js @@ -1,10 +1,9 @@ import { TableHeader } from '@tiptap/extension-table-header'; import { VueNodeViewRenderer } from '@tiptap/vue-2'; import TableCellHeaderWrapper from '../components/wrappers/table_cell_header.vue'; -import { isBlockTablesFeatureEnabled } from '../services/feature_flags'; export default TableHeader.extend({ - content: isBlockTablesFeatureEnabled() ? 'block+' : 'inline*', + content: 'block+', addNodeView() { return VueNodeViewRenderer(TableCellHeaderWrapper); }, diff --git a/app/assets/javascripts/content_editor/services/feature_flags.js b/app/assets/javascripts/content_editor/services/feature_flags.js deleted file mode 100644 index 5f7a4595938..00000000000 --- a/app/assets/javascripts/content_editor/services/feature_flags.js +++ /dev/null @@ -1,3 +0,0 @@ -export function isBlockTablesFeatureEnabled() { - return gon.features?.contentEditorBlockTables; -} diff --git a/app/assets/javascripts/content_editor/services/serialization_helpers.js b/app/assets/javascripts/content_editor/services/serialization_helpers.js index b2327555b45..ed5910fca18 100644 --- a/app/assets/javascripts/content_editor/services/serialization_helpers.js +++ b/app/assets/javascripts/content_editor/services/serialization_helpers.js @@ -1,5 +1,4 @@ import { uniq } from 'lodash'; -import { isBlockTablesFeatureEnabled } from './feature_flags'; const defaultAttrs = { td: { colspan: 1, rowspan: 1, colwidth: null }, @@ -75,7 +74,7 @@ function getChildren(node) { return children; } -function shouldRenderHTMLTable(table) { +export function shouldRenderHTMLTable(table) { const { rows, cells } = getRowsAndCells(table); const cellChildCount = Math.max(...cells.map((cell) => cell.childCount)); @@ -282,11 +281,6 @@ export function renderOrderedList(state, node) { } export function renderTableCell(state, node) { - if (!isBlockTablesFeatureEnabled()) { - state.renderInline(node); - return; - } - if (!isInBlockTable(node) || containsParagraphWithOnlyText(node)) { state.renderInline(node.child(0)); } else { @@ -303,9 +297,7 @@ export function renderTableRow(state, node) { } export function renderTable(state, node) { - if (isBlockTablesFeatureEnabled()) { - setIsInBlockTable(node, shouldRenderHTMLTable(node)); - } + setIsInBlockTable(node, shouldRenderHTMLTable(node)); if (isInBlockTable(node)) renderTagOpen(state, 'table'); @@ -317,9 +309,7 @@ export function renderTable(state, node) { state.closeBlock(node); state.flushClose(); - if (isBlockTablesFeatureEnabled()) { - unsetIsInBlockTable(node); - } + unsetIsInBlockTable(node); } export function renderHardBreak(state, node, parent, index) { diff --git a/app/assets/javascripts/content_editor/services/upload_helpers.js b/app/assets/javascripts/content_editor/services/upload_helpers.js index 8ac3f719309..f5bf2742748 100644 --- a/app/assets/javascripts/content_editor/services/upload_helpers.js +++ b/app/assets/javascripts/content_editor/services/upload_helpers.js @@ -72,8 +72,9 @@ const uploadImage = async ({ editor, file, uploadsPath, renderMarkdown }) => { ); } catch (e) { editor.commands.deleteRange({ from: position, to: position + 1 }); - editor.emit('error', { - error: __('An error occurred while uploading the image. Please try again.'), + editor.emit('alert', { + message: __('An error occurred while uploading the image. Please try again.'), + variant: 'danger', }); } }; @@ -102,8 +103,9 @@ const uploadAttachment = async ({ editor, file, uploadsPath, renderMarkdown }) = ); } catch (e) { editor.commands.deleteRange({ from, to: from + 1 }); - editor.emit('error', { - error: __('An error occurred while uploading the file. Please try again.'), + editor.emit('alert', { + message: __('An error occurred while uploading the file. Please try again.'), + variant: 'danger', }); } }; |