Skip to content

Commit 8899fef

Browse files
authored
Merge pull request #7793 from nextcloud/fix/link_preview_options
fix(PreviewOptions): Move three-dot menu into preview box or link bubble
2 parents 5bf0fc9 + d142d24 commit 8899fef

9 files changed

Lines changed: 150 additions & 517 deletions

File tree

cypress/e2e/Links.spec.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,8 @@ describe('test link marks', function () {
104104
cy.insertLine(link)
105105
clickLink(link)
106106

107-
cy.get('.link-view-bubble button[title="Remove link"]').click()
107+
cy.get('.link-view-bubble .link-options').click()
108+
cy.get('button').contains('Remove').click()
108109

109110
cy.getContent().find(`a[href*="${link}"]`).should('not.exist')
110111
})

cypress/e2e/nodes/PreviewOptions.spec.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ describe('Preview Options', function () {
2121
'nextcloud.com',
2222
)
2323
cy.get('[data-text-action-entry="insert-link-input"] button').click()
24-
cy.get('.preview-options').click()
24+
25+
cy.getContent().find(`a[href*="https://nextcloud.com"]`).click()
26+
cy.get('.link-options').click()
2527
})
2628

2729
it('should render previewOptions correctly', function () {

src/components/Editor/PreviewOptions.vue

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,60 @@
33
- SPDX-License-Identifier: AGPL-3.0-or-later
44
-->
55
<template>
6-
<div contenteditable="false" class="preview-options-container">
7-
<NcActions
8-
data-text-preview-options="select"
9-
class="preview-options"
10-
:open.sync="open"
11-
@open="onOpen">
6+
<NcActions
7+
data-text-link-options="select"
8+
class="link-options"
9+
:open.sync="open"
10+
@open="onOpen">
11+
<template #icon>
12+
<DotsVerticalIcon :size="20" />
13+
</template>
14+
<NcActionCaption :name="t('text', 'Preview options')" />
15+
<NcActionRadio
16+
data-text-preview-option="text-only"
17+
name="preview-option"
18+
value="text-only"
19+
:model-value="type"
20+
@change="(e) => toggle(e.currentTarget.value)">
21+
{{ t('text', 'Text only') }}
22+
</NcActionRadio>
23+
<NcActionRadio
24+
data-text-preview-option="link-preview"
25+
name="preview-option"
26+
value="link-preview"
27+
:model-value="type"
28+
@change="(e) => toggle(e.currentTarget.value)">
29+
{{ t('text', 'Show link preview') }}
30+
</NcActionRadio>
31+
32+
<NcActionSeparator />
33+
34+
<!-- Open link -->
35+
<NcActionButton v-if="href" close-after-click @click="openLink">
36+
<template #icon>
37+
<OpenIcon :size="20" />
38+
</template>
39+
{{ t('text', 'Open in new tab') }}
40+
</NcActionButton>
41+
42+
<!-- Copy link -->
43+
<NcActionButton v-if="href" close-after-click @click="copyLink">
44+
<template #icon>
45+
<CheckIcon v-if="copySuccess" :size="20" />
46+
<NcLoadingIcon v-else-if="copyLoading" :size="20" />
47+
<ContentCopyIcon v-else :size="20" />
48+
</template>
49+
{{ t('text', 'Copy link') }}
50+
</NcActionButton>
51+
52+
<!-- Remove link -->
53+
<NcActionButton close-after-click @click="deleteNode">
1254
<template #icon>
13-
<DotsVerticalIcon :size="20" />
55+
<DeleteOutlineIcon :size="20" />
1456
</template>
15-
<NcActionCaption :name="t('text', 'Preview options')" />
16-
<NcActionRadio
17-
data-text-preview-option="text-only"
18-
name="preview-option"
19-
value="text-only"
20-
:model-value="type"
21-
@change="(e) => toggle(e.currentTarget.value)">
22-
{{ t('text', 'Text only') }}
23-
</NcActionRadio>
24-
<NcActionRadio
25-
data-text-preview-option="link-preview"
26-
name="preview-option"
27-
value="link-preview"
28-
:model-value="type"
29-
@change="(e) => toggle(e.currentTarget.value)">
30-
{{ t('text', 'Show link preview') }}
31-
</NcActionRadio>
32-
<NcActionSeparator />
33-
<NcActionButton v-if="href" close-after-click @click="openLink">
34-
<template #icon>
35-
<OpenIcon :size="20" />
36-
</template>
37-
{{ t('text', 'Open in new tab') }}
38-
</NcActionButton>
39-
<NcActionButton close-after-click @click="deleteNode">
40-
<template #icon>
41-
<DeleteOutlineIcon :size="20" />
42-
</template>
43-
{{ t('text', 'Remove link') }}
44-
</NcActionButton>
45-
</NcActions>
46-
</div>
57+
{{ t('text', 'Remove link') }}
58+
</NcActionButton>
59+
</NcActions>
4760
</template>
4861

4962
<script>
@@ -53,24 +66,33 @@ import NcActionCaption from '@nextcloud/vue/components/NcActionCaption'
5366
import NcActionRadio from '@nextcloud/vue/components/NcActionRadio'
5467
import NcActions from '@nextcloud/vue/components/NcActions'
5568
import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator'
69+
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
70+
import CheckIcon from 'vue-material-design-icons/Check.vue'
71+
import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue'
5672
import DotsVerticalIcon from 'vue-material-design-icons/DotsVertical.vue'
5773
import OpenIcon from 'vue-material-design-icons/OpenInNew.vue'
5874
import DeleteOutlineIcon from 'vue-material-design-icons/TrashCanOutline.vue'
75+
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin.js'
5976
6077
export default {
6178
name: 'PreviewOptions',
6279
6380
components: {
81+
CheckIcon,
82+
ContentCopyIcon,
6483
DotsVerticalIcon,
6584
NcActions,
6685
NcActionButton,
6786
NcActionCaption,
6887
NcActionRadio,
6988
NcActionSeparator,
89+
NcLoadingIcon,
7090
DeleteOutlineIcon,
7191
OpenIcon,
7292
},
7393
94+
mixins: [CopyToClipboardMixin],
95+
7496
props: {
7597
type: {
7698
type: String,
@@ -88,6 +110,12 @@ export default {
88110
}
89111
},
90112
113+
computed: {
114+
isPreview() {
115+
return this.type === 'link-preview'
116+
},
117+
},
118+
91119
methods: {
92120
onOpen() {
93121
this.$emit('open')
@@ -96,13 +124,16 @@ export default {
96124
this.open = false
97125
this.$emit('toggle', type)
98126
},
99-
deleteNode() {
100-
this.$emit('delete')
101-
},
102127
openLink() {
103128
if (!this.href) return
104129
window.open(this.href, '_blank').focus()
105130
},
131+
async copyLink() {
132+
await this.copyToClipboard(this.href)
133+
},
134+
deleteNode() {
135+
this.$emit('delete')
136+
},
106137
t,
107138
},
108139
}
@@ -113,19 +144,4 @@ div[contenteditable='false'] {
113144
padding: 0;
114145
margin: 0;
115146
}
116-
117-
.preview-options-container {
118-
position: absolute;
119-
width: 0 !important;
120-
left: -44px;
121-
top: 50%;
122-
transform: translate(0, -50%);
123-
// Required to overlay the drag handler padding
124-
z-index: 10000;
125-
}
126-
127-
// Inside details, button needs to be shifted further
128-
.details-content .preview-options-container {
129-
left: calc(-44px - 24px);
130-
}
131147
</style>

src/components/Link/LinkBubbleView.vue

Lines changed: 25 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,6 @@
2020
<OpenInNewIcon :size="20" />
2121
</template>
2222
</NcButton>
23-
<!-- copy link -->
24-
<NcButton
25-
:title="copyLinkTooltip"
26-
:aria-label="copyLinkTooltip"
27-
type="tertiary"
28-
@click="copyLink">
29-
<template #icon>
30-
<CheckIcon v-if="copySuccess" :size="20" />
31-
<NcLoadingIcon v-else-if="copyLoading" :size="20" />
32-
<ContentCopyIcon v-else :size="20" />
33-
</template>
34-
</NcButton>
3523

3624
<!-- edit/save -->
3725
<div v-if="isEditable" class="edit-buttons">
@@ -55,29 +43,25 @@
5543
<CheckIcon :size="20" />
5644
</template>
5745
</NcButton>
58-
59-
<!-- remove link / dismiss changes -->
60-
<NcButton
61-
v-if="!edit"
62-
:title="t('text', 'Remove link')"
63-
:aria-label="t('text', 'Remove link')"
64-
type="tertiary"
65-
@click="removeLink">
66-
<template #icon>
67-
<LinkOffIcon :size="20" />
68-
</template>
69-
</NcButton>
70-
<NcButton
71-
v-else
72-
:title="t('text', 'Cancel')"
73-
:aria-label="t('text', 'Cancel')"
74-
type="tertiary"
75-
@click="stopEdit">
76-
<template #icon>
77-
<CloseIcon :size="20" />
78-
</template>
79-
</NcButton>
8046
</div>
47+
48+
<!-- preview options / dismiss changes -->
49+
<PreviewOptions
50+
v-if="isEditable && !edit"
51+
:href="href"
52+
type="text-only"
53+
@toggle="setPreview"
54+
@delete="removeLink" />
55+
<NcButton
56+
v-else
57+
:title="t('text', 'Cancel')"
58+
:aria-label="t('text', 'Cancel')"
59+
type="tertiary"
60+
@click="stopEdit">
61+
<template #icon>
62+
<CloseIcon :size="20" />
63+
</template>
64+
</NcButton>
8165
</div>
8266

8367
<!-- link edit form -->
@@ -106,38 +90,31 @@
10690
<script>
10791
import { t } from '@nextcloud/l10n'
10892
import NcButton from '@nextcloud/vue/components/NcButton'
109-
import NcLoadingIcon from '@nextcloud/vue/components/NcLoadingIcon'
11093
import NcTextField from '@nextcloud/vue/components/NcTextField'
11194
import { NcReferenceList } from '@nextcloud/vue/dist/Components/NcRichText.js'
112-
import CheckIcon from 'vue-material-design-icons/Check.vue'
11395
import CloseIcon from 'vue-material-design-icons/Close.vue'
114-
import ContentCopyIcon from 'vue-material-design-icons/ContentCopy.vue'
115-
import LinkOffIcon from 'vue-material-design-icons/LinkOff.vue'
11696
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
11797
import PencilOutlineIcon from 'vue-material-design-icons/PencilOutline.vue'
11898
119-
import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin.js'
12099
import { useOpenLinkHandler } from '../Editor.provider.ts'
100+
import PreviewOptions from '../Editor/PreviewOptions.vue'
121101
122102
const PROTOCOLS_WITH_PREVIEW = ['http:', 'https:']
123103
124104
export default {
125105
name: 'LinkBubbleView',
126106
127107
components: {
128-
CheckIcon,
108+
PreviewOptions,
129109
CloseIcon,
130-
ContentCopyIcon,
131110
NcButton,
132-
NcLoadingIcon,
133111
NcReferenceList,
134112
NcTextField,
135-
LinkOffIcon,
136113
OpenInNewIcon,
137114
PencilOutlineIcon,
138115
},
139116
140-
mixins: [CopyToClipboardMixin, useOpenLinkHandler],
117+
mixins: [useOpenLinkHandler],
141118
142119
props: {
143120
editor: {
@@ -164,16 +141,6 @@ export default {
164141
return this.href || 'no-href'
165142
},
166143
167-
copyLinkTooltip() {
168-
if (this.copied) {
169-
if (this.copySuccess) {
170-
return ''
171-
}
172-
return t('text', 'Cannot copy, please copy the link manually')
173-
}
174-
return t('text', 'Copy link to clipboard')
175-
},
176-
177144
/**
178145
* NcReferenceList only accepts full URLs with origin.
179146
*/
@@ -217,16 +184,16 @@ export default {
217184
this.$openLinkHandler.openLink(href)
218185
},
219186
220-
async copyLink() {
221-
await this.copyToClipboard(this.href)
222-
},
223-
224187
onReferenceListLoaded() {
225188
this.referenceTitle =
226189
this.$refs.referencelist.firstReference?.openGraphObject?.name
227190
?? null
228191
},
229192
193+
setPreview() {
194+
this.editor.chain().hideLinkBubble().setPreview().run()
195+
},
196+
230197
startEdit() {
231198
this.edit = true
232199
this.newHref = this.href

src/nodes/Paragraph.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import TiptapParagraph from '@tiptap/extension-paragraph'
77
import currentLineMenu from '../plugins/currentLineMenu.js'
8-
import previewOptions from '../plugins/previewOptions.js'
98

109
const Paragraph = TiptapParagraph.extend({
1110
parseHTML() {
@@ -45,10 +44,7 @@ const Paragraph = TiptapParagraph.extend({
4544
},
4645

4746
addProseMirrorPlugins() {
48-
return [
49-
currentLineMenu({ editor: this.editor }),
50-
previewOptions({ editor: this.editor }),
51-
]
47+
return [currentLineMenu({ editor: this.editor })]
5248
},
5349
})
5450

0 commit comments

Comments
 (0)