diff --git a/cypress/e2e/directediting.spec.js b/cypress/e2e/directediting.spec.js index 9915ad8064d..2fc348a015b 100644 --- a/cypress/e2e/directediting.spec.js +++ b/cypress/e2e/directediting.spec.js @@ -6,10 +6,8 @@ function enterContentAndClose() { cy.intercept({ method: 'POST', url: '**/session/*/close' }).as('closeRequest') cy.intercept({ method: 'POST', url: '**/session/*/push' }).as('push') cy.intercept({ method: 'POST', url: '**/session/*/sync' }).as('sync') - cy.getContent().type('# This is a headline') - cy.getContent().type('{enter}') - cy.getContent().type('Some text') - cy.getContent().type('{enter}') + cy.insertLine('# This is a headline') + cy.insertLine('Some text') cy.getContent().type('{ctrl+s}') cy.wait('@push') cy.wait('@sync') diff --git a/cypress/e2e/nodes/CodeBlock.spec.js b/cypress/e2e/nodes/CodeBlock.spec.js index 1a825932850..05b926d29af 100644 --- a/cypress/e2e/nodes/CodeBlock.spec.js +++ b/cypress/e2e/nodes/CodeBlock.spec.js @@ -104,7 +104,7 @@ describe('Front matter support', function() { cy.isolateTest({ sourceFile: 'codeblock.md' }) cy.openFile('codeblock.md').then(() => { cy.clearContent() - cy.getContent().type('{enter}```javascript{enter}') + cy.insertLine('```javascript') cy.getContent().type('const foo = "bar"{enter}{enter}{enter}') cy.getContent().find('.hljs-keyword').first().contains('const') }) @@ -123,7 +123,7 @@ describe('Front matter support', function() { it('Add an invalid mermaid block', function() { cy.isolateTest() cy.openFile('empty.md').then(() => { - cy.getContent().type('```mermaid{enter}') + cy.insertLine('```mermaid') cy.getContent().find('code').should('exist') cy.getContent().get('.split-view__preview').should('be.visible') // eslint-disable-next-line cypress/no-unnecessary-waiting @@ -138,7 +138,7 @@ describe('Front matter support', function() { it('Add a valid mermaid block', function() { cy.isolateTest() cy.openFile('empty.md').then(() => { - cy.getContent().type('```mermaid{enter}') + cy.insertLine('```mermaid') cy.getContent().find('code').should('exist') cy.getContent().get('.split-view__preview').should('be.visible') // eslint-disable-next-line cypress/no-unnecessary-waiting diff --git a/cypress/e2e/nodes/Links.spec.js b/cypress/e2e/nodes/Links.spec.js index 86a32c84ca1..5da85f466d7 100644 --- a/cypress/e2e/nodes/Links.spec.js +++ b/cypress/e2e/nodes/Links.spec.js @@ -1,11 +1,11 @@ -import { initUserAndFiles, randUser } from '../../utils/index.js' +import { randUser } from '../../utils/index.js' const user = randUser() const fileName = 'empty.md' describe('test link marks', function() { before(function() { - initUserAndFiles(user) + cy.createUser(user) }) beforeEach(function() { @@ -22,8 +22,8 @@ describe('test link marks', function() { describe('link preview', function() { it('shows a link preview', () => { - cy.getContent().type('https://nextcloud.com') - cy.getContent().type('{enter}') + const link = 'https://nextcloud.com/' + cy.insertLine(link) cy.getContent() .find('.widgets--list', { timeout: 10000 }) @@ -32,8 +32,7 @@ describe('test link marks', function() { }) it('does not show a link preview for links within a paragraph', () => { - cy.getContent().type('Please visit https://nextcloud.com') - cy.getContent().type('{enter}') + cy.insertLine('Please visit https://nextcloud.com') cy.getContent() .find('.widgets--list', { timeout: 10000 }) @@ -49,8 +48,7 @@ describe('test link marks', function() { const link = `${Cypress.env('baseUrl')}/apps/files/file-name?fileId=${id}` cy.clearContent() - cy.getContent() - .type(`${link}{enter}`) + cy.insertLine(link) cy.getContent() .find(`a[href*="${Cypress.env('baseUrl')}"]`) @@ -69,8 +67,7 @@ describe('test link marks', function() { const link = `${Cypress.env('baseUrl')}/file-name?fileId=${id}` cy.clearContent() - cy.getContent() - .type(`${link}{enter}`) + cy.insertLine(link) cy.getContent() .find(`a[href*="${Cypress.env('baseUrl')}"]`) @@ -84,16 +81,14 @@ describe('test link marks', function() { it('without protocol', () => { cy.clearContent() - cy.getContent() - .type('google.com{enter}') + cy.insertLine('google.com') cy.getContent() .find('a[href*="google.com"]') .should('not.exist') }) it('with protocol but without space', () => { - cy.getContent() - .type('https://nextcloud.com') + cy.getContent().type('https://nextcloud.com') cy.getContent() .find('a[href*="nextcloud.com"]') diff --git a/cypress/e2e/sections.spec.js b/cypress/e2e/sections.spec.js index 0571b10952f..dd0d967b1a5 100644 --- a/cypress/e2e/sections.spec.js +++ b/cypress/e2e/sections.spec.js @@ -44,7 +44,7 @@ describe('Content Sections', () => { it('Anchor ID is updated', () => { cy.visitTestFolder() cy.openFile(fileName, { force: true }) - cy.getContent().type('# Heading 1{enter}') + cy.insertLine('# Heading 1') cy.getContent() .find('h1 > a') .should('have.attr', 'id') @@ -83,8 +83,7 @@ describe('Content Sections', () => { cy.visitTestFolder() cy.openFile(fileName, { force: true }) // Issue #2868 - cy.getContent() - .type('# Heading 1{enter}') + cy.insertLine('# Heading 1') cy.getContent() .find('h1 > a') .should('have.attr', 'id') diff --git a/cypress/e2e/sync.spec.js b/cypress/e2e/sync.spec.js index 11f17f41d06..9be6edf9051 100644 --- a/cypress/e2e/sync.spec.js +++ b/cypress/e2e/sync.spec.js @@ -38,7 +38,7 @@ describe('Sync', () => { cy.openTestFile() cy.wait('@sync', { timeout: 10000 }) cy.getContent().find('h2').should('contain', 'Hello world') - cy.getContent().type('{moveToEnd}* Saving the doc saves the doc state{enter}') + cy.insertLine('{moveToEnd}* Saving the doc saves the doc state') cy.wait('@sync', { timeout: 10000 }) }) @@ -77,7 +77,7 @@ describe('Sync', () => { .should('not.contain', 'Document could not be loaded.') // FIXME: There seems to be a bug where typed words maybe lost if not waiting for the new session cy.wait('@syncAfterRecovery', { timeout: 10000 }) - cy.getContent().type('* more content added after the lost connection{enter}') + cy.insertLine('* more content added after the lost connection') cy.wait('@syncAfterRecovery', { timeout: 10000 }) cy.closeFile() cy.testName() @@ -137,7 +137,7 @@ describe('Sync', () => { .should('not.contain', 'Document could not be loaded.') // FIXME: There seems to be a bug where typed words maybe lost if not waiting for the new session cy.wait('@syncAfterRecovery', { timeout: 10000 }) - cy.getContent().type('* more content added after the lost connection{enter}') + cy.insertLine('* more content added after the lost connection') cy.wait('@syncAfterRecovery', { timeout: 10000 }) cy.closeFile() cy.testName() diff --git a/cypress/e2e/viewer.spec.js b/cypress/e2e/viewer.spec.js index 4a640ae5e7c..b5601a3a7f4 100644 --- a/cypress/e2e/viewer.spec.js +++ b/cypress/e2e/viewer.spec.js @@ -95,8 +95,7 @@ describe('Open test.md in viewer', function() { // This used to break with the focus trap that the viewer modal has cy.openFile('empty.md') - cy.getContent() - .type('- test{enter}') + cy.insertLine('- test') // Cypress does not have native tab key support, though this seems to work // for triggering the key handler of tiptap diff --git a/cypress/support/commands.js b/cypress/support/commands.js index f6a4ce32432..0491515a287 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -385,6 +385,11 @@ Cypress.Commands.add('clearContent', () => { cy.getContent() }) +Cypress.Commands.add('insertLine', (line = '') => { + cy.getContent() + .type(`${line}{enter}`) +}) + Cypress.Commands.add('openWorkspace', () => { cy.createDescription() cy.get('#rich-workspace .editor__content').click({ force: true }) diff --git a/src/marks/Link.js b/src/marks/Link.js index 70a9f48c8bc..086e82419bd 100644 --- a/src/marks/Link.js +++ b/src/marks/Link.js @@ -24,6 +24,8 @@ import TipTapLink from '@tiptap/extension-link' import { domHref, parseHref, openLink } from './../helpers/links.js' import { clickHandler, clickPreventer } from '../plugins/link.js' +const PROTOCOLS_TO_LINK_TO = ['http:', 'https:', 'mailto:', 'tel:'] + const Link = TipTapLink.extend({ addOptions() { @@ -59,10 +61,13 @@ const Link = TipTapLink.extend({ renderHTML(options) { const { mark } = options - + const url = new URL(mark.attrs.href, window.location) + const href = PROTOCOLS_TO_LINK_TO.includes(url.protocol) + ? domHref(mark, this.options.relativePath) + : '#' return ['a', { ...mark.attrs, - href: domHref(mark, this.options.relativePath), + href, rel: 'noopener noreferrer nofollow', }, 0] }, diff --git a/src/tests/helpers/links.spec.js b/src/tests/helpers/links.spec.js index dac80a9a594..ba2ef37d1df 100644 --- a/src/tests/helpers/links.spec.js +++ b/src/tests/helpers/links.spec.js @@ -12,10 +12,12 @@ global.OC = { global._oc_webroot = '' +const linkTo = href => domHref({ attrs: { href } }) + describe('Preparing href attributes for the DOM', () => { test('leave empty hrefs alone', () => { - expect(domHref({attrs: {href: ''}})).toBe('') + expect(linkTo('')).toBe('') }) test('leave undefined hrefs alone', () => { @@ -23,22 +25,21 @@ describe('Preparing href attributes for the DOM', () => { }) test('full url', () => { - expect(domHref({attrs: {href: 'https://otherdomain.tld'}})) - .toBe('https://otherdomain.tld') + expect(linkTo('https://otherdomain.tld')).toBe('https://otherdomain.tld') }) test('other protocol', () => { - expect(domHref({attrs: {href: 'mailTo:test@mail.example'}})) + expect(linkTo('mailTo:test@mail.example')) .toBe('mailTo:test@mail.example') }) test('relative link with fileid', () => { - expect(domHref({attrs: {href: 'otherfile?fileId=123'}})) + expect(linkTo('otherfile?fileId=123')) .toBe('/apps/files/?dir=/Wiki&openfile=123#relPath=otherfile') }) test('relative path with ../', () => { - expect(domHref({attrs: {href: '../other/otherfile?fileId=123'}})) + expect(linkTo('../other/otherfile?fileId=123')) .toBe('/apps/files/?dir=/other&openfile=123#relPath=../other/otherfile') }) @@ -48,7 +49,7 @@ describe('Preparing href attributes for the DOM', () => { }) test('absolute path', () => { - expect(domHref({attrs: {href: '/otherfile?fileId=123'}})) + expect(linkTo('/otherfile?fileId=123')) .toBe('/apps/files/?dir=/&openfile=123#relPath=/otherfile') })