Skip to content

Commit a333bb3

Browse files
committed
fix: replace link correctly
Signed-off-by: grnd-alt <github@belakkaf.net>
1 parent 5636ec1 commit a333bb3

3 files changed

Lines changed: 131 additions & 75 deletions

File tree

cypress/e2e/marks/Link.spec.js

Lines changed: 10 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
1-
/* eslint-disable no-unused-expressions */
21
/**
3-
* @copyright Copyright (c) 2024 Max <max@nextcloud.com>
4-
*
5-
* @author Max <max@nextcloud.com>
6-
*
7-
* @license AGPL-3.0-or-later
8-
*
9-
* This program is free software: you can redistribute it and/or modify
10-
* it under the terms of the GNU Affero General Public License as
11-
* published by the Free Software Foundation, either version 3 of the
12-
* License, or (at your option) any later version.
13-
*
14-
* This program is distributed in the hope that it will be useful,
15-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17-
* GNU Affero General Public License for more details.
18-
*
19-
* You should have received a copy of the GNU Affero General Public License
20-
* along with this program. If not, see <http://www.gnu.org/licenses/>.
21-
*
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
224
*/
235

246
import Markdown from './../../../src/extensions/Markdown.js'
@@ -27,68 +9,37 @@ import { createCustomEditor } from './../../support/components.js'
279
import { loadMarkdown, expectMarkdown } from '../nodes/helpers.js'
2810

2911
describe('Link marks', { retries: 0 }, () => {
30-
3112
const editor = createCustomEditor({
3213
content: '',
33-
extensions: [
34-
Markdown,
35-
Link,
36-
Italic,
37-
],
14+
extensions: [Markdown, Link, Italic],
3815
})
3916

4017
describe('insertOrSetLink command', { retries: 0 }, () => {
41-
4218
it('is available in commands', () => {
4319
expect(editor.commands).to.have.property('insertOrSetLink')
4420
})
4521

4622
it('can run on normal paragraph', () => {
4723
prepareEditor('hello\n', 3)
48-
expect(editor.can().insertOrSetLink()).to.be.ok
24+
expect(editor.can().insertOrSetLink()).toBe(true)
4925
})
5026

5127
it('will insert a link in a normal paragraph', () => {
5228
prepareEditor('hello\n', 3)
53-
editor.commands.insertOrSetLink('https://nextcloud.com', { href: 'https://nextcloud.com' })
29+
editor.commands.insertOrSetLink('https://nextcloud.com', {
30+
href: 'https://nextcloud.com',
31+
})
5432
expectMarkdown(editor, 'he\n\n<https://nextcloud.com>\n\nllo')
5533
})
56-
5734
})
5835

59-
/**
60-
* Expect a link in the editor.
61-
*/
62-
function expectLink() {
63-
expect(getParentNode().type.name).to.equal('paragraph')
64-
expect(getParentNode().attrs.href).to.equal('https://nextcloud.com')
65-
expect(getMark().attrs.href).to.equal('https://nextcloud.com')
66-
}
67-
68-
/**
69-
*
70-
*/
71-
function getParentNode() {
72-
const { state: { selection } } = editor
73-
return selection.$head.parent
74-
}
75-
7636
/**
7737
*
78-
*/
79-
function getMark() {
80-
const { state: { selection } } = editor
81-
console.info(selection.$head)
82-
return selection.$head.nodeAfter.marks[0]
83-
}
84-
85-
/**
86-
*
87-
* @param input
38+
* @param {*} input markdown content
39+
* @param {*} position cursor pos
8840
*/
8941
function prepareEditor(input, position = 1) {
9042
loadMarkdown(editor, input)
91-
editor.commands.setTextSelection( position )
43+
editor.commands.setTextSelection(position)
9244
}
93-
9445
})

src/marks/Link.js

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { markInputRule } from '@tiptap/core'
76
import TipTapLink from '@tiptap/extension-link'
87
import { domHref, parseHref } from './../helpers/links.js'
98
import { linkClicking } from '../plugins/links.js'
10-
import { isMarkActive } from '@tiptap/core'
9+
import { markInputRule, getMarkRange, isMarkActive } from '@tiptap/core'
1110

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

