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>2022-04-28 03:08:57 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-04-28 03:08:57 +0300
commitf5af3960ef1ea41338f4a06424ef2caaf9bead52 (patch)
tree9e6a2db90af1902c3855c766406de2f92810d279 /app/assets/javascripts/content_editor
parent04354e22bf7ae68d1594232716a18ce37d327807 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app/assets/javascripts/content_editor')
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/formatting.vue11
-rw-r--r--app/assets/javascripts/content_editor/components/bubble_menus/link.vue185
-rw-r--r--app/assets/javascripts/content_editor/components/content_editor.vue3
-rw-r--r--app/assets/javascripts/content_editor/components/toolbar_button.vue7
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 });