feat(extension-link): add validate option to link extension

#2779
This commit is contained in:
Dominik Biedebach 2022-05-13 11:36:46 +02:00 committed by Dominik
parent ccc37d5f24
commit 23e67adfa7
10 changed files with 276 additions and 9 deletions

View File

@ -0,0 +1,32 @@
import './styles.scss'
import React from 'react'
import Link from '@tiptap/extension-link'
import { EditorContent, useEditor } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
export default () => {
const editor = useEditor({
extensions: [
StarterKit,
Link.configure({
validate: link => /^https?:\/\//.test(link),
}),
],
content: `
<p>Hey! Try to type in url with and without a http/s protocol. - Links without a protocol should not get auto linked</p>
`,
editorProps: {
attributes: {
spellcheck: 'false',
},
},
})
return (
<div>
<EditorContent editor={editor} />
</div>
)
}

View File

@ -0,0 +1,42 @@
context('/src/Examples/AutolinkValidation/React/', () => {
before(() => {
cy.visit('/src/Examples/AutolinkValidation/React/')
})
beforeEach(() => {
cy.get('.ProseMirror').type('{selectall}{backspace}')
})
const validLinks = [
'https://tiptap.dev',
'http://tiptap.dev',
'https://www.tiptap.dev/',
'http://www.tiptap.dev/',
]
const invalidLinks = [
'tiptap.dev',
'www.tiptap.dev',
]
validLinks.forEach(link => {
it(`${link} should get autolinked`, () => {
cy.get('.ProseMirror').type(link)
cy.get('.ProseMirror').should('have.text', link)
cy.get('.ProseMirror')
.find('a')
.should('have.length', 1)
.should('have.attr', 'href', link)
})
})
invalidLinks.forEach(link => {
it(`${link} should NOT get autolinked`, () => {
cy.get('.ProseMirror').type(link)
cy.get('.ProseMirror').should('have.text', link)
cy.get('.ProseMirror')
.find('a')
.should('have.length', 0)
})
})
})

View File

@ -0,0 +1,54 @@
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}

View File

@ -0,0 +1,7 @@
context('/src/Examples/AutolinkValidation/Vue/', () => {
before(() => {
cy.visit('/src/Examples/AutolinkValidation/Vue/')
})
// TODO: Write tests
})

View File

@ -0,0 +1,101 @@
<template>
<editor-content :editor="editor" />
</template>
<script>
import { Editor, EditorContent } from '@tiptap/vue-3'
import StarterKit from '@tiptap/starter-kit'
import Link from '@tiptap/extension-link'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
StarterKit,
Link.configure({
validate: link => /^https?:\/\//.test(link),
}),
],
content: `
<p>Hey! Try to type in url with and without a http/s protocol. - Links without a protocol should not get auto linked</p>
`,
editorProps: {
attributes: {
spellcheck: 'false',
},
},
})
},
beforeUnmount() {
this.editor.destroy()
},
}
</script>
<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
ul,
ol {
padding: 0 1rem;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
}
img {
max-width: 100%;
height: auto;
}
hr {
margin: 1rem 0;
}
blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
}
</style>

View File

@ -64,6 +64,18 @@ Link.configure({
})
```
### validate
A function that validates every autolinked link. If it exists, it will be called with the link href as argument. If it returns `false`, the link will be removed.
Can be used to set rules for example excluding or including certain domains, tlds, etc.
```js
// only autolink urls with a protocol
Link.configure({
validate: href => /^https?:\/\//.test(href),
})
```
## Commands
### setLink()

View File

@ -1,15 +1,17 @@
import {
getMarksBetween,
findChildrenInRange,
combineTransactionSteps,
getChangedRanges,
} from '@tiptap/core'
import { Plugin, PluginKey } from 'prosemirror-state'
import { MarkType } from 'prosemirror-model'
import { find, test } from 'linkifyjs'
import { MarkType } from 'prosemirror-model'
import { Plugin, PluginKey } from 'prosemirror-state'
import {
combineTransactionSteps,
findChildrenInRange,
getChangedRanges,
getMarksBetween,
} from '@tiptap/core'
type AutolinkOptions = {
type: MarkType,
validate?: (url: string) => boolean,
}
export function autolink(options: AutolinkOptions): Plugin {
@ -70,6 +72,13 @@ export function autolink(options: AutolinkOptions): Plugin {
find(text)
.filter(link => link.isLink)
.filter(link => {
if (options.validate) {
return options.validate(link.value)
}
return true
})
// calculate link position
.map(link => ({
...link,

View File

@ -1,5 +1,7 @@
import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
import { find } from 'linkifyjs'
import { Mark, markPasteRule, mergeAttributes } from '@tiptap/core'
import { autolink } from './helpers/autolink'
import { clickHandler } from './helpers/clickHandler'
import { pasteHandler } from './helpers/pasteHandler'
@ -21,6 +23,12 @@ export interface LinkOptions {
* A list of HTML attributes to be rendered.
*/
HTMLAttributes: Record<string, any>,
/**
* A validation function that modifies link verification for the auto linker.
* @param url - The url to be validated.
* @returns - True if the url is valid, false otherwise.
*/
validate?: (url: string) => boolean,
}
declare module '@tiptap/core' {
@ -63,6 +71,7 @@ export const Link = Mark.create<LinkOptions>({
rel: 'noopener noreferrer nofollow',
class: null,
},
validate: undefined,
}
},
@ -143,6 +152,7 @@ export const Link = Mark.create<LinkOptions>({
if (this.options.autolink) {
plugins.push(autolink({
type: this.type,
validate: this.options.validate,
}))
}