@tiptap/core
Advanced tools
Comparing version 2.2.6 to 2.3.0
@@ -12,2 +12,4 @@ import { ParseOptions } from '@tiptap/pm/model'; | ||
updateSelection?: boolean; | ||
applyInputRules?: boolean; | ||
applyPasteRules?: boolean; | ||
}) => ReturnType; | ||
@@ -14,0 +16,0 @@ }; |
@@ -12,2 +12,4 @@ import { ParseOptions } from '@tiptap/pm/model'; | ||
updateSelection?: boolean; | ||
applyInputRules?: boolean; | ||
applyPasteRules?: boolean; | ||
}) => ReturnType; | ||
@@ -14,0 +16,0 @@ }; |
@@ -6,6 +6,5 @@ import { MarkType, NodeType, Schema } from '@tiptap/pm/model'; | ||
import { ExtensionManager } from './ExtensionManager.js'; | ||
import * as extensions from './extensions/index.js'; | ||
import { NodePos } from './NodePos.js'; | ||
import { CanCommands, ChainedCommands, EditorEvents, EditorOptions, JSONContent, SingleCommands, TextSerializer } from './types.js'; | ||
export { extensions }; | ||
export * as extensions from './extensions/index.js'; | ||
export interface HTMLElement { | ||
@@ -12,0 +11,0 @@ editor?: Editor; |
import { Extension } from '../Extension.js'; | ||
export declare const ClipboardTextSerializer: Extension<any, any>; | ||
export declare type ClipboardTextSerializerOptions = { | ||
blockSeparator?: string; | ||
}; | ||
export declare const ClipboardTextSerializer: Extension<ClipboardTextSerializerOptions, any>; |
@@ -61,2 +61,7 @@ import { Mark as ProseMirrorMark, Node as ProseMirrorNode, NodeType, ParseOptions } from '@tiptap/pm/model'; | ||
parseOptions: ParseOptions; | ||
coreExtensionOptions?: { | ||
clipboardTextSerializer?: { | ||
blockSeparator?: string; | ||
}; | ||
}; | ||
enableInputRules: EnableRules; | ||
@@ -63,0 +68,0 @@ enablePasteRules: EnableRules; |
{ | ||
"name": "@tiptap/core", | ||
"description": "headless rich text editor", | ||
"version": "2.2.6", | ||
"version": "2.3.0", | ||
"homepage": "https://tiptap.dev", | ||
@@ -35,3 +35,3 @@ "keywords": [ | ||
"devDependencies": { | ||
"@tiptap/pm": "^2.2.6" | ||
"@tiptap/pm": "^2.3.0" | ||
}, | ||
@@ -38,0 +38,0 @@ "peerDependencies": { |
@@ -16,2 +16,4 @@ import { ParseOptions } from '@tiptap/pm/model' | ||
updateSelection?: boolean | ||
applyInputRules?: boolean | ||
applyPasteRules?: boolean | ||
}, | ||
@@ -18,0 +20,0 @@ ) => ReturnType |
@@ -19,2 +19,4 @@ import { Fragment, Node as ProseMirrorNode, ParseOptions } from '@tiptap/pm/model' | ||
updateSelection?: boolean | ||
applyInputRules?: boolean | ||
applyPasteRules?: boolean | ||
}, | ||
@@ -35,2 +37,4 @@ ) => ReturnType | ||
updateSelection: true, | ||
applyInputRules: false, | ||
applyPasteRules: false, | ||
...options, | ||
@@ -81,2 +85,4 @@ } | ||
let newContent | ||
// if there is only plain text we have to use `insertText` | ||
@@ -88,10 +94,14 @@ // because this will keep the current marks | ||
if (Array.isArray(value)) { | ||
tr.insertText(value.map(v => v.text || '').join(''), from, to) | ||
newContent = value.map(v => v.text || '').join('') | ||
} else if (typeof value === 'object' && !!value && !!value.text) { | ||
tr.insertText(value.text, from, to) | ||
newContent = value.text | ||
} else { | ||
tr.insertText(value as string, from, to) | ||
newContent = value as string | ||
} | ||
tr.insertText(newContent, from, to) | ||
} else { | ||
tr.replaceWith(from, to, content) | ||
newContent = content | ||
tr.replaceWith(from, to, newContent) | ||
} | ||
@@ -103,2 +113,10 @@ | ||
} | ||
if (options.applyInputRules) { | ||
tr.setMeta('applyInputRules', { from, text: newContent }) | ||
} | ||
if (options.applyPasteRules) { | ||
tr.setMeta('applyPasteRules', { from, text: newContent }) | ||
} | ||
} | ||
@@ -105,0 +123,0 @@ |
@@ -12,3 +12,5 @@ import { | ||
import { ExtensionManager } from './ExtensionManager.js' | ||
import * as extensions from './extensions/index.js' | ||
import { | ||
ClipboardTextSerializer, Commands, Editable, FocusEvents, Keymap, Tabindex, | ||
} from './extensions/index.js' | ||
import { createDocument } from './helpers/createDocument.js' | ||
@@ -36,3 +38,3 @@ import { getAttributes } from './helpers/getAttributes.js' | ||
export { extensions } | ||
export * as extensions from './extensions/index.js' | ||
@@ -68,2 +70,3 @@ export interface HTMLElement { | ||
parseOptions: {}, | ||
coreExtensionOptions: {}, | ||
enableInputRules: true, | ||
@@ -241,3 +244,13 @@ enablePasteRules: true, | ||
private createExtensionManager(): void { | ||
const coreExtensions = this.options.enableCoreExtensions ? Object.values(extensions) : [] | ||
const coreExtensions = this.options.enableCoreExtensions ? [ | ||
Editable, | ||
ClipboardTextSerializer.configure({ | ||
blockSeparator: this.options.coreExtensionOptions?.clipboardTextSerializer?.blockSeparator, | ||
}), | ||
Commands, | ||
FocusEvents, | ||
Keymap, | ||
Tabindex, | ||
] : [] | ||
const allExtensions = [...coreExtensions, ...this.options.extensions].filter(extension => { | ||
@@ -244,0 +257,0 @@ return ['extension', 'node', 'mark'].includes(extension?.type) |
@@ -7,5 +7,15 @@ import { Plugin, PluginKey } from '@tiptap/pm/state' | ||
export const ClipboardTextSerializer = Extension.create({ | ||
export type ClipboardTextSerializerOptions = { | ||
blockSeparator?: string, | ||
} | ||
export const ClipboardTextSerializer = Extension.create<ClipboardTextSerializerOptions>({ | ||
name: 'clipboardTextSerializer', | ||
addOptions() { | ||
return { | ||
blockSeparator: undefined, | ||
} | ||
}, | ||
addProseMirrorPlugins() { | ||
@@ -27,2 +37,5 @@ return [ | ||
return getTextBetween(doc, range, { | ||
...(this.options.blockSeparator !== undefined | ||
? { blockSeparator: this.options.blockSeparator } | ||
: {}), | ||
textSerializers, | ||
@@ -29,0 +42,0 @@ }) |
@@ -194,2 +194,22 @@ import { EditorState, Plugin, TextSelection } from '@tiptap/pm/state' | ||
// if InputRule is triggered by insertContent() | ||
const simulatedInputMeta = tr.getMeta('applyInputRules') | ||
const isSimulatedInput = !!simulatedInputMeta | ||
if (isSimulatedInput) { | ||
setTimeout(() => { | ||
const { from, text } = simulatedInputMeta | ||
const to = from + text.length | ||
run({ | ||
editor, | ||
from, | ||
to, | ||
text, | ||
rules, | ||
plugin, | ||
}) | ||
}) | ||
} | ||
return tr.selectionSet || tr.docChanged ? null : prev | ||
@@ -196,0 +216,0 @@ }, |
@@ -139,3 +139,3 @@ import { | ||
const targetPos = this.pos + offset + (isBlock ? 0 : 1) | ||
const targetPos = this.pos + offset + 1 | ||
const $pos = this.resolvedPos.doc.resolve(targetPos) | ||
@@ -205,3 +205,3 @@ | ||
// iterate through children recursively finding all nodes which match the selector with the node name | ||
if (this.isBlock || !this.children || this.children.length === 0) { | ||
if (!this.children || this.children.length === 0) { | ||
return nodes | ||
@@ -208,0 +208,0 @@ } |
@@ -157,2 +157,12 @@ import { EditorState, Plugin } from '@tiptap/pm/state' | ||
const createClipboardPasteEvent = (text: string) => { | ||
const event = new ClipboardEvent('paste', { | ||
clipboardData: new DataTransfer(), | ||
}) | ||
event.clipboardData?.setData('text/html', text) | ||
return event | ||
} | ||
/** | ||
@@ -171,2 +181,41 @@ * Create an paste rules plugin. When enabled, it will cause pasted | ||
const processEvent = ({ | ||
state, | ||
from, | ||
to, | ||
rule, | ||
pasteEvt, | ||
}: { | ||
state: EditorState | ||
from: number | ||
to: { b: number } | ||
rule: PasteRule | ||
pasteEvt: ClipboardEvent | null | ||
}) => { | ||
const tr = state.tr | ||
const chainableState = createChainableState({ | ||
state, | ||
transaction: tr, | ||
}) | ||
const handler = run({ | ||
editor, | ||
state: chainableState, | ||
from: Math.max(from - 1, 0), | ||
to: to.b - 1, | ||
rule, | ||
pasteEvent: pasteEvt, | ||
dropEvent, | ||
}) | ||
if (!handler || !tr.steps.length) { | ||
return | ||
} | ||
dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null | ||
pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null | ||
return tr | ||
} | ||
const plugins = rules.map(rule => { | ||
@@ -217,10 +266,30 @@ return new Plugin({ | ||
if (!isPaste && !isDrop) { | ||
// if PasteRule is triggered by insertContent() | ||
const simulatedPasteMeta = transaction.getMeta('applyPasteRules') | ||
const isSimulatedPaste = !!simulatedPasteMeta | ||
if (!isPaste && !isDrop && !isSimulatedPaste) { | ||
return | ||
} | ||
// stop if there is no changed range | ||
// Handle simulated paste | ||
if (isSimulatedPaste) { | ||
const { from, text } = simulatedPasteMeta | ||
const to = from + text.length | ||
const pasteEvt = createClipboardPasteEvent(text) | ||
return processEvent({ | ||
rule, | ||
state, | ||
from, | ||
to: { b: to }, | ||
pasteEvt, | ||
}) | ||
} | ||
// handle actual paste/drop | ||
const from = oldState.doc.content.findDiffStart(state.doc.content) | ||
const to = oldState.doc.content.findDiffEnd(state.doc.content) | ||
// stop if there is no changed range | ||
if (!isNumber(from) || !to || from === to.b) { | ||
@@ -230,29 +299,9 @@ return | ||
// build a chainable state | ||
// so we can use a single transaction for all paste rules | ||
const tr = state.tr | ||
const chainableState = createChainableState({ | ||
return processEvent({ | ||
rule, | ||
state, | ||
transaction: tr, | ||
from, | ||
to, | ||
pasteEvt: pasteEvent, | ||
}) | ||
const handler = run({ | ||
editor, | ||
state: chainableState, | ||
from: Math.max(from - 1, 0), | ||
to: to.b - 1, | ||
rule, | ||
pasteEvent, | ||
dropEvent, | ||
}) | ||
// stop if there are no changes | ||
if (!handler || !tr.steps.length) { | ||
return | ||
} | ||
dropEvent = typeof DragEvent !== 'undefined' ? new DragEvent('drop') : null | ||
pasteEvent = typeof ClipboardEvent !== 'undefined' ? new ClipboardEvent('paste') : null | ||
return tr | ||
}, | ||
@@ -259,0 +308,0 @@ }) |
@@ -62,2 +62,7 @@ import { | ||
parseOptions: ParseOptions | ||
coreExtensionOptions?: { | ||
clipboardTextSerializer?: { | ||
blockSeparator?: string | ||
} | ||
} | ||
enableInputRules: EnableRules | ||
@@ -64,0 +69,0 @@ enablePasteRules: EnableRules |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
2071115
24260