diff --git a/demos/src/Marks/Link/React/index.jsx b/demos/src/Marks/Link/React/index.jsx index 04f989ad..f1ac6a8e 100644 --- a/demos/src/Marks/Link/React/index.jsx +++ b/demos/src/Marks/Link/React/index.jsx @@ -3,8 +3,8 @@ import { useEditor, EditorContent } from '@tiptap/react' import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' -import Link from '@tiptap/extension-link' import Code from '@tiptap/extension-code' +import Link from '@tiptap/extension-link' import './styles.scss' export default () => { @@ -13,10 +13,10 @@ export default () => { Document, Paragraph, Text, + Code, Link.configure({ openOnClick: false, }), - Code, ], content: `
diff --git a/demos/src/Marks/Link/Vue/index.vue b/demos/src/Marks/Link/Vue/index.vue index 2cb88d1c..6aa0579e 100644 --- a/demos/src/Marks/Link/Vue/index.vue +++ b/demos/src/Marks/Link/Vue/index.vue @@ -15,8 +15,8 @@ import { Editor, EditorContent } from '@tiptap/vue-3' import Document from '@tiptap/extension-document' import Paragraph from '@tiptap/extension-paragraph' import Text from '@tiptap/extension-text' -import Link from '@tiptap/extension-link' import Code from '@tiptap/extension-code' +import Link from '@tiptap/extension-link' export default { components: { @@ -35,10 +35,10 @@ export default { Document, Paragraph, Text, + Code, Link.configure({ openOnClick: false, }), - Code, ], content: `
diff --git a/docs/api/marks/link.md b/docs/api/marks/link.md
index 56bf7df7..34641145 100644
--- a/docs/api/marks/link.md
+++ b/docs/api/marks/link.md
@@ -20,14 +20,14 @@ npm install @tiptap/extension-link
## Settings
-### HTMLAttributes
-Custom HTML attributes that should be added to the rendered HTML tag.
+### autolink
+If enabled, it adds links as you type.
+
+Default: `true`
```js
Link.configure({
- HTMLAttributes: {
- class: 'my-custom-class',
- },
+ autolink: false,
})
```
@@ -53,6 +53,16 @@ Link.configure({
})
```
+### HTMLAttributes
+Custom HTML attributes that should be added to the rendered HTML tag.
+
+```js
+Link.configure({
+ HTMLAttributes: {
+ class: 'my-custom-class',
+ },
+})
+```
## Commands
diff --git a/packages/core/src/helpers/combineTransactionSteps.ts b/packages/core/src/helpers/combineTransactionSteps.ts
new file mode 100644
index 00000000..a7a4b9cb
--- /dev/null
+++ b/packages/core/src/helpers/combineTransactionSteps.ts
@@ -0,0 +1,18 @@
+import { Node as ProseMirrorNode } from 'prosemirror-model'
+import { Transaction } from 'prosemirror-state'
+import { Transform } from 'prosemirror-transform'
+
+/**
+ * Returns a new `Transform` based on all steps of the passed transactions.
+ */
+export default function combineTransactionSteps(oldDoc: ProseMirrorNode, transactions: Transaction[]): Transform {
+ const transform = new Transform(oldDoc)
+
+ transactions.forEach(transaction => {
+ transaction.steps.forEach(step => {
+ transform.step(step)
+ })
+ })
+
+ return transform
+}
diff --git a/packages/core/src/helpers/getChangedRanges.ts b/packages/core/src/helpers/getChangedRanges.ts
new file mode 100644
index 00000000..b39419a2
--- /dev/null
+++ b/packages/core/src/helpers/getChangedRanges.ts
@@ -0,0 +1,82 @@
+import { Transform, Step } from 'prosemirror-transform'
+import { Range } from '../types'
+import removeDuplicates from '../utilities/removeDuplicates'
+
+export type ChangedRange = {
+ oldRange: Range,
+ newRange: Range,
+}
+
+/**
+ * Removes duplicated ranges and ranges that are
+ * fully captured by other ranges.
+ */
+function simplifyChangedRanges(changes: ChangedRange[]): ChangedRange[] {
+ const uniqueChanges = removeDuplicates(changes)
+
+ return uniqueChanges.length === 1
+ ? uniqueChanges
+ : uniqueChanges.filter((change, index) => {
+ const rest = uniqueChanges.filter((_, i) => i !== index)
+
+ return !rest.some(otherChange => {
+ return change.oldRange.from >= otherChange.oldRange.from
+ && change.oldRange.to <= otherChange.oldRange.to
+ && change.newRange.from >= otherChange.newRange.from
+ && change.newRange.to <= otherChange.newRange.to
+ })
+ })
+}
+
+/**
+ * Returns a list of changed ranges
+ * based on the first and last state of all steps.
+ */
+export default function getChangedRanges(transform: Transform): ChangedRange[] {
+ const { mapping, steps } = transform
+ const changes: ChangedRange[] = []
+
+ mapping.maps.forEach((stepMap, index) => {
+ const ranges: Range[] = []
+
+ // This accounts for step changes where no range was actually altered
+ // e.g. when setting a mark, node attribute, etc.
+ // @ts-ignore
+ if (!stepMap.ranges.length) {
+ const { from, to } = steps[index] as Step & {
+ from?: number,
+ to?: number,
+ }
+
+ if (from === undefined || to === undefined) {
+ return
+ }
+
+ ranges.push({ from, to })
+ } else {
+ stepMap.forEach((from, to) => {
+ ranges.push({ from, to })
+ })
+ }
+
+ ranges.forEach(({ from, to }) => {
+ const newStart = mapping.slice(index).map(from, -1)
+ const newEnd = mapping.slice(index).map(to)
+ const oldStart = mapping.invert().map(newStart, -1)
+ const oldEnd = mapping.invert().map(newEnd)
+
+ changes.push({
+ oldRange: {
+ from: oldStart,
+ to: oldEnd,
+ },
+ newRange: {
+ from: newStart,
+ to: newEnd,
+ },
+ })
+ })
+ })
+
+ return simplifyChangedRanges(changes)
+}
diff --git a/packages/core/src/helpers/getMarksBetween.ts b/packages/core/src/helpers/getMarksBetween.ts
index 75373fcf..6e62bddd 100644
--- a/packages/core/src/helpers/getMarksBetween.ts
+++ b/packages/core/src/helpers/getMarksBetween.ts
@@ -1,16 +1,37 @@
-import { EditorState } from 'prosemirror-state'
+import { Node as ProseMirrorNode } from 'prosemirror-model'
import { MarkRange } from '../types'
+import getMarkRange from './getMarkRange'
-export default function getMarksBetween(from: number, to: number, state: EditorState): MarkRange[] {
+export default function getMarksBetween(from: number, to: number, doc: ProseMirrorNode): MarkRange[] {
const marks: MarkRange[] = []
- state.doc.nodesBetween(from, to, (node, pos) => {
- marks.push(...node.marks.map(mark => ({
- from: pos,
- to: pos + node.nodeSize,
- mark,
- })))
- })
+ // get all inclusive marks on empty selection
+ if (from === to) {
+ doc
+ .resolve(from)
+ .marks()
+ .forEach(mark => {
+ const $pos = doc.resolve(from - 1)
+ const range = getMarkRange($pos, mark.type)
+
+ if (!range) {
+ return
+ }
+
+ marks.push({
+ mark,
+ ...range,
+ })
+ })
+ } else {
+ doc.nodesBetween(from, to, (node, pos) => {
+ marks.push(...node.marks.map(mark => ({
+ from: pos,
+ to: pos + node.nodeSize,
+ mark,
+ })))
+ })
+ }
return marks
}
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
index 38059236..65c7b252 100644
--- a/packages/core/src/index.ts
+++ b/packages/core/src/index.ts
@@ -23,6 +23,7 @@ export { default as textPasteRule } from './pasteRules/textPasteRule'
export { default as callOrReturn } from './utilities/callOrReturn'
export { default as mergeAttributes } from './utilities/mergeAttributes'
+export { default as combineTransactionSteps } from './helpers/combineTransactionSteps'
export { default as defaultBlockAt } from './helpers/defaultBlockAt'
export { default as getExtensionField } from './helpers/getExtensionField'
export { default as findChildren } from './helpers/findChildren'
@@ -32,6 +33,7 @@ export { default as findParentNodeClosestToPos } from './helpers/findParentNodeC
export { default as generateHTML } from './helpers/generateHTML'
export { default as generateJSON } from './helpers/generateJSON'
export { default as generateText } from './helpers/generateText'
+export { default as getChangedRanges } from './helpers/getChangedRanges'
export { default as getSchema } from './helpers/getSchema'
export { default as getHTMLFromFragment } from './helpers/getHTMLFromFragment'
export { default as getDebugJSON } from './helpers/getDebugJSON'
diff --git a/packages/core/src/inputRules/markInputRule.ts b/packages/core/src/inputRules/markInputRule.ts
index a55c4e15..42c8d77b 100644
--- a/packages/core/src/inputRules/markInputRule.ts
+++ b/packages/core/src/inputRules/markInputRule.ts
@@ -37,7 +37,7 @@ export default function markInputRule(config: {
const textStart = range.from + fullMatch.indexOf(captureGroup)
const textEnd = textStart + captureGroup.length
- const excludedMarks = getMarksBetween(range.from, range.to, state)
+ const excludedMarks = getMarksBetween(range.from, range.to, state.doc)
.filter(item => {
// @ts-ignore
const excluded = item.mark.type.excluded as MarkType[]
diff --git a/packages/core/src/pasteRules/markPasteRule.ts b/packages/core/src/pasteRules/markPasteRule.ts
index 12f9cbf6..b0dd30cf 100644
--- a/packages/core/src/pasteRules/markPasteRule.ts
+++ b/packages/core/src/pasteRules/markPasteRule.ts
@@ -37,7 +37,7 @@ export default function markPasteRule(config: {
const textStart = range.from + fullMatch.indexOf(captureGroup)
const textEnd = textStart + captureGroup.length
- const excludedMarks = getMarksBetween(range.from, range.to, state)
+ const excludedMarks = getMarksBetween(range.from, range.to, state.doc)
.filter(item => {
// @ts-ignore
const excluded = item.mark.type.excluded as MarkType[]
diff --git a/packages/core/src/utilities/removeDuplicates.ts b/packages/core/src/utilities/removeDuplicates.ts
new file mode 100644
index 00000000..73930ea7
--- /dev/null
+++ b/packages/core/src/utilities/removeDuplicates.ts
@@ -0,0 +1,15 @@
+/**
+ * Removes duplicated values within an array.
+ * Supports numbers, strings and objects.
+ */
+export default function removeDuplicates