Skip to content

Commit 6e2892d

Browse files
remove old link when editing (#5690)
* remove old link when editing Signed-off-by: grnd-alt <github@belakkaf.net> * refactor(links): add insertOrSetLink command Signed-off-by: Max <max@nextcloud.com> --------- Signed-off-by: Max <max@nextcloud.com> Signed-off-by: grnd-alt <github@belakkaf.net> Co-authored-by: Max <max@nextcloud.com>
1 parent 8f7b2fe commit 6e2892d

6 files changed

Lines changed: 262 additions & 20 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { randUser } from '../../utils/index.js'
6+
import { randUser } from '../utils/index.js'
77

88
const user = randUser()
99
const fileName = 'empty.md'

cypress/e2e/marks/Link.spec.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import Markdown from './../../../src/extensions/Markdown.js'
7+
import { Italic, Link } from './../../../src/marks/index.js'
8+
import { createCustomEditor } from './../../support/components.js'
9+
import { loadMarkdown, expectMarkdown } from '../nodes/helpers.js'
10+
11+
describe('Link marks', { retries: 0 }, () => {
12+
const editor = createCustomEditor({
13+
content: '',
14+
extensions: [Markdown, Link, Italic],
15+
})
16+
17+
describe('insertOrSetLink command', { retries: 0 }, () => {
18+
it('is available in commands', () => {
19+
expect(editor.commands).to.have.property('insertOrSetLink')
20+
})
21+
22+
it('can run on normal paragraph', () => {
23+
prepareEditor('hello\n', 3)
24+
expect(editor.can().insertOrSetLink().run()).to.equal(true)
25+
})
26+
27+
it('will insert a link in a normal paragraph', () => {
28+
prepareEditor('hello\n', 3)
29+
editor.commands.insertOrSetLink('https://nextcloud.com', {
30+
href: 'https://nextcloud.com',
31+
})
32+
expectMarkdown(editor, 'he\n\n<https://nextcloud.com>\n\nllo')
33+
})
34+
})
35+
36+
/**
37+
*
38+
* @param {*} input markdown content
39+
* @param {*} position cursor pos
40+
*/
41+
function prepareEditor(input, position = 1) {
42+
loadMarkdown(editor, input)
43+
editor.commands.setTextSelection(position)
44+
}
45+
})

src/components/Menu/ActionInsertLink.vue

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -189,24 +189,7 @@ export default {
189189
// Avoid issues when parsing urls later on in markdown that might be entered in an invalid format (e.g. "mailto: example@example.com")
190190
const href = url.replaceAll(' ', '%20')
191191
const chain = this.$editor.chain()
192-
// Check if any text is selected, if not insert the link using the given text property
193-
if (this.$editor.view.state?.selection.empty) {
194-
chain.insertContent({
195-
type: 'paragraph',
196-
content: [{
197-
type: 'text',
198-
marks: [{
199-
type: 'link',
200-
attrs: {
201-
href,
202-
},
203-
}],
204-
text,
205-
}],
206-
})
207-
} else {
208-
chain.setLink({ href })
209-
}
192+
chain.insertOrSetLink(text, { href })
210193
chain.focus().run()
211194
},
212195
/**

src/marks/Link.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +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'
9+
import { markInputRule, getMarkRange, isMarkActive } from '@tiptap/core'
1010

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

@@ -87,6 +87,49 @@ const Link = TipTapLink.extend({
8787
}),
8888
]
8989
},
90+
addCommands() {
91+
return {
92+
...this.parent?.(),
93+
insertOrSetLink: (text, attrs) => ({ state, chain, commands }) => {
94+
// Check if any text is selected,
95+
// if not insert the link using the given text property
96+
if (state.selection.empty) {
97+
if (isMarkActive(state, this.name)) {
98+
99+
// get current href to check what to replace, assumes there's only one link mark on the anchor
100+
let href = ''
101+
state.selection.$anchor.marks().forEach(item => {
102+
if (item.attrs.href && item.type.name === 'link') {
103+
href = item.attrs.href
104+
}
105+
})
106+
commands.deleteRange(getMarkRange(state.selection.$anchor, state.schema.marks.link, { href }))
107+
return chain().insertContent({
108+
type: 'text',
109+
marks: [{
110+
type: 'link',
111+
attrs,
112+
}],
113+
text,
114+
})
115+
}
116+
return chain().insertContent({
117+
type: 'paragraph',
118+
content: [{
119+
type: 'text',
120+
marks: [{
121+
type: 'link',
122+
attrs,
123+
}],
124+
text,
125+
}],
126+
})
127+
} else {
128+
return commands.setLink(attrs)
129+
}
130+
},
131+
}
132+
},
90133

91134
addProseMirrorPlugins() {
92135
const plugins = this.parent()

src/tests/marks/Link.spec.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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()).toMatchSnapshot()
23+
})
24+
25+
it('Should only update link the anchor is on', () => {
26+
const editor = createCustomEditor(
27+
'<p><a href="nextcloud.com">Test</a><a href="not-nextcloud.com">second link</a></p>',
28+
[Link],
29+
)
30+
editor.commands.setTextSelection(3)
31+
editor.commands.insertOrSetLink('updated.de', { href: 'updated.de' })
32+
expect(editor.getJSON()).toMatchSnapshot()
33+
})
34+
35+
it('should insert new link if none at anchor', () => {
36+
const editor = createCustomEditor(
37+
'<p><a href="nextcloud.com">Test</a> HELLO WORLD</p>',
38+
[Link],
39+
)
40+
editor.commands.setTextSelection(10)
41+
editor.commands.insertOrSetLink('new link', { href: 'https://nextcloud.com' })
42+
expect(editor.getJSON()).toMatchSnapshot()
43+
})
44+
})
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`Link extension integrated in the editor > Should only update link the anchor is on 1`] = `
4+
{
5+
"content": [
6+
{
7+
"content": [
8+
{
9+
"marks": [
10+
{
11+
"attrs": {
12+
"href": "updated.de",
13+
"title": null,
14+
},
15+
"type": "link",
16+
},
17+
],
18+
"text": "updated.de",
19+
"type": "text",
20+
},
21+
{
22+
"marks": [
23+
{
24+
"attrs": {
25+
"href": "not-nextcloud.com",
26+
"title": null,
27+
},
28+
"type": "link",
29+
},
30+
],
31+
"text": "second link",
32+
"type": "text",
33+
},
34+
],
35+
"type": "paragraph",
36+
},
37+
],
38+
"type": "doc",
39+
}
40+
`;
41+
42+
exports[`Link extension integrated in the editor > should insert new link if none at anchor 1`] = `
43+
{
44+
"content": [
45+
{
46+
"content": [
47+
{
48+
"marks": [
49+
{
50+
"attrs": {
51+
"href": "nextcloud.com",
52+
"title": null,
53+
},
54+
"type": "link",
55+
},
56+
],
57+
"text": "Test",
58+
"type": "text",
59+
},
60+
{
61+
"text": " HELL",
62+
"type": "text",
63+
},
64+
],
65+
"type": "paragraph",
66+
},
67+
{
68+
"content": [
69+
{
70+
"marks": [
71+
{
72+
"attrs": {
73+
"href": "https://nextcloud.com",
74+
"title": null,
75+
},
76+
"type": "link",
77+
},
78+
],
79+
"text": "new link",
80+
"type": "text",
81+
},
82+
],
83+
"type": "paragraph",
84+
},
85+
{
86+
"content": [
87+
{
88+
"text": "O WORLD",
89+
"type": "text",
90+
},
91+
],
92+
"type": "paragraph",
93+
},
94+
],
95+
"type": "doc",
96+
}
97+
`;
98+
99+
exports[`Link extension integrated in the editor > should update link if anchor has mark 1`] = `
100+
{
101+
"content": [
102+
{
103+
"content": [
104+
{
105+
"marks": [
106+
{
107+
"attrs": {
108+
"href": "updated.de",
109+
"title": null,
110+
},
111+
"type": "link",
112+
},
113+
],
114+
"text": "updated.de",
115+
"type": "text",
116+
},
117+
{
118+
"text": " HELLO WORLD",
119+
"type": "text",
120+
},
121+
],
122+
"type": "paragraph",
123+
},
124+
],
125+
"type": "doc",
126+
}
127+
`;

0 commit comments

Comments
 (0)