diff --git a/cypress/e2e/nodes/Links.spec.js b/cypress/e2e/nodes/Links.spec.js index 13d6db365e6..b84fb82db94 100644 --- a/cypress/e2e/nodes/Links.spec.js +++ b/cypress/e2e/nodes/Links.spec.js @@ -47,7 +47,7 @@ describe('test link marks', function() { cy.get('.link-view-bubble .widget-default', { timeout: 10000 }) .find('.widget-default--name') .contains('Nextcloud') - .click({ force: true }) + .click() }) it('shows a link preview in the bubble after browsing to link', () => { @@ -64,6 +64,16 @@ describe('test link marks', function() { .contains('Nextcloud') }) + it('open button opens a new tab', () => { + const link = 'https://nextcloud.com/' + cy.insertLine(link) + clickLink(link) + + cy.get('.link-view-bubble button[title="Open link"]').click() + + cy.get('@winOpen').should('have.been.calledOnce') + }) + it('closes the link bubble when clicking elsewhere', () => { const link = 'https://nextcloud.com/' cy.insertLine(link) diff --git a/cypress/e2e/nodes/PreviewOptions.spec.js b/cypress/e2e/nodes/PreviewOptions.spec.js index df0e22e86fd..48d5b53f3ce 100644 --- a/cypress/e2e/nodes/PreviewOptions.spec.js +++ b/cypress/e2e/nodes/PreviewOptions.spec.js @@ -23,6 +23,7 @@ describe('Preview Options', function() { }) it('should render previewOptions correctly', function() { + cy.get('.action-button__text').contains('Open in new tab').should('be.visible') cy.get('.action-button__text').contains('Remove link').should('be.visible') cy.get('.action-radio__label').each(el => { cy.wrap(el).invoke('text').should('match', /Text only|Show link preview/) diff --git a/src/components/Editor.provider.js b/src/components/Editor.provider.js index bcc4085f7f8..3d6233379ee 100644 --- a/src/components/Editor.provider.js +++ b/src/components/Editor.provider.js @@ -3,6 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +import { openLink } from '../helpers/links.js' import { logger } from '../helpers/logger.js' export const EDITOR = Symbol('tiptap:editor') @@ -16,6 +17,7 @@ export const SYNC_SERVICE = Symbol('sync:service') export const EDITOR_UPLOAD = Symbol('editor:upload') export const HOOK_MENTION_SEARCH = Symbol('hook:mention-search') export const HOOK_MENTION_INSERT = Symbol('hook:mention-insert') +export const OPEN_LINK_HANDLER = Symbol('editor:open-link-handler') export const useEditorMixin = { inject: { @@ -99,3 +101,13 @@ export const useMentionHook = { }, }, } +export const useOpenLinkHandler = { + inject: { + $openLinkHandler: { + from: OPEN_LINK_HANDLER, + default: { + openLink, + }, + }, + }, +} diff --git a/src/components/Editor/PreviewOptions.vue b/src/components/Editor/PreviewOptions.vue index 36857afc449..ac1a97c7265 100644 --- a/src/components/Editor/PreviewOptions.vue +++ b/src/components/Editor/PreviewOptions.vue @@ -27,11 +27,17 @@ {{ t('text', 'Show link preview') }} + + + {{ t('text', 'Open in new tab') }} + - {{ t('text','Remove link') }} + {{ t('text', 'Remove link') }} @@ -45,6 +51,7 @@ import NcActionCaption from '@nextcloud/vue/components/NcActionCaption' import NcActionSeparator from '@nextcloud/vue/components/NcActionSeparator' import DotsVerticalIcon from 'vue-material-design-icons/DotsVertical.vue' import DeleteIcon from 'vue-material-design-icons/Delete.vue' +import OpenIcon from 'vue-material-design-icons/OpenInNew.vue' export default { name: 'PreviewOptions', @@ -57,6 +64,7 @@ export default { NcActionRadio, NcActionSeparator, DeleteIcon, + OpenIcon, }, props: { @@ -64,6 +72,11 @@ export default { type: String, required: true, }, + href: { + type: String, + required: false, + default: '', + }, }, data() { @@ -83,6 +96,10 @@ export default { deleteNode() { this.$emit('delete') }, + openLink() { + if (!this.href) return + window.open(this.href, '_blank').focus() + }, }, } diff --git a/src/components/Link/LinkBubbleView.vue b/src/components/Link/LinkBubbleView.vue index 9d980280a34..6c355d9ece1 100644 --- a/src/components/Link/LinkBubbleView.vue +++ b/src/components/Link/LinkBubbleView.vue @@ -10,6 +10,15 @@ + + + + { editor.commands.hideLinkBubble() diff --git a/src/tests/plugins/extractLinkParagraphs.spec.js b/src/tests/plugins/extractLinkParagraphs.spec.js index a4fcd2419b0..3ba2b69c184 100644 --- a/src/tests/plugins/extractLinkParagraphs.spec.js +++ b/src/tests/plugins/extractLinkParagraphs.spec.js @@ -9,8 +9,9 @@ import Preview from '../../nodes/Preview.js' import createCustomEditor from '../testHelpers/createCustomEditor.ts' describe('extractLinkParagraphs', () => { - const link = 'Link' - const preview = 'Link' + const href = 'https://nextcloud.com' + const link = `Link` + const preview = `Link` it('returns an empty array for an empty doc', () => { const doc = prepareDoc('') @@ -23,7 +24,7 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { pos: 0, type: 'text-only', nodeSize: 6 }, + { href, pos: 0, type: 'text-only', nodeSize: 6 }, ]) }) @@ -31,7 +32,7 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(preview) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { pos: 0, type: 'link-preview', nodeSize: 6 }, + { href, pos: 0, type: 'link-preview', nodeSize: 6 }, ]) }) @@ -40,7 +41,7 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { pos: 0, type: 'text-only', nodeSize: 7 }, + { href, pos: 0, type: 'text-only', nodeSize: 7 }, ]) }) @@ -50,8 +51,8 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { pos: 0, type: 'text-only', nodeSize: 6 }, - { pos: 6, type: 'text-only', nodeSize: 6 }, + { href, pos: 0, type: 'text-only', nodeSize: 6 }, + { href, pos: 6, type: 'text-only', nodeSize: 6 }, ]) }) @@ -60,8 +61,8 @@ describe('extractLinkParagraphs', () => { const doc = prepareDoc(content) const paragraphs = extractLinkParagraphs(doc) expect(paragraphs).toEqual([ - { pos: 0, type: 'text-only', nodeSize: 6 }, - { pos: 6, type: 'link-preview', nodeSize: 6 }, + { href, pos: 0, type: 'text-only', nodeSize: 6 }, + { href, pos: 6, type: 'link-preview', nodeSize: 6 }, ]) })