Skip to content

Commit

Permalink
Merge pull request #6486 from nextcloud/fix/check-link-protocol
Browse files Browse the repository at this point in the history
Check link protocol
  • Loading branch information
max-nextcloud authored Oct 2, 2024
2 parents ac87420 + bf045fd commit 86fb317
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 62 deletions.
6 changes: 2 additions & 4 deletions cypress/e2e/directediting.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,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')
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/nodes/CodeBlock.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,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')
})
Expand All @@ -108,7 +108,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
Expand All @@ -123,7 +123,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
Expand Down
82 changes: 48 additions & 34 deletions cypress/e2e/nodes/Links.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

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() {
Expand All @@ -26,14 +26,17 @@ describe('test link marks', function() {
})

describe('link bubble', function() {
it('shows a link preview in the bubble after clicking link', () => {
const link = 'https://nextcloud.com/'
cy.getContent()
.type(`${link}{enter}`)

function clickLink(link, options = {}) {
cy.getContent()
.find(`a[href*="${link}"]`)
.click()
.click(options)
}

it('shows a link preview in the bubble after clicking link', () => {
const link = 'https://nextcloud.com/'
cy.insertLine(link)
clickLink(link)

cy.get('.link-view-bubble .widget-default', { timeout: 10000 })
.find('.widget-default--name')
Expand All @@ -43,8 +46,7 @@ describe('test link marks', function() {

it('shows a link preview in the bubble after browsing to link', () => {
const link = 'https://nextcloud.com/'
cy.getContent()
.type(`${link}{enter}`)
cy.insertLine(link)
cy.getContent()
.find(`a[href*="${link}"]`)

Expand All @@ -58,12 +60,9 @@ describe('test link marks', function() {

it('closes the link bubble when clicking elsewhere', () => {
const link = 'https://nextcloud.com/'
cy.getContent()
.type(`${link}{enter}`)
cy.getContent()
.find(`a[href*="${link}"]`)
cy.getContent()
.type('{upArrow}')
cy.insertLine(link)
clickLink(link)

cy.get('.link-view-bubble .widget-default', { timeout: 10000 })
.find('.widget-default--name')
.contains('Nextcloud')
Expand All @@ -75,10 +74,8 @@ describe('test link marks', function() {
})

it('allows to edit a link in the bubble', () => {
cy.getContent()
.type('https://example.org{enter}')
cy.getContent()
.type('{upArrow}{rightArrow}')
cy.insertLine('https://example.org')
clickLink('https://example.org')

cy.get('.link-view-bubble button[title="Edit link"]')
.click()
Expand All @@ -96,10 +93,8 @@ describe('test link marks', function() {

it('allows to remove a link in the bubble', () => {
const link = 'https://nextcloud.com'
cy.getContent()
.type(`${link}{enter}`)
cy.getContent()
.type('{upArrow}{rightArrow}')
cy.insertLine(link)
clickLink(link)

cy.get('.link-view-bubble button[title="Remove link"]')
.click()
Expand All @@ -112,17 +107,39 @@ describe('test link marks', function() {

it('Ctrl-click on a link opens a new tab', () => {
const link = 'https://nextcloud.com/'
cy.getContent()
.type(`${link}{enter}`)
cy.insertLine(link)

cy.getContent()
.find(`a[href*="${link}"]`)
.click({ ctrlKey: true })
clickLink(link, { ctrlKey: true })

cy.get('@winOpen')
.should('have.been.calledOnce')
.should('have.been.calledWith', link)
})

it('Handles typed in markdown links with text', () => {
const link = 'https://nextcloud.com/'
cy.insertLine(`[text](${link})`)
clickLink(link)
cy.get('.link-view-bubble .widget-default', { timeout: 10000 })
.find('.widget-default--name')
.contains('Nextcloud')
cy.get('.link-view-bubble a')
.should('have.attr', 'href', link)
})

it('Leaves out link to other protocols', () => {
const link = 'other://protocol'
cy.insertLine(`[text](${link})`)
cy.getContent()
.find(`a[href*="${link}"]`)
.should('not.exist')
clickLink('#')
cy.get('.link-view-bubble__title', { timeout: 10000 })
.contains('other://protocol')
cy.get('.link-view-bubble a')
.should('not.exist')
})

})

describe('autolink', function() {
Expand All @@ -133,8 +150,7 @@ describe('test link marks', function() {

const link = `${Cypress.env('baseUrl')}/apps/files/?dir=/&openfile=${id}#relPath=/${fileName}`
cy.clearContent()
cy.getContent()
.type(`${link}{enter}`)
cy.insertLine(link)

cy.getContent()
.find(`a[href*="${Cypress.env('baseUrl')}"]`)
Expand All @@ -143,16 +159,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"]')
Expand Down
5 changes: 2 additions & 3 deletions cypress/e2e/sections.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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')
Expand Down Expand Up @@ -89,8 +89,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')
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/sync.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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 })
})

Expand Down Expand Up @@ -60,7 +60,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()
Expand Down Expand Up @@ -120,7 +120,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()
Expand Down
3 changes: 1 addition & 2 deletions cypress/e2e/viewer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,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
Expand Down
5 changes: 5 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,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 })
Expand Down
9 changes: 8 additions & 1 deletion src/components/Link/LinkBubbleView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
</div>

<!-- link preview -->
<NcReferenceList v-else-if="href"
<NcReferenceList v-else-if="showPreview"
ref="referencelist"
:text="sanitizedHref"
:limit="1"
Expand All @@ -97,6 +97,8 @@ import PencilIcon from 'vue-material-design-icons/Pencil.vue'

import CopyToClipboardMixin from '../../mixins/CopyToClipboardMixin.js'

const PROTOCOLS_WITH_PREVIEW = ['http:', 'https:']

export default {
name: 'LinkBubbleView',

Expand Down Expand Up @@ -162,6 +164,11 @@ export default {
title() {
return this.referenceTitle ? this.referenceTitle : this.sanitizedHref
},

showPreview() {
const url = new URL(this.href, window.location)
return this.href && PROTOCOLS_WITH_PREVIEW.includes(url.protocol)
},
},

watch: {
Expand Down
8 changes: 7 additions & 1 deletion src/marks/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import TipTapLink from '@tiptap/extension-link'
import { domHref, parseHref } from './../helpers/links.js'
import { linkClicking } from '../plugins/links.js'

const PROTOCOLS_TO_LINK_TO = ['http:', 'https:', 'mailto:', 'tel:']

const extractHrefFromMatch = (match) => {
return { href: match.groups.href }
}
Expand Down Expand Up @@ -58,9 +60,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,
'data-md-href': mark.attrs.href,
rel: 'noopener noreferrer nofollow',
}, 0]
Expand Down
22 changes: 11 additions & 11 deletions src/tests/helpers/links.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,43 +21,43 @@ global._oc_webroot = ''
jest.mock('@nextcloud/initial-state')
loadState.mockImplementation((app, key) => 'files')

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', () => {
expect(domHref({attrs: {}})).toBe(undefined)
})

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'}}))
.toBe('mailTo:test@mail.example')
test('other protocols', () => {
expect(linkTo('mailto:name@otherdomain.tld')).toBe('mailto:name@otherdomain.tld')
})

test('relative link with fileid (old format from file picker)', () => {
expect(domHref({attrs: {href: 'otherfile?fileId=123'}}))
expect(linkTo('otherfile?fileId=123'))
.toBe('http://localhost/f/123')
})

test('relative path with ../ (old format from file picker)', () => {
expect(domHref({attrs: {href: '../other/otherfile?fileId=123'}}))
expect(linkTo('../other/otherfile?fileId=123'))
.toBe('http://localhost/f/123')
})

test('absolute path (old format from file picker)', () => {
expect(domHref({attrs: {href: '/other/otherfile?fileId=123'}}))
expect(linkTo('/other/otherfile?fileId=123'))
.toBe('http://localhost/f/123')
})

test('absolute path (old format from file picker)', () => {
expect(domHref({attrs: {href: '/otherfile?fileId=123'}}))
expect(linkTo('/otherfile?fileId=123'))
.toBe('http://localhost/f/123')
})

Expand Down Expand Up @@ -139,7 +139,7 @@ describe('Preparing href attributes for the DOM in Collectives app', () => {
})

test('relative link with fileid in Collectives', () => {
expect(domHref({attrs: {href: 'otherfile?fileId=123'}}))
expect(linkTo('otherfile?fileId=123'))
.toBe('otherfile?fileId=123')
})
})

0 comments on commit 86fb317

Please sign in to comment.