mirror of
https://github.com/ueberdosis/tiptap.git
synced 2025-06-07 09:25:29 +08:00
Merge branch 'main' into develop
This commit is contained in:
commit
65cef599bc
@ -19,7 +19,7 @@
|
||||
"remixicon": "^2.5.0",
|
||||
"shiki": "^0.10.0",
|
||||
"simplify-js": "^1.2.4",
|
||||
"y-prosemirror": "^1.2.5",
|
||||
"y-prosemirror": "^1.2.6",
|
||||
"y-webrtc": "^10.3.0",
|
||||
"yjs": "^13.6.11"
|
||||
},
|
||||
|
@ -20,6 +20,7 @@ export default () => {
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
autolink: true,
|
||||
defaultProtocol: 'https',
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
|
@ -52,6 +52,15 @@ context('/src/Marks/Link/React/', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should allow exiting the link once set', () => {
|
||||
cy.get('.tiptap').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#" target="_self">Example Text2</a></p>')
|
||||
cy.get('.tiptap').type('{rightArrow}')
|
||||
|
||||
cy.get('button:first').should('not.have.class', 'is-active')
|
||||
})
|
||||
})
|
||||
|
||||
it('detects autolinking', () => {
|
||||
cy.get('.tiptap').type('https://example.com ').find('a').should('contain', 'https://example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
@ -62,6 +71,16 @@ context('/src/Marks/Link/React/', () => {
|
||||
.should('have.attr', 'href', 'https://tiptap4u.com')
|
||||
})
|
||||
|
||||
it('uses the default protocol', () => {
|
||||
cy.get('.tiptap').type('example.com ').find('a').should('contain', 'example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
})
|
||||
|
||||
it('uses a non-default protocol if present', () => {
|
||||
cy.get('.tiptap').type('http://example.com ').find('a').should('contain', 'http://example.com')
|
||||
.should('have.attr', 'href', 'http://example.com')
|
||||
})
|
||||
|
||||
it('detects a pasted URL within a text', () => {
|
||||
cy.get('.tiptap')
|
||||
.paste({
|
||||
|
@ -63,6 +63,15 @@ context('/src/Marks/Link/Vue/', () => {
|
||||
.should('have.attr', 'href', 'https://example2.com')
|
||||
})
|
||||
|
||||
it('should allow exiting the link once set', () => {
|
||||
cy.get('.tiptap').then(([{ editor }]) => {
|
||||
editor.commands.setContent('<p><a href="#" target="_self">Example Text2</a></p>')
|
||||
cy.get('.tiptap').type('{rightArrow}')
|
||||
|
||||
cy.get('button:first').should('not.have.class', 'is-active')
|
||||
})
|
||||
})
|
||||
|
||||
it('detects autolinking', () => {
|
||||
cy.get('.tiptap').type('https://example.com ').find('a').should('contain', 'https://example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
@ -73,6 +82,16 @@ context('/src/Marks/Link/Vue/', () => {
|
||||
.should('have.attr', 'href', 'https://tiptap4u.com')
|
||||
})
|
||||
|
||||
it('uses the default protocol', () => {
|
||||
cy.get('.tiptap').type('example.com ').find('a').should('contain', 'example.com')
|
||||
.should('have.attr', 'href', 'https://example.com')
|
||||
})
|
||||
|
||||
it('uses a non-default protocol if present', () => {
|
||||
cy.get('.tiptap').type('http://example.com ').find('a').should('contain', 'http://example.com')
|
||||
.should('have.attr', 'href', 'http://example.com')
|
||||
})
|
||||
|
||||
it('detects a pasted URL with query params', () => {
|
||||
cy.get('.tiptap')
|
||||
.type('{backspace}')
|
||||
|
@ -38,6 +38,7 @@ export default {
|
||||
Code,
|
||||
Link.configure({
|
||||
openOnClick: false,
|
||||
defaultProtocol: 'https',
|
||||
}),
|
||||
],
|
||||
content: `
|
||||
|
@ -27,7 +27,7 @@ Most of the core extensions register their own keyboard shortcuts. Depending on
|
||||
| Bold | `Control` `B` | `Cmd` `B` |
|
||||
| Italicize | `Control` `I` | `Cmd` `I` |
|
||||
| Underline | `Control` `U` | `Cmd` `U` |
|
||||
| Strikethrough | `Control` `Shift` `X` | `Cmd` `Shift` `X` |
|
||||
| Strikethrough | `Control` `Shift` `S` | `Cmd` `Shift` `S` |
|
||||
| Highlight | `Control` `Shift` `H` | `Cmd` `Shift` `H` |
|
||||
| Code | `Control` `E` | `Cmd` `E` |
|
||||
|
||||
|
@ -76,6 +76,20 @@ Link.configure({
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
### default protocol
|
||||
The default protocol used by `linkOnPaste` and `autolink` when no protocol is defined.
|
||||
|
||||
By default, the href generated for example.com is http://example.com and this option allows that protocol to be customized.
|
||||
|
||||
Default: `http`
|
||||
|
||||
```js
|
||||
Link.configure({
|
||||
defaultProtocol: 'https',
|
||||
})
|
||||
```
|
||||
|
||||
### HTMLAttributes
|
||||
Custom HTML attributes that should be added to the rendered HTML tag.
|
||||
|
||||
|
16
package-lock.json
generated
16
package-lock.json
generated
@ -67,7 +67,7 @@
|
||||
"remixicon": "^2.5.0",
|
||||
"shiki": "^0.10.0",
|
||||
"simplify-js": "^1.2.4",
|
||||
"y-prosemirror": "^1.2.5",
|
||||
"y-prosemirror": "^1.2.6",
|
||||
"y-webrtc": "^10.3.0",
|
||||
"yjs": "^13.6.11"
|
||||
},
|
||||
@ -20160,9 +20160,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/y-prosemirror": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.5.tgz",
|
||||
"integrity": "sha512-T/JATxC8P2Dbvq/dAiaiztD1a8KEwRP8oLRlT8YlaZdNlLGE1Ea0IJ8If25UlDYmk+4+uqLbqT/S+dzUmwwgbA==",
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.6.tgz",
|
||||
"integrity": "sha512-rGz8kX4v/uFJrLaqZvsezY1JGN/zTDSPMO76zRbNcpE63OEiw2PBCEQi9ZlfbEwgCMoeJLUT+otNyO/Oj73TGQ==",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.42"
|
||||
},
|
||||
@ -20478,7 +20478,7 @@
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.5.0-beta.1",
|
||||
"@tiptap/pm": "^2.5.0-beta.1",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@ -20487,7 +20487,7 @@
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.0.0",
|
||||
"@tiptap/pm": "^2.0.0",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"packages/extension-collaboration-cursor": {
|
||||
@ -20496,7 +20496,7 @@
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.5.0-beta.1",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
@ -20504,7 +20504,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.0.0",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
}
|
||||
},
|
||||
"packages/extension-color": {
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { MarkType, NodeType } from '@tiptap/pm/model'
|
||||
import {
|
||||
Mark, MarkType, Node, NodeType,
|
||||
} from '@tiptap/pm/model'
|
||||
import { SelectionRange } from '@tiptap/pm/state'
|
||||
|
||||
import { getMarkType } from '../helpers/getMarkType.js'
|
||||
import { getNodeType } from '../helpers/getNodeType.js'
|
||||
@ -51,37 +54,49 @@ export const updateAttributes: RawCommands['updateAttributes'] = (typeOrName, at
|
||||
}
|
||||
|
||||
if (dispatch) {
|
||||
tr.selection.ranges.forEach(range => {
|
||||
let lastPos: number | undefined
|
||||
let lastNode: Node | undefined
|
||||
let trimmedFrom: number
|
||||
let trimmedTo: number
|
||||
|
||||
tr.selection.ranges.forEach((range: SelectionRange) => {
|
||||
const from = range.$from.pos
|
||||
const to = range.$to.pos
|
||||
|
||||
state.doc.nodesBetween(from, to, (node, pos) => {
|
||||
state.doc.nodesBetween(from, to, (node: Node, pos: number) => {
|
||||
if (nodeType && nodeType === node.type) {
|
||||
tr.setNodeMarkup(pos, undefined, {
|
||||
...node.attrs,
|
||||
...attributes,
|
||||
})
|
||||
}
|
||||
|
||||
if (markType && node.marks.length) {
|
||||
node.marks.forEach(mark => {
|
||||
if (markType === mark.type) {
|
||||
const trimmedFrom = Math.max(pos, from)
|
||||
const trimmedTo = Math.min(pos + node.nodeSize, to)
|
||||
|
||||
tr.addMark(
|
||||
trimmedFrom,
|
||||
trimmedTo,
|
||||
markType.create({
|
||||
...mark.attrs,
|
||||
...attributes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
trimmedFrom = Math.max(pos, from)
|
||||
trimmedTo = Math.min(pos + node.nodeSize, to)
|
||||
lastPos = pos
|
||||
lastNode = node
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
if (lastNode) {
|
||||
|
||||
if (lastPos !== undefined) {
|
||||
tr.setNodeMarkup(lastPos, undefined, {
|
||||
...lastNode.attrs,
|
||||
...attributes,
|
||||
})
|
||||
}
|
||||
|
||||
if (markType && lastNode.marks.length) {
|
||||
lastNode.marks.forEach((mark: Mark) => {
|
||||
if (markType === mark.type) {
|
||||
tr.addMark(
|
||||
trimmedFrom,
|
||||
trimmedTo,
|
||||
markType.create({
|
||||
...mark.attrs,
|
||||
...attributes,
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
|
@ -189,7 +189,7 @@ export class BubbleMenuView {
|
||||
|
||||
update(view: EditorView, oldState?: EditorState) {
|
||||
const { state } = view
|
||||
const hasValidSelection = state.selection.$from.pos !== state.selection.$to.pos
|
||||
const hasValidSelection = state.selection.from !== state.selection.to
|
||||
|
||||
if (this.updateDelay > 0 && hasValidSelection) {
|
||||
this.handleDebouncedUpdate(view, oldState)
|
||||
|
@ -30,11 +30,11 @@
|
||||
],
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.5.0-beta.1",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.0.0",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -31,12 +31,12 @@
|
||||
"devDependencies": {
|
||||
"@tiptap/core": "^2.5.0-beta.1",
|
||||
"@tiptap/pm": "^2.5.0-beta.1",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tiptap/core": "^2.0.0",
|
||||
"@tiptap/pm": "^2.0.0",
|
||||
"y-prosemirror": "^1.2.5"
|
||||
"y-prosemirror": "^1.2.6"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -33,6 +33,7 @@ function isValidLinkStructure(tokens: Array<ReturnType<MultiToken['toObject']>>)
|
||||
|
||||
type AutolinkOptions = {
|
||||
type: MarkType
|
||||
defaultProtocol: string
|
||||
validate: (url: string) => boolean
|
||||
}
|
||||
|
||||
@ -115,7 +116,7 @@ export function autolink(options: AutolinkOptions): Plugin {
|
||||
return false
|
||||
}
|
||||
|
||||
const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject())
|
||||
const linksBeforeSpace = tokenize(lastWordBeforeSpace).map(t => t.toObject(options.defaultProtocol))
|
||||
|
||||
if (!isValidLinkStructure(linksBeforeSpace)) {
|
||||
return false
|
||||
|
@ -5,6 +5,7 @@ import { find } from 'linkifyjs'
|
||||
|
||||
type PasteHandlerOptions = {
|
||||
editor: Editor
|
||||
defaultProtocol: string
|
||||
type: MarkType
|
||||
}
|
||||
|
||||
@ -27,7 +28,7 @@ export function pasteHandler(options: PasteHandlerOptions): Plugin {
|
||||
textContent += node.textContent
|
||||
})
|
||||
|
||||
const link = find(textContent).find(item => item.isLink && item.value === textContent)
|
||||
const link = find(textContent, { defaultProtocol: options.defaultProtocol }).find(item => item.isLink && item.value === textContent)
|
||||
|
||||
if (!textContent || !link) {
|
||||
return false
|
||||
|
@ -42,6 +42,11 @@ export interface LinkOptions {
|
||||
*/
|
||||
protocols: Array<LinkProtocolOptions | string>
|
||||
|
||||
/**
|
||||
* Default protocol to use when no protocol is specified.
|
||||
* @default 'http'
|
||||
*/
|
||||
defaultProtocol: string
|
||||
/**
|
||||
* If enabled, links will be opened on click.
|
||||
* @default true
|
||||
@ -115,6 +120,8 @@ export const Link = Mark.create<LinkOptions>({
|
||||
|
||||
keepOnSplit: false,
|
||||
|
||||
exitable: true,
|
||||
|
||||
onCreate() {
|
||||
this.options.protocols.forEach(protocol => {
|
||||
if (typeof protocol === 'string') {
|
||||
@ -139,6 +146,7 @@ export const Link = Mark.create<LinkOptions>({
|
||||
linkOnPaste: true,
|
||||
autolink: true,
|
||||
protocols: [],
|
||||
defaultProtocol: 'http',
|
||||
HTMLAttributes: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
@ -255,6 +263,7 @@ export const Link = Mark.create<LinkOptions>({
|
||||
plugins.push(
|
||||
autolink({
|
||||
type: this.type,
|
||||
defaultProtocol: this.options.defaultProtocol,
|
||||
validate: this.options.validate,
|
||||
}),
|
||||
)
|
||||
@ -272,6 +281,7 @@ export const Link = Mark.create<LinkOptions>({
|
||||
plugins.push(
|
||||
pasteHandler({
|
||||
editor: this.editor,
|
||||
defaultProtocol: this.options.defaultProtocol,
|
||||
type: this.type,
|
||||
}),
|
||||
)
|
||||
|
@ -302,7 +302,7 @@ export const Table = Node.create<TableOptions>({
|
||||
const node = createTable(editor.schema, rows, cols, withHeaderRow)
|
||||
|
||||
if (dispatch) {
|
||||
const offset = tr.selection.anchor + 1
|
||||
const offset = tr.selection.from + 1
|
||||
|
||||
tr.replaceSelectionWith(node)
|
||||
.scrollIntoView()
|
||||
|
Loading…
Reference in New Issue
Block a user