@@ -90,28 +89,28 @@ const Link = TipTapLink.extend({
9089
},
9190
addCommands() {
9291
return {
93-
/**
94-
* Update the target of existing links.
95-
* Insert a link if there currently is none.
96-
*
97-
*/
9892
insertOrSetLink: (text, attrs) => ({ state, chain, commands }) => {
9993
// Check if any text is selected,
10094
// if not insert the link using the given text property
10195
if (state.selection.empty) {
10296
if (isMarkActive(state, this.name)) {
103-
commands.deleteNode('paragraph')
97+
98+
// get current href to check what to replace, assumes there's only one link mark on the anchor
99+
let href = ''
100+
state.selection.$anchor.marks().forEach(item => {
101+
if (item.attrs.href && item.type.name === 'link') {
102+
href = item.attrs.href
103+
}
104+
})
105+
commands.deleteRange(getMarkRange(state.selection.$anchor, state.schema.marks.link, { href }))
104106
}
105107
return chain().insertContent({
106-
type: 'paragraph',
107-
content: [{
108-
type: 'text',
109-
marks: [{
110-
type: 'link',
111-
attrs,
112-
}],
113-
text,
108+
type: 'text',
109+
marks: [{
110+
type: 'link',
111+
attrs,
114112
}],
113+
text,
115114
})
116115
} else {
117116
return commands.setLink(attrs)

src/tests/marks/Link.spec.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
import Link from './../../marks/Link.js'
6+
import Underline from '../../marks/Underline.js'
7+
import createCustomEditor from '../testHelpers/createCustomEditor.ts'
8+
9+
describe('Link extension integrated in the editor', () => {
10+
it('should have link available in commands', () => {
11+
const editor = createCustomEditor('<p><a href="nextcloud.com">Test</a> HELLO WORLD</p>', [Link])
12+
expect(editor.commands).toHaveProperty('insertOrSetLink')
13+
})
14+
15+
it('should update link if anchor has mark', () => {
16+
const editor = createCustomEditor(
17+
'<p><a href="nextcloud.com">Te<u>s</u>t</a> HELLO WORLD</p>',
18+
[Link, Underline],
19+
)
20+
editor.commands.setTextSelection(3)
21+
editor.commands.insertOrSetLink('updated.de', { href: 'updated.de' })
22+
expect(editor.getJSON()).toEqual({
23+
content: [
24+
{
25+
content: [
26+
{
27+
marks: [
28+
{ attrs: { href: 'updated.de', title: null }, type: 'link' },
29+
],
30+
text: 'updated.de',
31+
type: 'text',
32+
},
33+
{ text: ' HELLO WORLD', type: 'text' },
34+
],
35+
type: 'paragraph',
36+
},
37+
],
38+
type: 'doc',
39+
})
40+
})
41+
42+
it('Should only update link the anchor is on', () => {
43+
const editor = createCustomEditor(
44+
'<p><a href="nextcloud.com">Test</a><a href="not-nextcloud.com">second link</a></p>',
45+
[Link],
46+
)
47+
editor.commands.setTextSelection(3)
48+
editor.commands.insertOrSetLink('updated.de', { href: 'updated.de' })
49+
expect(editor.getJSON()).toEqual({
50+
content: [
51+
{
52+
content: [
53+
{
54+
marks: [
55+
{ attrs: { href: 'updated.de', title: null }, type: 'link' },
56+
],
57+
text: 'updated.de',
58+
type: 'text',
59+
},
60+
{
61+
marks: [
62+
{
63+
attrs: {
64+
href: 'not-nextcloud.com',
65+
title: null,
66+
},
67+
type: 'link',
68+
},
69+
],
70+
text: 'second link',
71+
type: 'text',
72+
},
73+
],
74+
type: 'paragraph',
75+
},
76+
],
77+
type: 'doc',
78+
})
79+
})
80+
81+
it('should insert new link if none at anchor', () => {
82+
const editor = createCustomEditor(
83+
'<p><a href="nextcloud.com">Test</a> HELLO WORLD</p>',
84+
[Link],
85+
)
86+
editor.commands.setTextSelection(10)
87+
expect(editor.getJSON()).toEqual({
88+
content: [
89+
{
90+
content: [
91+
{
92+
marks: [
93+
{ attrs: { href: 'nextcloud.com', title: null }, type: 'link' },
94+
],
95+
text: 'Test',
96+
type: 'text',
97+
},
98+
{ text: ' HELLO WORLD', type: 'text' },
99+
],
100+
type: 'paragraph',
101+
},
102+
],
103+
type: 'doc',
104+
})
105+
})
106+
})

0 commit comments

Comments
 (0)