Skip to content

Commit

Permalink
feat: add trailing-node extension
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 committed Aug 12, 2024
1 parent 37829b4 commit 0a2e970
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/extension-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './focus.js'
export * from './selection.js'
export * from './trailing-node.js'
84 changes: 84 additions & 0 deletions packages/extension-utils/src/trailing-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Extension } from '@tiptap/core'
import { Node, NodeType } from '@tiptap/pm/model'
import { Plugin, PluginKey } from '@tiptap/pm/state'

function nodeEqualsType({ types, node }: { types: NodeType | NodeType[]; node: Node | null | undefined }) {
return (node && Array.isArray(types) && types.includes(node.type)) || node?.type === types
}

/**
* Extension based on:
* - https://github.com/ueberdosis/tiptap/blob/v1/packages/tiptap-extensions/src/extensions/TrailingNode.js
* - https://github.com/remirror/remirror/blob/e0f1bec4a1e8073ce8f5500d62193e52321155b9/packages/prosemirror-trailing-node/src/trailing-node-plugin.ts
*/

export interface TrailingNodeOptions {
/**
* The node type that should be inserted at the end of the document.
* @note the node will always be added to the `notAfter` lists to
* prevent an infinite loop.
* @default 'paragraph'
*/
node: string;
/**
* The node types after which the trailing node should not be inserted.
* @default ['paragraph']
*/
notAfter?: string | string[];
}

/**
* This extension allows you to add an extra node at the end of the document.
* @see https://www.tiptap.dev/api/extensions/trailing-node
*/
export const TrailingNode = Extension.create<TrailingNodeOptions>({
name: 'trailingNode',

addOptions() {
return {
node: 'paragraph',
notAfter: [],
}
},

addProseMirrorPlugins() {
const plugin = new PluginKey(this.name)
const disabledNodes = Object.entries(this.editor.schema.nodes)
.map(([, value]) => value)
.filter(node => (this.options.notAfter || []).concat(this.options.node).includes(node.name))

return [
new Plugin({
key: plugin,
appendTransaction: (_, __, state) => {
const { doc, tr, schema } = state
const shouldInsertNodeAtEnd = plugin.getState(state)
const endPosition = doc.content.size
const type = schema.nodes[this.options.node]

if (!shouldInsertNodeAtEnd) {
return
}

return tr.insert(endPosition, type.create())
},
state: {
init: (_, state) => {
const lastNode = state.tr.doc.lastChild

return !nodeEqualsType({ node: lastNode, types: disabledNodes })
},
apply: (tr, value) => {
if (!tr.docChanged) {
return value
}

const lastNode = tr.doc.lastChild

return !nodeEqualsType({ node: lastNode, types: disabledNodes })
},
},
}),
]
},
})

0 comments on commit 0a2e970

Please sign in to comment.