diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-09 00:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-10-09 00:09:48 +0300 |
commit | 59e6c2df22c69baa791529db3326e68c9de10b54 (patch) | |
tree | aa75309a037a6031c38f8ccd9afe53cbcd519355 /app/assets/javascripts/content_editor | |
parent | e7527f548681e4f9efd32f9c3da937ad74c68948 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
6 files changed, 119 insertions, 0 deletions
diff --git a/app/assets/javascripts/content_editor/components/top_toolbar.vue b/app/assets/javascripts/content_editor/components/top_toolbar.vue index 82a449ae6af..89182b3a09f 100644 --- a/app/assets/javascripts/content_editor/components/top_toolbar.vue +++ b/app/assets/javascripts/content_editor/components/top_toolbar.vue @@ -112,6 +112,15 @@ export default { @execute="trackToolbarControlExecution" /> <toolbar-button + data-testid="details" + content-type="details" + icon-name="details-block" + class="gl-mx-2" + editor-command="toggleDetails" + :label="__('Add a collapsible section')" + @execute="trackToolbarControlExecution" + /> + <toolbar-button data-testid="horizontal-rule" content-type="horizontalRule" icon-name="dash" diff --git a/app/assets/javascripts/content_editor/components/wrappers/details.vue b/app/assets/javascripts/content_editor/components/wrappers/details.vue new file mode 100644 index 00000000000..aff15ac3e53 --- /dev/null +++ b/app/assets/javascripts/content_editor/components/wrappers/details.vue @@ -0,0 +1,33 @@ +<script> +import { NodeViewWrapper, NodeViewContent } from '@tiptap/vue-2'; + +export default { + name: 'DetailsWrapper', + components: { + NodeViewWrapper, + NodeViewContent, + }, + props: { + node: { + type: Object, + required: true, + }, + }, + data() { + return { + open: true, + }; + }, +}; +</script> +<template> + <node-view-wrapper class="gl-display-flex"> + <div + class="details-toggle-icon" + data-testid="details-toggle-icon" + :class="{ 'is-open': open }" + @click="open = !open" + ></div> + <node-view-content as="ul" class="details-content" :class="{ 'is-open': open }" /> + </node-view-wrapper> +</template> diff --git a/app/assets/javascripts/content_editor/extensions/details.js b/app/assets/javascripts/content_editor/extensions/details.js new file mode 100644 index 00000000000..e3d54ed01fd --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/details.js @@ -0,0 +1,36 @@ +import { Node } from '@tiptap/core'; +import { VueNodeViewRenderer } from '@tiptap/vue-2'; +import { wrappingInputRule } from 'prosemirror-inputrules'; +import DetailsWrapper from '../components/wrappers/details.vue'; + +export const inputRegex = /^\s*(<details>)$/; + +export default Node.create({ + name: 'details', + content: 'detailsContent+', + // eslint-disable-next-line @gitlab/require-i18n-strings + group: 'block list', + + parseHTML() { + return [{ tag: 'details' }]; + }, + + renderHTML({ HTMLAttributes }) { + return ['ul', HTMLAttributes, 0]; + }, + + addNodeView() { + return VueNodeViewRenderer(DetailsWrapper); + }, + + addInputRules() { + return [wrappingInputRule(inputRegex, this.type)]; + }, + + addCommands() { + return { + setDetails: () => ({ commands }) => commands.wrapInList('details'), + toggleDetails: () => ({ commands }) => commands.toggleList('details', 'detailsContent'), + }; + }, +}); diff --git a/app/assets/javascripts/content_editor/extensions/details_content.js b/app/assets/javascripts/content_editor/extensions/details_content.js new file mode 100644 index 00000000000..fb6c49d91aa --- /dev/null +++ b/app/assets/javascripts/content_editor/extensions/details_content.js @@ -0,0 +1,25 @@ +import { Node } from '@tiptap/core'; +import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; + +export default Node.create({ + name: 'detailsContent', + content: 'block+', + defining: true, + + parseHTML() { + return [ + { tag: '*', consuming: false, context: 'details/', priority: PARSE_HTML_PRIORITY_HIGHEST }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ['li', HTMLAttributes, 0]; + }, + + addKeyboardShortcuts() { + return { + Enter: () => this.editor.commands.splitListItem('detailsContent'), + 'Shift-Tab': () => this.editor.commands.liftListItem('detailsContent'), + }; + }, +}); 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 0471adf67e9..3b4fda26a36 100644 --- a/app/assets/javascripts/content_editor/services/create_content_editor.js +++ b/app/assets/javascripts/content_editor/services/create_content_editor.js @@ -10,6 +10,8 @@ import Code from '../extensions/code'; import CodeBlockHighlight from '../extensions/code_block_highlight'; import DescriptionItem from '../extensions/description_item'; import DescriptionList from '../extensions/description_list'; +import Details from '../extensions/details'; +import DetailsContent from '../extensions/details_content'; import Division from '../extensions/division'; import Document from '../extensions/document'; import Dropcursor from '../extensions/dropcursor'; @@ -81,6 +83,8 @@ export const createContentEditor = ({ CodeBlockHighlight, DescriptionItem, DescriptionList, + Details, + DetailsContent, Document, Division, Dropcursor, diff --git a/app/assets/javascripts/content_editor/services/markdown_serializer.js b/app/assets/javascripts/content_editor/services/markdown_serializer.js index 19b67f6fde8..8b6cff3f2e1 100644 --- a/app/assets/javascripts/content_editor/services/markdown_serializer.js +++ b/app/assets/javascripts/content_editor/services/markdown_serializer.js @@ -11,6 +11,8 @@ import Code from '../extensions/code'; import CodeBlockHighlight from '../extensions/code_block_highlight'; import DescriptionItem from '../extensions/description_item'; import DescriptionList from '../extensions/description_list'; +import Details from '../extensions/details'; +import DetailsContent from '../extensions/details_content'; import Division from '../extensions/division'; import Emoji from '../extensions/emoji'; import Figure from '../extensions/figure'; @@ -53,6 +55,7 @@ import { renderImage, renderPlayable, renderHTMLNode, + renderContent, } from './serialization_helpers'; const defaultSerializerConfig = { @@ -133,6 +136,15 @@ const defaultSerializerConfig = { renderHTMLNode(node.attrs.isTerm ? 'dt' : 'dd')(state, node); if (index === parent.childCount - 1) state.ensureNewLine(); }, + [Details.name]: renderHTMLNode('details', true), + [DetailsContent.name]: (state, node, parent, index) => { + if (!index) renderHTMLNode('summary')(state, node); + else { + if (index === 1) state.ensureNewLine(); + renderContent(state, node); + if (index === parent.childCount - 1) state.ensureNewLine(); + } + }, [Emoji.name]: (state, node) => { const { name } = node.attrs; |