Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check link protocol #6486

Merged
merged 5 commits into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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('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 = {}) {

Check warning on line 30 in cypress/e2e/nodes/Links.spec.js

View workflow job for this annotation

GitHub Actions / NPM lint

Missing JSDoc comment
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 @@

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 @@

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 @@
})

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 @@

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 @@

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 @@

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 @@

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')
})
})
Loading