diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-28 03:08:57 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-04-28 03:08:57 +0300 |
commit | f5af3960ef1ea41338f4a06424ef2caaf9bead52 (patch) | |
tree | 9e6a2db90af1902c3855c766406de2f92810d279 /app/assets/javascripts/content_editor | |
parent | 04354e22bf7ae68d1594232716a18ce37d327807 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
4 files changed, 205 insertions, 1 deletions
diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue index dba03a1e3e2..2971a1e40ba 100644 --- a/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue +++ b/app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue @@ -79,6 +79,17 @@ export default { :label="__('Code')" @execute="trackToolbarControlExecution" /> + <toolbar-button + data-testid="link" + content-type="link" + icon-name="link" + editor-command="toggleLink" + :editor-command-params="{ href: '' }" + category="tertiary" + size="medium" + :label="__('Insert link')" + @execute="trackToolbarControlExecution" + /> </gl-button-group> </bubble-menu> </template> diff --git a/app/assets/javascripts/content_editor/components/bubble_menus/link.vue b/app/assets/javascripts/content_editor/components/bubble_menus/link.vue new file mode 100644 index 00000000000..87853fda7b8 --- /dev/null +++ b/app/assets/javascripts/content_editor/components/bubble_menus/link.vue @@ -0,0 +1,185 @@ +<script> +import { + GlLink, + GlForm, + GlFormGroup, + GlFormInput, + GlButton, + GlButtonGroup, + GlTooltipDirective as GlTooltip, +} from '@gitlab/ui'; +import { BubbleMenu } from '@tiptap/vue-2'; +import Link from '../../extensions/link'; +import EditorStateObserver from '../editor_state_observer.vue'; + +export default { + components: { + BubbleMenu, + GlForm, + GlFormGroup, + GlFormInput, + GlLink, + GlButton, + GlButtonGroup, + EditorStateObserver, + }, + directives: { + GlTooltip, + }, + inject: ['tiptapEditor'], + data() { + return { + linkHref: undefined, + linkCanonicalSrc: undefined, + linkTitle: undefined, + + isEditing: false, + }; + }, + watch: { + linkCanonicalSrc(value) { + if (!value) this.isEditing = true; + }, + }, + methods: { + shouldShow() { + const shouldShow = this.tiptapEditor.isActive(Link.name); + + if (!shouldShow) this.isEditing = false; + + return shouldShow; + }, + + startEditingLink() { + // select the entire link + this.tiptapEditor.chain().focus().extendMarkRange(Link.name).run(); + + this.isEditing = true; + }, + + endEditingLink() { + this.isEditing = false; + + if (!this.linkCanonicalSrc && !this.linkHref) { + this.removeLink(); + } + }, + + cancelEditingLink() { + this.endEditingLink(); + this.updateLinkToState(); + }, + + saveEditedLink() { + if (!this.linkCanonicalSrc) { + this.removeLink(); + } else { + this.tiptapEditor + .chain() + .focus() + .extendMarkRange(Link.name) + .updateAttributes(Link.name, { + href: this.linkCanonicalSrc, + canonicalSrc: this.linkCanonicalSrc, + title: this.linkTitle, + }) + .run(); + } + + this.endEditingLink(); + }, + + updateLinkToState() { + if (!this.tiptapEditor.isActive(Link.name)) return; + + const { href, title, canonicalSrc } = this.tiptapEditor.getAttributes(Link.name); + + this.linkTitle = title; + this.linkHref = href; + this.linkCanonicalSrc = canonicalSrc || href; + }, + + copyLinkHref() { + navigator.clipboard.writeText(this.linkCanonicalSrc); + }, + + removeLink() { + this.tiptapEditor.chain().focus().extendMarkRange(Link.name).unsetLink().run(); + }, + }, +}; +</script> +<template> + <bubble-menu + data-testid="link-bubble-menu" + class="gl-shadow gl-rounded-base gl-bg-white" + :editor="tiptapEditor" + plugin-key="bubbleMenuLink" + :should-show="() => shouldShow()" + :tippy-options="{ placement: 'bottom' }" + > + <editor-state-observer @transaction="updateLinkToState"> + <gl-button-group v-if="!isEditing" class="gl-display-flex gl-align-items-center"> + <gl-link + v-gl-tooltip + :href="linkHref" + :aria-label="linkCanonicalSrc" + :title="linkCanonicalSrc" + target="_blank" + class="gl-px-3 gl-overflow-hidden gl-white-space-nowrap gl-text-overflow-ellipsis" + > + {{ linkCanonicalSrc }} + </gl-link> + <gl-button + v-gl-tooltip + variant="default" + category="tertiary" + size="medium" + data-testid="copy-link-url" + :aria-label="__('Copy link URL')" + :title="__('Copy link URL')" + icon="copy-to-clipboard" + @click="copyLinkHref" + /> + <gl-button + v-gl-tooltip + variant="default" + category="tertiary" + size="medium" + data-testid="edit-link" + :aria-label="__('Edit link')" + :title="__('Edit link')" + icon="pencil" + @click="startEditingLink" + /> + <gl-button + v-gl-tooltip + variant="default" + category="tertiary" + size="medium" + data-testid="remove-link" + :aria-label="__('Remove link')" + :title="__('Remove link')" + icon="unlink" + @click="removeLink" + /> + </gl-button-group> + <gl-form v-else class="bubble-menu-form gl-p-4 gl-w-100" @submit="saveEditedLink"> + <gl-form-group data-testid="link-href-group" :label="__('URL')" label-for="link-href"> + <gl-form-input id="link-href" v-model="linkCanonicalSrc" /> + </gl-form-group> + <gl-form-group data-testid="link-title-group" :label="__('Title')" label-for="link-title"> + <gl-form-input id="link-title" v-model="linkTitle" /> + </gl-form-group> + <div class="gl-display-flex gl-justify-content-end"> + <gl-button class="gl-mr-3" data-testid="cancel-link" @click="cancelEditingLink"> + {{ __('Cancel') }} + </gl-button> + <gl-button variant="confirm" type="submit"> + {{ __('Apply') }} + </gl-button> + </div> + </gl-form> + </editor-state-observer> + </bubble-menu> +</template> diff --git a/app/assets/javascripts/content_editor/components/content_editor.vue b/app/assets/javascripts/content_editor/components/content_editor.vue index 585d100dfc6..a3247298b19 100644 --- a/app/assets/javascripts/content_editor/components/content_editor.vue +++ b/app/assets/javascripts/content_editor/components/content_editor.vue @@ -6,6 +6,7 @@ import ContentEditorProvider from './content_editor_provider.vue'; import EditorStateObserver from './editor_state_observer.vue'; import FormattingBubbleMenu from './bubble_menus/formatting.vue'; import CodeBlockBubbleMenu from './bubble_menus/code_block.vue'; +import LinkBubbleMenu from './bubble_menus/link.vue'; import TopToolbar from './top_toolbar.vue'; import LoadingIndicator from './loading_indicator.vue'; @@ -18,6 +19,7 @@ export default { TopToolbar, FormattingBubbleMenu, CodeBlockBubbleMenu, + LinkBubbleMenu, EditorStateObserver, }, props: { @@ -92,6 +94,7 @@ export default { <div class="gl-relative"> <formatting-bubble-menu /> <code-block-bubble-menu /> + <link-bubble-menu /> <tiptap-editor-content class="md" :editor="contentEditor.tiptapEditor" /> <loading-indicator /> </div> diff --git a/app/assets/javascripts/content_editor/components/toolbar_button.vue b/app/assets/javascripts/content_editor/components/toolbar_button.vue index 441185e28c6..c16dc34e36f 100644 --- a/app/assets/javascripts/content_editor/components/toolbar_button.vue +++ b/app/assets/javascripts/content_editor/components/toolbar_button.vue @@ -29,6 +29,11 @@ export default { required: false, default: '', }, + editorCommandParams: { + type: Object, + required: false, + default: null, + }, variant: { type: String, required: false, @@ -58,7 +63,7 @@ export default { const { contentType } = this; if (this.editorCommand) { - this.tiptapEditor.chain()[this.editorCommand]().focus().run(); + this.tiptapEditor.chain()[this.editorCommand](this.editorCommandParams).focus().run(); } this.$emit('execute', { contentType }); |