@tiptap/extension-unique-id 3.23.6 → 3.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -107,8 +107,12 @@ var UniqueID = import_core.Extension.create({
107
107
  if (!this.options.updateDocument) {
108
108
  return;
109
109
  }
110
- const collaboration = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaboration");
111
- const collaborationCaret = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaborationCaret");
110
+ const collaboration = this.editor.extensionManager.extensions.find(
111
+ (ext) => ext.name === "collaboration"
112
+ );
113
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
114
+ (ext) => ext.name === "collaborationCaret"
115
+ );
112
116
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean);
113
117
  const collab = collabExtensions.find((ext) => {
114
118
  var _a2;
@@ -138,18 +142,27 @@ var UniqueID = import_core.Extension.create({
138
142
  if (collaboration) {
139
143
  if (provider) {
140
144
  provider.on("synced", createIds);
145
+ this.storage.cleanupSyncedListener = () => provider.off("synced", createIds);
141
146
  }
142
147
  } else {
143
148
  return createIds();
144
149
  }
145
150
  },
151
+ onDestroy() {
152
+ var _a, _b;
153
+ (_b = (_a = this.storage).cleanupSyncedListener) == null ? void 0 : _b.call(_a);
154
+ },
146
155
  addProseMirrorPlugins() {
147
156
  if (!this.options.updateDocument) {
148
157
  return [];
149
158
  }
150
159
  const extensionStorage = this.storage;
151
- const collaboration = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaboration");
152
- const collaborationCaret = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaborationCaret");
160
+ const collaboration = this.editor.extensionManager.extensions.find(
161
+ (ext) => ext.name === "collaboration"
162
+ );
163
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
164
+ (ext) => ext.name === "collaborationCaret"
165
+ );
153
166
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean);
154
167
  const collabWithProvider = collabExtensions.find((ext) => {
155
168
  var _a;
@@ -341,7 +354,10 @@ function generateUniqueIds(doc, extensions) {
341
354
  if (!uniqueIDExtension) {
342
355
  throw new Error("UniqueID extension not found in the extensions array");
343
356
  }
344
- const schema = (0, import_core2.getSchema)([...extensions.filter((ext) => ext.name !== "uniqueID"), uniqueIDExtension]);
357
+ const schema = (0, import_core2.getSchema)([
358
+ ...extensions.filter((ext) => ext.name !== "uniqueID"),
359
+ uniqueIDExtension
360
+ ]);
345
361
  const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options;
346
362
  const types = configuredTypes === "all" ? Object.keys(schema.nodes).filter((type) => type !== "doc" && type !== "text") : configuredTypes;
347
363
  const contentNode = import_model2.Node.fromJSON(schema, doc);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/unique-id.ts","../src/helpers/findDuplicates.ts","../src/generate-unique-ids.ts"],"sourcesContent":["import { UniqueID } from './unique-id.js'\n\nexport * from './generate-unique-ids.js'\nexport * from './unique-id.js'\n\nexport default UniqueID\n","import {\n type Extensions,\n combineTransactionSteps,\n Extension,\n findChildren,\n findChildrenInRange,\n getChangedRanges,\n splitExtensions,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Fragment, Slice } from '@tiptap/pm/model'\nimport type { Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { v4 as uuidv4 } from 'uuid'\n\nimport { findDuplicates } from './helpers/findDuplicates.js'\n\nexport type UniqueIDGenerationContext = {\n node: ProseMirrorNode\n pos: number\n}\n\nexport interface UniqueIDOptions {\n /**\n * The name of the attribute to add the unique ID to.\n * @default \"id\"\n */\n attributeName: string\n /**\n * The types of nodes to add unique IDs to.\n * Use `\"all\"` to add IDs to every node type except `doc` and `text`.\n * @default []\n */\n types: string[] | 'all'\n /**\n * The function that generates the unique ID. By default, a UUID v4 is\n * generated. However, you can provide your own function to generate the\n * unique ID based on the node type and the position.\n */\n generateID: (ctx: UniqueIDGenerationContext) => any\n /**\n * Ignore some mutations, for example applied from other users through the collaboration plugin.\n *\n * @default null\n */\n filterTransaction: ((transaction: Transaction) => boolean) | null\n /**\n * Whether to update the document by adding unique IDs to the nodes. Set this\n * property to `false` if the document is in `readonly` mode, is immutable, or\n * you don't want it to be modified.\n *\n * @default true\n */\n updateDocument: boolean\n}\n\nconst resolveTypes = (types: UniqueIDOptions['types'], extensions: Extensions): string[] => {\n if (types !== 'all') {\n return types\n }\n\n const { nodeExtensions } = splitExtensions(extensions)\n\n return nodeExtensions.map(extension => extension.name).filter(type => type !== 'doc' && type !== 'text')\n}\n\nexport const UniqueID = Extension.create<UniqueIDOptions>({\n name: 'uniqueID',\n\n // we’ll set a very high priority to make sure this runs first\n // and is compatible with `appendTransaction` hooks of other extensions\n priority: 10000,\n\n addOptions() {\n return {\n attributeName: 'id',\n types: [],\n generateID: () => uuidv4(),\n filterTransaction: null,\n updateDocument: true,\n }\n },\n\n /**\n * Extension storage for coordination between `addProseMirrorPlugins` and `appendTransaction`.\n * `needsInitialIdGeneration` is set to `true` when the Collaboration extension is\n * detected but no provider is available in its options, deferring ID creation\n * to the first `y-sync$` transaction.\n */\n addStorage() {\n return {\n needsInitialIdGeneration: false,\n }\n },\n\n addGlobalAttributes() {\n const types = resolveTypes(this.options.types, this.extensions)\n\n return [\n {\n types,\n attributes: {\n [this.options.attributeName]: {\n default: null,\n parseHTML: element => element.getAttribute(`data-${this.options.attributeName}`),\n renderHTML: attributes => {\n if (!attributes[this.options.attributeName]) {\n return {}\n }\n\n return {\n [`data-${this.options.attributeName}`]: attributes[this.options.attributeName],\n }\n },\n },\n },\n },\n ]\n },\n\n // check initial content for missing ids\n onCreate() {\n if (!this.options.updateDocument) {\n return\n }\n\n const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')\n const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')\n\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collab = collabExtensions.find(ext => ext?.options?.provider)\n const provider = collab?.options?.provider\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n const { attributeName, generateID } = this.options\n const nodesWithoutId = findChildren(doc, node => {\n return types.includes(node.type.name) && node.attrs[attributeName] === null\n })\n\n nodesWithoutId.forEach(({ node, pos }) => {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n })\n\n tr.setMeta('addToHistory', false)\n\n view.dispatch(tr)\n\n if (provider) {\n provider.off('synced', createIds)\n }\n }\n\n /**\n * We need to handle collaboration a bit different here\n * because we can't automatically add IDs when the provider is not yet synced\n * otherwise we end up with empty paragraphs\n */\n if (collaboration) {\n if (provider) {\n provider.on('synced', createIds)\n }\n // When collaboration is present but no provider is in extension options,\n // needsInitialIdGeneration was already set in addProseMirrorPlugins\n // (which runs synchronously during editor construction, before any\n // y-sync$ transaction can arrive).\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n if (!this.options.updateDocument) {\n return []\n }\n\n // Capture storage via closure so appendTransaction can access `needsInitialIdGeneration`.\n // `extensionManager.extensions` returns different objects than `this` due to\n // Tiptap's child-extension flattening, so we capture the reference directly.\n const extensionStorage = this.storage\n\n // Detect collaboration early — before any y-sync$ transaction can arrive.\n // onCreate runs on a deferred setTimeout(0), so a y-sync$ transaction could\n // be applied before it. Setting the flag here (synchronous during editor\n // construction) ensures the first y-sync$ is always handled.\n const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')\n const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collabWithProvider = collabExtensions.find(ext => ext?.options?.provider)\n\n if (collaboration && !collabWithProvider) {\n extensionStorage.needsInitialIdGeneration = true\n }\n\n let dragSourceElement: Element | null = null\n let transformPasted = false\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n\n return [\n new Plugin({\n key: new PluginKey('uniqueID'),\n\n appendTransaction: (transactions, oldState, newState) => {\n const hasDocChanges =\n transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n const filterTransactions =\n this.options.filterTransaction && transactions.some(tr => !this.options.filterTransaction?.(tr))\n\n const isCollabTransaction = transactions.find(tr => tr.getMeta('y-sync$'))\n\n if (isCollabTransaction) {\n if (extensionStorage.needsInitialIdGeneration) {\n extensionStorage.needsInitialIdGeneration = false\n\n // Run full-document ID creation after the first Yjs sync.\n // Use a seen-set so only the second+ occurrence of a duplicated\n // ID is regenerated, preserving one existing ID per value.\n const { tr } = newState\n const { attributeName, generateID } = this.options\n const allNodes = findChildren(newState.doc, node => types.includes(node.type.name))\n const seen = new Set<string>()\n\n allNodes.forEach(({ node, pos }) => {\n const currentId = node.attrs[attributeName]\n\n if (currentId === null || seen.has(currentId)) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n } else {\n seen.add(currentId)\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // Restore stored marks since setNodeMarkup resets them\n tr.setStoredMarks(newState.tr.storedMarks)\n // Mark this transaction as coming from UniqueID to prevent\n // infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n tr.setMeta('addToHistory', false)\n return tr\n }\n\n return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { attributeName, generateID } = this.options\n const transform = combineTransactionSteps(oldState.doc, transactions as Transaction[])\n const { mapping } = transform\n\n // get changed ranges based on the old state\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n const newNodes = findChildrenInRange(newState.doc, newRange, node => {\n return types.includes(node.type.name)\n })\n\n const newIds = newNodes.map(({ node }) => node.attrs[attributeName]).filter(id => id !== null)\n\n newNodes.forEach(({ node, pos }, i) => {\n // instead of checking `node.attrs[attributeName]` directly\n // we look at the current state of the node within `tr.doc`.\n // this helps to prevent adding new ids to the same node\n // if the node changed multiple times within one transaction\n const id = tr.doc.nodeAt(pos)?.attrs[attributeName]\n\n if (id === null) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n const nextNodeInTr = tr.doc.nodeAt(nextNode.pos)\n if (nextNodeInTr?.attrs[attributeName] && nextNodeInTr.attrs[attributeName] !== id) {\n return\n }\n\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n const generatedId = generateID({ node, pos })\n\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generatedId,\n })\n newIds[i] = generatedId\n\n return tr\n }\n\n const duplicatedNewIds = findDuplicates(newIds)\n\n // check if the node doesn’t exist in the old state\n const { deleted } = mapping.invert().mapResult(pos)\n\n const newNode = deleted && duplicatedNewIds.includes(id)\n\n if (newNode) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n }\n })\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // `tr.setNodeMarkup` resets the stored marks\n // so we'll restore them if they exist\n tr.setStoredMarks(newState.tr.storedMarks)\n\n // Mark this transaction as coming from UniqueID\n // to prevent infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n\n return tr\n },\n\n // we register a global drag handler to track the current drag source element\n view(view) {\n const handleDragstart = (event: DragEvent) => {\n dragSourceElement = view.dom.parentElement?.contains(event.target as Element)\n ? view.dom.parentElement\n : null\n }\n\n window.addEventListener('dragstart', handleDragstart)\n\n return {\n destroy() {\n window.removeEventListener('dragstart', handleDragstart)\n },\n }\n },\n\n props: {\n // `handleDOMEvents` is called before `transformPasted`\n // so we can do some checks before\n handleDOMEvents: {\n // only create new ids for dropped content\n // or dropped content while holding `alt`\n // or content is dragged from another editor\n drop: (view, event) => {\n if (\n dragSourceElement !== view.dom.parentElement ||\n event.dataTransfer?.effectAllowed === 'copyMove' ||\n event.dataTransfer?.effectAllowed === 'copy'\n ) {\n dragSourceElement = null\n transformPasted = true\n }\n\n return false\n },\n // always create new ids on pasted content\n paste: () => {\n transformPasted = true\n\n return false\n },\n },\n\n // we’ll remove ids for every pasted node\n // so we can create a new one within `appendTransaction`\n transformPasted: slice => {\n if (!transformPasted) {\n return slice\n }\n\n const { attributeName } = this.options\n const removeId = (fragment: Fragment): Fragment => {\n const list: ProseMirrorNode[] = []\n\n fragment.forEach(node => {\n // don’t touch text nodes\n if (node.isText) {\n list.push(node)\n\n return\n }\n\n // check for any other child nodes\n if (!types.includes(node.type.name)) {\n list.push(node.copy(removeId(node.content)))\n\n return\n }\n\n // remove id\n const nodeWithoutId = node.type.create(\n {\n ...node.attrs,\n [attributeName]: null,\n },\n removeId(node.content),\n node.marks,\n )\n\n list.push(nodeWithoutId)\n })\n\n return Fragment.from(list)\n }\n\n // reset check\n transformPasted = false\n\n return new Slice(removeId(slice.content), slice.openStart, slice.openEnd)\n },\n },\n }),\n ]\n },\n})\n","/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const seen = new Set()\n const duplicates = new Set<any>()\n\n items.forEach(item => {\n if (seen.has(item)) {\n duplicates.add(item)\n } else {\n seen.add(item)\n }\n })\n\n return Array.from(duplicates)\n}\n","import type { Extensions, JSONContent } from '@tiptap/core'\nimport { findChildren, getSchema } from '@tiptap/core'\nimport { Node } from '@tiptap/pm/model'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport type { UniqueID } from './unique-id.js'\n\n/**\n * Creates a new document with unique IDs added to the nodes. Does the same\n * thing as the UniqueID extension, but without the need to create an `Editor`\n * instance. This lets you add unique IDs to the document in the server.\n *\n * When you call it, include the `UniqueID` extension in the `extensions` array.\n * The configuration from the `UniqueID` extension will be picked up\n * automatically, including its configuration options like `types` and\n * `attributeName`.\n *\n * @see `UniqueID` extension for more information.\n *\n * @throws {Error} If the `UniqueID` extension is not found in the extensions array.\n *\n * @example\n * const doc = {\n * type: 'doc',\n * content: [\n * { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }\n * ]\n * }\n * const newDoc = addUniqueIds(doc, [StarterKit, UniqueID.configure({ types: ['paragraph', 'heading'] })])\n * console.log(newDoc)\n * // Result:\n * // {\n * // type: 'doc',\n * // content: [\n * // { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], id: '123' }\n * // ]\n * // }\n *\n * @param doc - A Tiptap JSON document to add unique IDs to.\n * @param extensions - The extensions to use. Must include the `UniqueID` extension.\n * @returns The updated Tiptap JSON document, with the unique IDs added to the nodes.\n */\nexport function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSONContent {\n // Find the UniqueID extension in the extensions array. If it's not found, throw an error.\n const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as typeof UniqueID | undefined\n if (!uniqueIDExtension) {\n throw new Error('UniqueID extension not found in the extensions array')\n }\n // Convert the JSON content to a ProseMirror node\n const schema = getSchema([...extensions.filter(ext => ext.name !== 'uniqueID'), uniqueIDExtension])\n const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options\n const types =\n configuredTypes === 'all'\n ? Object.keys(schema.nodes).filter(type => type !== 'doc' && type !== 'text')\n : configuredTypes\n const contentNode = Node.fromJSON(schema, doc)\n\n // Find nodes that don't have a unique ID\n const nodesWithoutId = findChildren(contentNode, node => {\n return !node.attrs[attributeName] && types.includes(node.type.name)\n })\n\n // Edit the document to add unique IDs to the nodes that don't have a unique ID\n let tr = EditorState.create({\n doc: contentNode,\n }).tr\n // eslint-disable-next-line no-restricted-syntax\n for (const { node, pos } of nodesWithoutId) {\n tr = tr.setNodeAttribute(pos, attributeName, generateID({ node, pos }))\n }\n\n // Return the updated document\n return tr.doc.toJSON()\n}\n"],"mappings":"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","names":["uuidv4","_a","tr","attributeName","generateID","import_core","import_model","import_state"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/unique-id.ts","../src/helpers/findDuplicates.ts","../src/generate-unique-ids.ts"],"sourcesContent":["import { UniqueID } from './unique-id.js'\n\nexport * from './generate-unique-ids.js'\nexport * from './unique-id.js'\n\nexport default UniqueID\n","import {\n type Extensions,\n combineTransactionSteps,\n Extension,\n findChildren,\n findChildrenInRange,\n getChangedRanges,\n splitExtensions,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Fragment, Slice } from '@tiptap/pm/model'\nimport type { Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { v4 as uuidv4 } from 'uuid'\n\nimport { findDuplicates } from './helpers/findDuplicates.js'\n\nexport type UniqueIDGenerationContext = {\n node: ProseMirrorNode\n pos: number\n}\n\nexport interface UniqueIDOptions {\n /**\n * The name of the attribute to add the unique ID to.\n * @default \"id\"\n */\n attributeName: string\n /**\n * The types of nodes to add unique IDs to.\n * Use `\"all\"` to add IDs to every node type except `doc` and `text`.\n * @default []\n */\n types: string[] | 'all'\n /**\n * The function that generates the unique ID. By default, a UUID v4 is\n * generated. However, you can provide your own function to generate the\n * unique ID based on the node type and the position.\n */\n generateID: (ctx: UniqueIDGenerationContext) => any\n /**\n * Ignore some mutations, for example applied from other users through the collaboration plugin.\n *\n * @default null\n */\n filterTransaction: ((transaction: Transaction) => boolean) | null\n /**\n * Whether to update the document by adding unique IDs to the nodes. Set this\n * property to `false` if the document is in `readonly` mode, is immutable, or\n * you don't want it to be modified.\n *\n * @default true\n */\n updateDocument: boolean\n}\n\nconst resolveTypes = (types: UniqueIDOptions['types'], extensions: Extensions): string[] => {\n if (types !== 'all') {\n return types\n }\n\n const { nodeExtensions } = splitExtensions(extensions)\n\n return nodeExtensions\n .map(extension => extension.name)\n .filter(type => type !== 'doc' && type !== 'text')\n}\n\nexport const UniqueID = Extension.create<UniqueIDOptions>({\n name: 'uniqueID',\n\n // we’ll set a very high priority to make sure this runs first\n // and is compatible with `appendTransaction` hooks of other extensions\n priority: 10000,\n\n addOptions() {\n return {\n attributeName: 'id',\n types: [],\n generateID: () => uuidv4(),\n filterTransaction: null,\n updateDocument: true,\n }\n },\n\n /**\n * Extension storage for coordination between `addProseMirrorPlugins` and `appendTransaction`.\n * `needsInitialIdGeneration` is set to `true` when the Collaboration extension is\n * detected but no provider is available in its options, deferring ID creation\n * to the first `y-sync$` transaction.\n */\n addStorage() {\n return {\n needsInitialIdGeneration: false,\n }\n },\n\n addGlobalAttributes() {\n const types = resolveTypes(this.options.types, this.extensions)\n\n return [\n {\n types,\n attributes: {\n [this.options.attributeName]: {\n default: null,\n parseHTML: element => element.getAttribute(`data-${this.options.attributeName}`),\n renderHTML: attributes => {\n if (!attributes[this.options.attributeName]) {\n return {}\n }\n\n return {\n [`data-${this.options.attributeName}`]: attributes[this.options.attributeName],\n }\n },\n },\n },\n },\n ]\n },\n\n // check initial content for missing ids\n onCreate() {\n if (!this.options.updateDocument) {\n return\n }\n\n const collaboration = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaboration',\n )\n const collaborationCaret = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaborationCaret',\n )\n\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collab = collabExtensions.find(ext => ext?.options?.provider)\n const provider = collab?.options?.provider\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n const { attributeName, generateID } = this.options\n const nodesWithoutId = findChildren(doc, node => {\n return types.includes(node.type.name) && node.attrs[attributeName] === null\n })\n\n nodesWithoutId.forEach(({ node, pos }) => {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n })\n\n tr.setMeta('addToHistory', false)\n\n view.dispatch(tr)\n\n if (provider) {\n provider.off('synced', createIds)\n }\n }\n\n /**\n * We need to handle collaboration a bit different here\n * because we can't automatically add IDs when the provider is not yet synced\n * otherwise we end up with empty paragraphs\n */\n if (collaboration) {\n if (provider) {\n provider.on('synced', createIds)\n // Detach on destroy too, in case the editor is destroyed before the\n // provider syncs — otherwise `createIds` (and the editor it closes over)\n // stays referenced by the shared provider and leaks.\n this.storage.cleanupSyncedListener = () => provider.off('synced', createIds)\n }\n // When collaboration is present but no provider is in extension options,\n // needsInitialIdGeneration was already set in addProseMirrorPlugins\n // (which runs synchronously during editor construction, before any\n // y-sync$ transaction can arrive).\n } else {\n return createIds()\n }\n },\n\n onDestroy() {\n this.storage.cleanupSyncedListener?.()\n },\n\n addProseMirrorPlugins() {\n if (!this.options.updateDocument) {\n return []\n }\n\n // Capture storage via closure so appendTransaction can access `needsInitialIdGeneration`.\n // `extensionManager.extensions` returns different objects than `this` due to\n // Tiptap's child-extension flattening, so we capture the reference directly.\n const extensionStorage = this.storage\n\n // Detect collaboration early — before any y-sync$ transaction can arrive.\n // onCreate runs on a deferred setTimeout(0), so a y-sync$ transaction could\n // be applied before it. Setting the flag here (synchronous during editor\n // construction) ensures the first y-sync$ is always handled.\n const collaboration = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaboration',\n )\n const collaborationCaret = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaborationCaret',\n )\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collabWithProvider = collabExtensions.find(ext => ext?.options?.provider)\n\n if (collaboration && !collabWithProvider) {\n extensionStorage.needsInitialIdGeneration = true\n }\n\n let dragSourceElement: Element | null = null\n let transformPasted = false\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n\n return [\n new Plugin({\n key: new PluginKey('uniqueID'),\n\n appendTransaction: (transactions, oldState, newState) => {\n const hasDocChanges =\n transactions.some(transaction => transaction.docChanged) &&\n !oldState.doc.eq(newState.doc)\n const filterTransactions =\n this.options.filterTransaction &&\n transactions.some(tr => !this.options.filterTransaction?.(tr))\n\n const isCollabTransaction = transactions.find(tr => tr.getMeta('y-sync$'))\n\n if (isCollabTransaction) {\n if (extensionStorage.needsInitialIdGeneration) {\n extensionStorage.needsInitialIdGeneration = false\n\n // Run full-document ID creation after the first Yjs sync.\n // Use a seen-set so only the second+ occurrence of a duplicated\n // ID is regenerated, preserving one existing ID per value.\n const { tr } = newState\n const { attributeName, generateID } = this.options\n const allNodes = findChildren(newState.doc, node => types.includes(node.type.name))\n const seen = new Set<string>()\n\n allNodes.forEach(({ node, pos }) => {\n const currentId = node.attrs[attributeName]\n\n if (currentId === null || seen.has(currentId)) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n } else {\n seen.add(currentId)\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // Restore stored marks since setNodeMarkup resets them\n tr.setStoredMarks(newState.tr.storedMarks)\n // Mark this transaction as coming from UniqueID to prevent\n // infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n tr.setMeta('addToHistory', false)\n return tr\n }\n\n return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { attributeName, generateID } = this.options\n const transform = combineTransactionSteps(oldState.doc, transactions as Transaction[])\n const { mapping } = transform\n\n // get changed ranges based on the old state\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n const newNodes = findChildrenInRange(newState.doc, newRange, node => {\n return types.includes(node.type.name)\n })\n\n const newIds = newNodes\n .map(({ node }) => node.attrs[attributeName])\n .filter(id => id !== null)\n\n newNodes.forEach(({ node, pos }, i) => {\n // instead of checking `node.attrs[attributeName]` directly\n // we look at the current state of the node within `tr.doc`.\n // this helps to prevent adding new ids to the same node\n // if the node changed multiple times within one transaction\n const id = tr.doc.nodeAt(pos)?.attrs[attributeName]\n\n if (id === null) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n const nextNodeInTr = tr.doc.nodeAt(nextNode.pos)\n if (\n nextNodeInTr?.attrs[attributeName] &&\n nextNodeInTr.attrs[attributeName] !== id\n ) {\n return\n }\n\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n const generatedId = generateID({ node, pos })\n\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generatedId,\n })\n newIds[i] = generatedId\n\n return tr\n }\n\n const duplicatedNewIds = findDuplicates(newIds)\n\n // check if the node doesn’t exist in the old state\n const { deleted } = mapping.invert().mapResult(pos)\n\n const newNode = deleted && duplicatedNewIds.includes(id)\n\n if (newNode) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n }\n })\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // `tr.setNodeMarkup` resets the stored marks\n // so we'll restore them if they exist\n tr.setStoredMarks(newState.tr.storedMarks)\n\n // Mark this transaction as coming from UniqueID\n // to prevent infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n\n return tr\n },\n\n // we register a global drag handler to track the current drag source element\n view(view) {\n const handleDragstart = (event: DragEvent) => {\n dragSourceElement = view.dom.parentElement?.contains(event.target as Element)\n ? view.dom.parentElement\n : null\n }\n\n window.addEventListener('dragstart', handleDragstart)\n\n return {\n destroy() {\n window.removeEventListener('dragstart', handleDragstart)\n },\n }\n },\n\n props: {\n // `handleDOMEvents` is called before `transformPasted`\n // so we can do some checks before\n handleDOMEvents: {\n // only create new ids for dropped content\n // or dropped content while holding `alt`\n // or content is dragged from another editor\n drop: (view, event) => {\n if (\n dragSourceElement !== view.dom.parentElement ||\n event.dataTransfer?.effectAllowed === 'copyMove' ||\n event.dataTransfer?.effectAllowed === 'copy'\n ) {\n dragSourceElement = null\n transformPasted = true\n }\n\n return false\n },\n // always create new ids on pasted content\n paste: () => {\n transformPasted = true\n\n return false\n },\n },\n\n // we’ll remove ids for every pasted node\n // so we can create a new one within `appendTransaction`\n transformPasted: slice => {\n if (!transformPasted) {\n return slice\n }\n\n const { attributeName } = this.options\n const removeId = (fragment: Fragment): Fragment => {\n const list: ProseMirrorNode[] = []\n\n fragment.forEach(node => {\n // don’t touch text nodes\n if (node.isText) {\n list.push(node)\n\n return\n }\n\n // check for any other child nodes\n if (!types.includes(node.type.name)) {\n list.push(node.copy(removeId(node.content)))\n\n return\n }\n\n // remove id\n const nodeWithoutId = node.type.create(\n {\n ...node.attrs,\n [attributeName]: null,\n },\n removeId(node.content),\n node.marks,\n )\n\n list.push(nodeWithoutId)\n })\n\n return Fragment.from(list)\n }\n\n // reset check\n transformPasted = false\n\n return new Slice(removeId(slice.content), slice.openStart, slice.openEnd)\n },\n },\n }),\n ]\n },\n})\n","/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const seen = new Set()\n const duplicates = new Set<any>()\n\n items.forEach(item => {\n if (seen.has(item)) {\n duplicates.add(item)\n } else {\n seen.add(item)\n }\n })\n\n return Array.from(duplicates)\n}\n","import type { Extensions, JSONContent } from '@tiptap/core'\nimport { findChildren, getSchema } from '@tiptap/core'\nimport { Node } from '@tiptap/pm/model'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport type { UniqueID } from './unique-id.js'\n\n/**\n * Creates a new document with unique IDs added to the nodes. Does the same\n * thing as the UniqueID extension, but without the need to create an `Editor`\n * instance. This lets you add unique IDs to the document in the server.\n *\n * When you call it, include the `UniqueID` extension in the `extensions` array.\n * The configuration from the `UniqueID` extension will be picked up\n * automatically, including its configuration options like `types` and\n * `attributeName`.\n *\n * @see `UniqueID` extension for more information.\n *\n * @throws {Error} If the `UniqueID` extension is not found in the extensions array.\n *\n * @example\n * const doc = {\n * type: 'doc',\n * content: [\n * { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }\n * ]\n * }\n * const newDoc = addUniqueIds(doc, [StarterKit, UniqueID.configure({ types: ['paragraph', 'heading'] })])\n * console.log(newDoc)\n * // Result:\n * // {\n * // type: 'doc',\n * // content: [\n * // { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], id: '123' }\n * // ]\n * // }\n *\n * @param doc - A Tiptap JSON document to add unique IDs to.\n * @param extensions - The extensions to use. Must include the `UniqueID` extension.\n * @returns The updated Tiptap JSON document, with the unique IDs added to the nodes.\n */\nexport function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSONContent {\n // Find the UniqueID extension in the extensions array. If it's not found, throw an error.\n const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as\n | typeof UniqueID\n | undefined\n if (!uniqueIDExtension) {\n throw new Error('UniqueID extension not found in the extensions array')\n }\n // Convert the JSON content to a ProseMirror node\n const schema = getSchema([\n ...extensions.filter(ext => ext.name !== 'uniqueID'),\n uniqueIDExtension,\n ])\n const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options\n const types =\n configuredTypes === 'all'\n ? Object.keys(schema.nodes).filter(type => type !== 'doc' && type !== 'text')\n : configuredTypes\n const contentNode = Node.fromJSON(schema, doc)\n\n // Find nodes that don't have a unique ID\n const nodesWithoutId = findChildren(contentNode, node => {\n return !node.attrs[attributeName] && types.includes(node.type.name)\n })\n\n // Edit the document to add unique IDs to the nodes that don't have a unique ID\n let tr = EditorState.create({\n doc: contentNode,\n }).tr\n // oxlint-disable-next-line no-restricted-syntax\n for (const { node, pos } of nodesWithoutId) {\n tr = tr.setNodeAttribute(pos, attributeName, generateID({ node, pos }))\n }\n\n // Return the updated document\n return tr.doc.toJSON()\n}\n"],"mappings":"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","names":["uuidv4","_a","tr","attributeName","generateID","import_core","import_model","import_state"]}
package/dist/index.js CHANGED
@@ -86,8 +86,12 @@ var UniqueID = Extension.create({
86
86
  if (!this.options.updateDocument) {
87
87
  return;
88
88
  }
89
- const collaboration = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaboration");
90
- const collaborationCaret = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaborationCaret");
89
+ const collaboration = this.editor.extensionManager.extensions.find(
90
+ (ext) => ext.name === "collaboration"
91
+ );
92
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
93
+ (ext) => ext.name === "collaborationCaret"
94
+ );
91
95
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean);
92
96
  const collab = collabExtensions.find((ext) => {
93
97
  var _a2;
@@ -117,18 +121,27 @@ var UniqueID = Extension.create({
117
121
  if (collaboration) {
118
122
  if (provider) {
119
123
  provider.on("synced", createIds);
124
+ this.storage.cleanupSyncedListener = () => provider.off("synced", createIds);
120
125
  }
121
126
  } else {
122
127
  return createIds();
123
128
  }
124
129
  },
130
+ onDestroy() {
131
+ var _a, _b;
132
+ (_b = (_a = this.storage).cleanupSyncedListener) == null ? void 0 : _b.call(_a);
133
+ },
125
134
  addProseMirrorPlugins() {
126
135
  if (!this.options.updateDocument) {
127
136
  return [];
128
137
  }
129
138
  const extensionStorage = this.storage;
130
- const collaboration = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaboration");
131
- const collaborationCaret = this.editor.extensionManager.extensions.find((ext) => ext.name === "collaborationCaret");
139
+ const collaboration = this.editor.extensionManager.extensions.find(
140
+ (ext) => ext.name === "collaboration"
141
+ );
142
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
143
+ (ext) => ext.name === "collaborationCaret"
144
+ );
132
145
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean);
133
146
  const collabWithProvider = collabExtensions.find((ext) => {
134
147
  var _a;
@@ -320,7 +333,10 @@ function generateUniqueIds(doc, extensions) {
320
333
  if (!uniqueIDExtension) {
321
334
  throw new Error("UniqueID extension not found in the extensions array");
322
335
  }
323
- const schema = getSchema([...extensions.filter((ext) => ext.name !== "uniqueID"), uniqueIDExtension]);
336
+ const schema = getSchema([
337
+ ...extensions.filter((ext) => ext.name !== "uniqueID"),
338
+ uniqueIDExtension
339
+ ]);
324
340
  const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options;
325
341
  const types = configuredTypes === "all" ? Object.keys(schema.nodes).filter((type) => type !== "doc" && type !== "text") : configuredTypes;
326
342
  const contentNode = Node.fromJSON(schema, doc);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/unique-id.ts","../src/helpers/findDuplicates.ts","../src/generate-unique-ids.ts","../src/index.ts"],"sourcesContent":["import {\n type Extensions,\n combineTransactionSteps,\n Extension,\n findChildren,\n findChildrenInRange,\n getChangedRanges,\n splitExtensions,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Fragment, Slice } from '@tiptap/pm/model'\nimport type { Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { v4 as uuidv4 } from 'uuid'\n\nimport { findDuplicates } from './helpers/findDuplicates.js'\n\nexport type UniqueIDGenerationContext = {\n node: ProseMirrorNode\n pos: number\n}\n\nexport interface UniqueIDOptions {\n /**\n * The name of the attribute to add the unique ID to.\n * @default \"id\"\n */\n attributeName: string\n /**\n * The types of nodes to add unique IDs to.\n * Use `\"all\"` to add IDs to every node type except `doc` and `text`.\n * @default []\n */\n types: string[] | 'all'\n /**\n * The function that generates the unique ID. By default, a UUID v4 is\n * generated. However, you can provide your own function to generate the\n * unique ID based on the node type and the position.\n */\n generateID: (ctx: UniqueIDGenerationContext) => any\n /**\n * Ignore some mutations, for example applied from other users through the collaboration plugin.\n *\n * @default null\n */\n filterTransaction: ((transaction: Transaction) => boolean) | null\n /**\n * Whether to update the document by adding unique IDs to the nodes. Set this\n * property to `false` if the document is in `readonly` mode, is immutable, or\n * you don't want it to be modified.\n *\n * @default true\n */\n updateDocument: boolean\n}\n\nconst resolveTypes = (types: UniqueIDOptions['types'], extensions: Extensions): string[] => {\n if (types !== 'all') {\n return types\n }\n\n const { nodeExtensions } = splitExtensions(extensions)\n\n return nodeExtensions.map(extension => extension.name).filter(type => type !== 'doc' && type !== 'text')\n}\n\nexport const UniqueID = Extension.create<UniqueIDOptions>({\n name: 'uniqueID',\n\n // we’ll set a very high priority to make sure this runs first\n // and is compatible with `appendTransaction` hooks of other extensions\n priority: 10000,\n\n addOptions() {\n return {\n attributeName: 'id',\n types: [],\n generateID: () => uuidv4(),\n filterTransaction: null,\n updateDocument: true,\n }\n },\n\n /**\n * Extension storage for coordination between `addProseMirrorPlugins` and `appendTransaction`.\n * `needsInitialIdGeneration` is set to `true` when the Collaboration extension is\n * detected but no provider is available in its options, deferring ID creation\n * to the first `y-sync$` transaction.\n */\n addStorage() {\n return {\n needsInitialIdGeneration: false,\n }\n },\n\n addGlobalAttributes() {\n const types = resolveTypes(this.options.types, this.extensions)\n\n return [\n {\n types,\n attributes: {\n [this.options.attributeName]: {\n default: null,\n parseHTML: element => element.getAttribute(`data-${this.options.attributeName}`),\n renderHTML: attributes => {\n if (!attributes[this.options.attributeName]) {\n return {}\n }\n\n return {\n [`data-${this.options.attributeName}`]: attributes[this.options.attributeName],\n }\n },\n },\n },\n },\n ]\n },\n\n // check initial content for missing ids\n onCreate() {\n if (!this.options.updateDocument) {\n return\n }\n\n const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')\n const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')\n\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collab = collabExtensions.find(ext => ext?.options?.provider)\n const provider = collab?.options?.provider\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n const { attributeName, generateID } = this.options\n const nodesWithoutId = findChildren(doc, node => {\n return types.includes(node.type.name) && node.attrs[attributeName] === null\n })\n\n nodesWithoutId.forEach(({ node, pos }) => {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n })\n\n tr.setMeta('addToHistory', false)\n\n view.dispatch(tr)\n\n if (provider) {\n provider.off('synced', createIds)\n }\n }\n\n /**\n * We need to handle collaboration a bit different here\n * because we can't automatically add IDs when the provider is not yet synced\n * otherwise we end up with empty paragraphs\n */\n if (collaboration) {\n if (provider) {\n provider.on('synced', createIds)\n }\n // When collaboration is present but no provider is in extension options,\n // needsInitialIdGeneration was already set in addProseMirrorPlugins\n // (which runs synchronously during editor construction, before any\n // y-sync$ transaction can arrive).\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n if (!this.options.updateDocument) {\n return []\n }\n\n // Capture storage via closure so appendTransaction can access `needsInitialIdGeneration`.\n // `extensionManager.extensions` returns different objects than `this` due to\n // Tiptap's child-extension flattening, so we capture the reference directly.\n const extensionStorage = this.storage\n\n // Detect collaboration early — before any y-sync$ transaction can arrive.\n // onCreate runs on a deferred setTimeout(0), so a y-sync$ transaction could\n // be applied before it. Setting the flag here (synchronous during editor\n // construction) ensures the first y-sync$ is always handled.\n const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')\n const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collabWithProvider = collabExtensions.find(ext => ext?.options?.provider)\n\n if (collaboration && !collabWithProvider) {\n extensionStorage.needsInitialIdGeneration = true\n }\n\n let dragSourceElement: Element | null = null\n let transformPasted = false\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n\n return [\n new Plugin({\n key: new PluginKey('uniqueID'),\n\n appendTransaction: (transactions, oldState, newState) => {\n const hasDocChanges =\n transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)\n const filterTransactions =\n this.options.filterTransaction && transactions.some(tr => !this.options.filterTransaction?.(tr))\n\n const isCollabTransaction = transactions.find(tr => tr.getMeta('y-sync$'))\n\n if (isCollabTransaction) {\n if (extensionStorage.needsInitialIdGeneration) {\n extensionStorage.needsInitialIdGeneration = false\n\n // Run full-document ID creation after the first Yjs sync.\n // Use a seen-set so only the second+ occurrence of a duplicated\n // ID is regenerated, preserving one existing ID per value.\n const { tr } = newState\n const { attributeName, generateID } = this.options\n const allNodes = findChildren(newState.doc, node => types.includes(node.type.name))\n const seen = new Set<string>()\n\n allNodes.forEach(({ node, pos }) => {\n const currentId = node.attrs[attributeName]\n\n if (currentId === null || seen.has(currentId)) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n } else {\n seen.add(currentId)\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // Restore stored marks since setNodeMarkup resets them\n tr.setStoredMarks(newState.tr.storedMarks)\n // Mark this transaction as coming from UniqueID to prevent\n // infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n tr.setMeta('addToHistory', false)\n return tr\n }\n\n return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { attributeName, generateID } = this.options\n const transform = combineTransactionSteps(oldState.doc, transactions as Transaction[])\n const { mapping } = transform\n\n // get changed ranges based on the old state\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n const newNodes = findChildrenInRange(newState.doc, newRange, node => {\n return types.includes(node.type.name)\n })\n\n const newIds = newNodes.map(({ node }) => node.attrs[attributeName]).filter(id => id !== null)\n\n newNodes.forEach(({ node, pos }, i) => {\n // instead of checking `node.attrs[attributeName]` directly\n // we look at the current state of the node within `tr.doc`.\n // this helps to prevent adding new ids to the same node\n // if the node changed multiple times within one transaction\n const id = tr.doc.nodeAt(pos)?.attrs[attributeName]\n\n if (id === null) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n const nextNodeInTr = tr.doc.nodeAt(nextNode.pos)\n if (nextNodeInTr?.attrs[attributeName] && nextNodeInTr.attrs[attributeName] !== id) {\n return\n }\n\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n const generatedId = generateID({ node, pos })\n\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generatedId,\n })\n newIds[i] = generatedId\n\n return tr\n }\n\n const duplicatedNewIds = findDuplicates(newIds)\n\n // check if the node doesn’t exist in the old state\n const { deleted } = mapping.invert().mapResult(pos)\n\n const newNode = deleted && duplicatedNewIds.includes(id)\n\n if (newNode) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n }\n })\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // `tr.setNodeMarkup` resets the stored marks\n // so we'll restore them if they exist\n tr.setStoredMarks(newState.tr.storedMarks)\n\n // Mark this transaction as coming from UniqueID\n // to prevent infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n\n return tr\n },\n\n // we register a global drag handler to track the current drag source element\n view(view) {\n const handleDragstart = (event: DragEvent) => {\n dragSourceElement = view.dom.parentElement?.contains(event.target as Element)\n ? view.dom.parentElement\n : null\n }\n\n window.addEventListener('dragstart', handleDragstart)\n\n return {\n destroy() {\n window.removeEventListener('dragstart', handleDragstart)\n },\n }\n },\n\n props: {\n // `handleDOMEvents` is called before `transformPasted`\n // so we can do some checks before\n handleDOMEvents: {\n // only create new ids for dropped content\n // or dropped content while holding `alt`\n // or content is dragged from another editor\n drop: (view, event) => {\n if (\n dragSourceElement !== view.dom.parentElement ||\n event.dataTransfer?.effectAllowed === 'copyMove' ||\n event.dataTransfer?.effectAllowed === 'copy'\n ) {\n dragSourceElement = null\n transformPasted = true\n }\n\n return false\n },\n // always create new ids on pasted content\n paste: () => {\n transformPasted = true\n\n return false\n },\n },\n\n // we’ll remove ids for every pasted node\n // so we can create a new one within `appendTransaction`\n transformPasted: slice => {\n if (!transformPasted) {\n return slice\n }\n\n const { attributeName } = this.options\n const removeId = (fragment: Fragment): Fragment => {\n const list: ProseMirrorNode[] = []\n\n fragment.forEach(node => {\n // don’t touch text nodes\n if (node.isText) {\n list.push(node)\n\n return\n }\n\n // check for any other child nodes\n if (!types.includes(node.type.name)) {\n list.push(node.copy(removeId(node.content)))\n\n return\n }\n\n // remove id\n const nodeWithoutId = node.type.create(\n {\n ...node.attrs,\n [attributeName]: null,\n },\n removeId(node.content),\n node.marks,\n )\n\n list.push(nodeWithoutId)\n })\n\n return Fragment.from(list)\n }\n\n // reset check\n transformPasted = false\n\n return new Slice(removeId(slice.content), slice.openStart, slice.openEnd)\n },\n },\n }),\n ]\n },\n})\n","/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const seen = new Set()\n const duplicates = new Set<any>()\n\n items.forEach(item => {\n if (seen.has(item)) {\n duplicates.add(item)\n } else {\n seen.add(item)\n }\n })\n\n return Array.from(duplicates)\n}\n","import type { Extensions, JSONContent } from '@tiptap/core'\nimport { findChildren, getSchema } from '@tiptap/core'\nimport { Node } from '@tiptap/pm/model'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport type { UniqueID } from './unique-id.js'\n\n/**\n * Creates a new document with unique IDs added to the nodes. Does the same\n * thing as the UniqueID extension, but without the need to create an `Editor`\n * instance. This lets you add unique IDs to the document in the server.\n *\n * When you call it, include the `UniqueID` extension in the `extensions` array.\n * The configuration from the `UniqueID` extension will be picked up\n * automatically, including its configuration options like `types` and\n * `attributeName`.\n *\n * @see `UniqueID` extension for more information.\n *\n * @throws {Error} If the `UniqueID` extension is not found in the extensions array.\n *\n * @example\n * const doc = {\n * type: 'doc',\n * content: [\n * { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }\n * ]\n * }\n * const newDoc = addUniqueIds(doc, [StarterKit, UniqueID.configure({ types: ['paragraph', 'heading'] })])\n * console.log(newDoc)\n * // Result:\n * // {\n * // type: 'doc',\n * // content: [\n * // { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], id: '123' }\n * // ]\n * // }\n *\n * @param doc - A Tiptap JSON document to add unique IDs to.\n * @param extensions - The extensions to use. Must include the `UniqueID` extension.\n * @returns The updated Tiptap JSON document, with the unique IDs added to the nodes.\n */\nexport function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSONContent {\n // Find the UniqueID extension in the extensions array. If it's not found, throw an error.\n const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as typeof UniqueID | undefined\n if (!uniqueIDExtension) {\n throw new Error('UniqueID extension not found in the extensions array')\n }\n // Convert the JSON content to a ProseMirror node\n const schema = getSchema([...extensions.filter(ext => ext.name !== 'uniqueID'), uniqueIDExtension])\n const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options\n const types =\n configuredTypes === 'all'\n ? Object.keys(schema.nodes).filter(type => type !== 'doc' && type !== 'text')\n : configuredTypes\n const contentNode = Node.fromJSON(schema, doc)\n\n // Find nodes that don't have a unique ID\n const nodesWithoutId = findChildren(contentNode, node => {\n return !node.attrs[attributeName] && types.includes(node.type.name)\n })\n\n // Edit the document to add unique IDs to the nodes that don't have a unique ID\n let tr = EditorState.create({\n doc: contentNode,\n }).tr\n // eslint-disable-next-line no-restricted-syntax\n for (const { node, pos } of nodesWithoutId) {\n tr = tr.setNodeAttribute(pos, attributeName, generateID({ node, pos }))\n }\n\n // Return the updated document\n return tr.doc.toJSON()\n}\n","import { UniqueID } from './unique-id.js'\n\nexport * from './generate-unique-ids.js'\nexport * from './unique-id.js'\n\nexport default UniqueID\n"],"mappings":"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","names":["_a","tr","attributeName","generateID","findChildren"]}
1
+ {"version":3,"sources":["../src/unique-id.ts","../src/helpers/findDuplicates.ts","../src/generate-unique-ids.ts","../src/index.ts"],"sourcesContent":["import {\n type Extensions,\n combineTransactionSteps,\n Extension,\n findChildren,\n findChildrenInRange,\n getChangedRanges,\n splitExtensions,\n} from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { Fragment, Slice } from '@tiptap/pm/model'\nimport type { Transaction } from '@tiptap/pm/state'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\nimport { v4 as uuidv4 } from 'uuid'\n\nimport { findDuplicates } from './helpers/findDuplicates.js'\n\nexport type UniqueIDGenerationContext = {\n node: ProseMirrorNode\n pos: number\n}\n\nexport interface UniqueIDOptions {\n /**\n * The name of the attribute to add the unique ID to.\n * @default \"id\"\n */\n attributeName: string\n /**\n * The types of nodes to add unique IDs to.\n * Use `\"all\"` to add IDs to every node type except `doc` and `text`.\n * @default []\n */\n types: string[] | 'all'\n /**\n * The function that generates the unique ID. By default, a UUID v4 is\n * generated. However, you can provide your own function to generate the\n * unique ID based on the node type and the position.\n */\n generateID: (ctx: UniqueIDGenerationContext) => any\n /**\n * Ignore some mutations, for example applied from other users through the collaboration plugin.\n *\n * @default null\n */\n filterTransaction: ((transaction: Transaction) => boolean) | null\n /**\n * Whether to update the document by adding unique IDs to the nodes. Set this\n * property to `false` if the document is in `readonly` mode, is immutable, or\n * you don't want it to be modified.\n *\n * @default true\n */\n updateDocument: boolean\n}\n\nconst resolveTypes = (types: UniqueIDOptions['types'], extensions: Extensions): string[] => {\n if (types !== 'all') {\n return types\n }\n\n const { nodeExtensions } = splitExtensions(extensions)\n\n return nodeExtensions\n .map(extension => extension.name)\n .filter(type => type !== 'doc' && type !== 'text')\n}\n\nexport const UniqueID = Extension.create<UniqueIDOptions>({\n name: 'uniqueID',\n\n // we’ll set a very high priority to make sure this runs first\n // and is compatible with `appendTransaction` hooks of other extensions\n priority: 10000,\n\n addOptions() {\n return {\n attributeName: 'id',\n types: [],\n generateID: () => uuidv4(),\n filterTransaction: null,\n updateDocument: true,\n }\n },\n\n /**\n * Extension storage for coordination between `addProseMirrorPlugins` and `appendTransaction`.\n * `needsInitialIdGeneration` is set to `true` when the Collaboration extension is\n * detected but no provider is available in its options, deferring ID creation\n * to the first `y-sync$` transaction.\n */\n addStorage() {\n return {\n needsInitialIdGeneration: false,\n }\n },\n\n addGlobalAttributes() {\n const types = resolveTypes(this.options.types, this.extensions)\n\n return [\n {\n types,\n attributes: {\n [this.options.attributeName]: {\n default: null,\n parseHTML: element => element.getAttribute(`data-${this.options.attributeName}`),\n renderHTML: attributes => {\n if (!attributes[this.options.attributeName]) {\n return {}\n }\n\n return {\n [`data-${this.options.attributeName}`]: attributes[this.options.attributeName],\n }\n },\n },\n },\n },\n ]\n },\n\n // check initial content for missing ids\n onCreate() {\n if (!this.options.updateDocument) {\n return\n }\n\n const collaboration = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaboration',\n )\n const collaborationCaret = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaborationCaret',\n )\n\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collab = collabExtensions.find(ext => ext?.options?.provider)\n const provider = collab?.options?.provider\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n const { attributeName, generateID } = this.options\n const nodesWithoutId = findChildren(doc, node => {\n return types.includes(node.type.name) && node.attrs[attributeName] === null\n })\n\n nodesWithoutId.forEach(({ node, pos }) => {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n })\n\n tr.setMeta('addToHistory', false)\n\n view.dispatch(tr)\n\n if (provider) {\n provider.off('synced', createIds)\n }\n }\n\n /**\n * We need to handle collaboration a bit different here\n * because we can't automatically add IDs when the provider is not yet synced\n * otherwise we end up with empty paragraphs\n */\n if (collaboration) {\n if (provider) {\n provider.on('synced', createIds)\n // Detach on destroy too, in case the editor is destroyed before the\n // provider syncs — otherwise `createIds` (and the editor it closes over)\n // stays referenced by the shared provider and leaks.\n this.storage.cleanupSyncedListener = () => provider.off('synced', createIds)\n }\n // When collaboration is present but no provider is in extension options,\n // needsInitialIdGeneration was already set in addProseMirrorPlugins\n // (which runs synchronously during editor construction, before any\n // y-sync$ transaction can arrive).\n } else {\n return createIds()\n }\n },\n\n onDestroy() {\n this.storage.cleanupSyncedListener?.()\n },\n\n addProseMirrorPlugins() {\n if (!this.options.updateDocument) {\n return []\n }\n\n // Capture storage via closure so appendTransaction can access `needsInitialIdGeneration`.\n // `extensionManager.extensions` returns different objects than `this` due to\n // Tiptap's child-extension flattening, so we capture the reference directly.\n const extensionStorage = this.storage\n\n // Detect collaboration early — before any y-sync$ transaction can arrive.\n // onCreate runs on a deferred setTimeout(0), so a y-sync$ transaction could\n // be applied before it. Setting the flag here (synchronous during editor\n // construction) ensures the first y-sync$ is always handled.\n const collaboration = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaboration',\n )\n const collaborationCaret = this.editor.extensionManager.extensions.find(\n ext => ext.name === 'collaborationCaret',\n )\n const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)\n const collabWithProvider = collabExtensions.find(ext => ext?.options?.provider)\n\n if (collaboration && !collabWithProvider) {\n extensionStorage.needsInitialIdGeneration = true\n }\n\n let dragSourceElement: Element | null = null\n let transformPasted = false\n const types = resolveTypes(this.options.types, this.editor.extensionManager.extensions)\n\n return [\n new Plugin({\n key: new PluginKey('uniqueID'),\n\n appendTransaction: (transactions, oldState, newState) => {\n const hasDocChanges =\n transactions.some(transaction => transaction.docChanged) &&\n !oldState.doc.eq(newState.doc)\n const filterTransactions =\n this.options.filterTransaction &&\n transactions.some(tr => !this.options.filterTransaction?.(tr))\n\n const isCollabTransaction = transactions.find(tr => tr.getMeta('y-sync$'))\n\n if (isCollabTransaction) {\n if (extensionStorage.needsInitialIdGeneration) {\n extensionStorage.needsInitialIdGeneration = false\n\n // Run full-document ID creation after the first Yjs sync.\n // Use a seen-set so only the second+ occurrence of a duplicated\n // ID is regenerated, preserving one existing ID per value.\n const { tr } = newState\n const { attributeName, generateID } = this.options\n const allNodes = findChildren(newState.doc, node => types.includes(node.type.name))\n const seen = new Set<string>()\n\n allNodes.forEach(({ node, pos }) => {\n const currentId = node.attrs[attributeName]\n\n if (currentId === null || seen.has(currentId)) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n } else {\n seen.add(currentId)\n }\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // Restore stored marks since setNodeMarkup resets them\n tr.setStoredMarks(newState.tr.storedMarks)\n // Mark this transaction as coming from UniqueID to prevent\n // infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n tr.setMeta('addToHistory', false)\n return tr\n }\n\n return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { attributeName, generateID } = this.options\n const transform = combineTransactionSteps(oldState.doc, transactions as Transaction[])\n const { mapping } = transform\n\n // get changed ranges based on the old state\n const changes = getChangedRanges(transform)\n\n changes.forEach(({ newRange }) => {\n const newNodes = findChildrenInRange(newState.doc, newRange, node => {\n return types.includes(node.type.name)\n })\n\n const newIds = newNodes\n .map(({ node }) => node.attrs[attributeName])\n .filter(id => id !== null)\n\n newNodes.forEach(({ node, pos }, i) => {\n // instead of checking `node.attrs[attributeName]` directly\n // we look at the current state of the node within `tr.doc`.\n // this helps to prevent adding new ids to the same node\n // if the node changed multiple times within one transaction\n const id = tr.doc.nodeAt(pos)?.attrs[attributeName]\n\n if (id === null) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n const nextNodeInTr = tr.doc.nodeAt(nextNode.pos)\n if (\n nextNodeInTr?.attrs[attributeName] &&\n nextNodeInTr.attrs[attributeName] !== id\n ) {\n return\n }\n\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n const generatedId = generateID({ node, pos })\n\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generatedId,\n })\n newIds[i] = generatedId\n\n return tr\n }\n\n const duplicatedNewIds = findDuplicates(newIds)\n\n // check if the node doesn’t exist in the old state\n const { deleted } = mapping.invert().mapResult(pos)\n\n const newNode = deleted && duplicatedNewIds.includes(id)\n\n if (newNode) {\n tr.setNodeMarkup(pos, undefined, {\n ...node.attrs,\n [attributeName]: generateID({ node, pos }),\n })\n }\n })\n })\n\n if (!tr.steps.length) {\n return\n }\n\n // `tr.setNodeMarkup` resets the stored marks\n // so we'll restore them if they exist\n tr.setStoredMarks(newState.tr.storedMarks)\n\n // Mark this transaction as coming from UniqueID\n // to prevent infinite loops with other extensions (e.g., TrailingNode)\n tr.setMeta('__uniqueIDTransaction', true)\n\n return tr\n },\n\n // we register a global drag handler to track the current drag source element\n view(view) {\n const handleDragstart = (event: DragEvent) => {\n dragSourceElement = view.dom.parentElement?.contains(event.target as Element)\n ? view.dom.parentElement\n : null\n }\n\n window.addEventListener('dragstart', handleDragstart)\n\n return {\n destroy() {\n window.removeEventListener('dragstart', handleDragstart)\n },\n }\n },\n\n props: {\n // `handleDOMEvents` is called before `transformPasted`\n // so we can do some checks before\n handleDOMEvents: {\n // only create new ids for dropped content\n // or dropped content while holding `alt`\n // or content is dragged from another editor\n drop: (view, event) => {\n if (\n dragSourceElement !== view.dom.parentElement ||\n event.dataTransfer?.effectAllowed === 'copyMove' ||\n event.dataTransfer?.effectAllowed === 'copy'\n ) {\n dragSourceElement = null\n transformPasted = true\n }\n\n return false\n },\n // always create new ids on pasted content\n paste: () => {\n transformPasted = true\n\n return false\n },\n },\n\n // we’ll remove ids for every pasted node\n // so we can create a new one within `appendTransaction`\n transformPasted: slice => {\n if (!transformPasted) {\n return slice\n }\n\n const { attributeName } = this.options\n const removeId = (fragment: Fragment): Fragment => {\n const list: ProseMirrorNode[] = []\n\n fragment.forEach(node => {\n // don’t touch text nodes\n if (node.isText) {\n list.push(node)\n\n return\n }\n\n // check for any other child nodes\n if (!types.includes(node.type.name)) {\n list.push(node.copy(removeId(node.content)))\n\n return\n }\n\n // remove id\n const nodeWithoutId = node.type.create(\n {\n ...node.attrs,\n [attributeName]: null,\n },\n removeId(node.content),\n node.marks,\n )\n\n list.push(nodeWithoutId)\n })\n\n return Fragment.from(list)\n }\n\n // reset check\n transformPasted = false\n\n return new Slice(removeId(slice.content), slice.openStart, slice.openEnd)\n },\n },\n }),\n ]\n },\n})\n","/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const seen = new Set()\n const duplicates = new Set<any>()\n\n items.forEach(item => {\n if (seen.has(item)) {\n duplicates.add(item)\n } else {\n seen.add(item)\n }\n })\n\n return Array.from(duplicates)\n}\n","import type { Extensions, JSONContent } from '@tiptap/core'\nimport { findChildren, getSchema } from '@tiptap/core'\nimport { Node } from '@tiptap/pm/model'\nimport { EditorState } from '@tiptap/pm/state'\n\nimport type { UniqueID } from './unique-id.js'\n\n/**\n * Creates a new document with unique IDs added to the nodes. Does the same\n * thing as the UniqueID extension, but without the need to create an `Editor`\n * instance. This lets you add unique IDs to the document in the server.\n *\n * When you call it, include the `UniqueID` extension in the `extensions` array.\n * The configuration from the `UniqueID` extension will be picked up\n * automatically, including its configuration options like `types` and\n * `attributeName`.\n *\n * @see `UniqueID` extension for more information.\n *\n * @throws {Error} If the `UniqueID` extension is not found in the extensions array.\n *\n * @example\n * const doc = {\n * type: 'doc',\n * content: [\n * { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }] }\n * ]\n * }\n * const newDoc = addUniqueIds(doc, [StarterKit, UniqueID.configure({ types: ['paragraph', 'heading'] })])\n * console.log(newDoc)\n * // Result:\n * // {\n * // type: 'doc',\n * // content: [\n * // { type: 'paragraph', content: [{ type: 'text', text: 'Hello, world!' }], id: '123' }\n * // ]\n * // }\n *\n * @param doc - A Tiptap JSON document to add unique IDs to.\n * @param extensions - The extensions to use. Must include the `UniqueID` extension.\n * @returns The updated Tiptap JSON document, with the unique IDs added to the nodes.\n */\nexport function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSONContent {\n // Find the UniqueID extension in the extensions array. If it's not found, throw an error.\n const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as\n | typeof UniqueID\n | undefined\n if (!uniqueIDExtension) {\n throw new Error('UniqueID extension not found in the extensions array')\n }\n // Convert the JSON content to a ProseMirror node\n const schema = getSchema([\n ...extensions.filter(ext => ext.name !== 'uniqueID'),\n uniqueIDExtension,\n ])\n const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options\n const types =\n configuredTypes === 'all'\n ? Object.keys(schema.nodes).filter(type => type !== 'doc' && type !== 'text')\n : configuredTypes\n const contentNode = Node.fromJSON(schema, doc)\n\n // Find nodes that don't have a unique ID\n const nodesWithoutId = findChildren(contentNode, node => {\n return !node.attrs[attributeName] && types.includes(node.type.name)\n })\n\n // Edit the document to add unique IDs to the nodes that don't have a unique ID\n let tr = EditorState.create({\n doc: contentNode,\n }).tr\n // oxlint-disable-next-line no-restricted-syntax\n for (const { node, pos } of nodesWithoutId) {\n tr = tr.setNodeAttribute(pos, attributeName, generateID({ node, pos }))\n }\n\n // Return the updated document\n return tr.doc.toJSON()\n}\n","import { UniqueID } from './unique-id.js'\n\nexport * from './generate-unique-ids.js'\nexport * from './unique-id.js'\n\nexport default UniqueID\n"],"mappings":"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","names":["_a","tr","attributeName","generateID","findChildren"]}
package/package.json CHANGED
@@ -1,18 +1,30 @@
1
1
  {
2
2
  "name": "@tiptap/extension-unique-id",
3
+ "version": "3.25.0",
3
4
  "description": "unique id extension for tiptap",
4
- "version": "3.23.6",
5
- "homepage": "https://tiptap.dev/api/extensions/unique-id",
6
5
  "keywords": [
7
6
  "tiptap",
8
7
  "tiptap extension"
9
8
  ],
9
+ "homepage": "https://tiptap.dev/api/extensions/unique-id",
10
10
  "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/ueberdosis/tiptap",
14
+ "directory": "packages/extension-unique-id"
15
+ },
11
16
  "funding": {
12
17
  "type": "github",
13
18
  "url": "https://github.com/sponsors/ueberdosis"
14
19
  },
20
+ "files": [
21
+ "src",
22
+ "dist"
23
+ ],
15
24
  "type": "module",
25
+ "main": "dist/index.cjs",
26
+ "module": "dist/index.js",
27
+ "types": "dist/index.d.ts",
16
28
  "exports": {
17
29
  ".": {
18
30
  "types": {
@@ -23,31 +35,18 @@
23
35
  "require": "./dist/index.cjs"
24
36
  }
25
37
  },
26
- "main": "dist/index.cjs",
27
- "module": "dist/index.js",
28
- "types": "dist/index.d.ts",
29
- "files": [
30
- "src",
31
- "dist"
32
- ],
33
- "peerDependencies": {
34
- "@tiptap/core": "3.23.6",
35
- "@tiptap/pm": "3.23.6"
36
- },
37
38
  "dependencies": {
38
39
  "uuid": "^14.0.0"
39
40
  },
40
41
  "devDependencies": {
41
- "@tiptap/core": "^3.23.6",
42
- "@tiptap/pm": "^3.23.6"
42
+ "@tiptap/core": "^3.25.0",
43
+ "@tiptap/pm": "^3.25.0"
43
44
  },
44
- "repository": {
45
- "type": "git",
46
- "url": "https://github.com/ueberdosis/tiptap",
47
- "directory": "packages/extension-unique-id"
45
+ "peerDependencies": {
46
+ "@tiptap/core": "3.25.0",
47
+ "@tiptap/pm": "3.25.0"
48
48
  },
49
49
  "scripts": {
50
- "build": "tsup",
51
- "lint": "prettier ./src/ --check && eslint --cache --quiet --no-error-on-unmatched-pattern ./src/"
50
+ "build": "tsup"
52
51
  }
53
52
  }
@@ -42,12 +42,17 @@ import type { UniqueID } from './unique-id.js'
42
42
  */
43
43
  export function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSONContent {
44
44
  // Find the UniqueID extension in the extensions array. If it's not found, throw an error.
45
- const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as typeof UniqueID | undefined
45
+ const uniqueIDExtension = extensions.find(ext => ext.name === 'uniqueID') as
46
+ | typeof UniqueID
47
+ | undefined
46
48
  if (!uniqueIDExtension) {
47
49
  throw new Error('UniqueID extension not found in the extensions array')
48
50
  }
49
51
  // Convert the JSON content to a ProseMirror node
50
- const schema = getSchema([...extensions.filter(ext => ext.name !== 'uniqueID'), uniqueIDExtension])
52
+ const schema = getSchema([
53
+ ...extensions.filter(ext => ext.name !== 'uniqueID'),
54
+ uniqueIDExtension,
55
+ ])
51
56
  const { types: configuredTypes, attributeName, generateID } = uniqueIDExtension.options
52
57
  const types =
53
58
  configuredTypes === 'all'
@@ -64,7 +69,7 @@ export function generateUniqueIds(doc: JSONContent, extensions: Extensions): JSO
64
69
  let tr = EditorState.create({
65
70
  doc: contentNode,
66
71
  }).tr
67
- // eslint-disable-next-line no-restricted-syntax
72
+ // oxlint-disable-next-line no-restricted-syntax
68
73
  for (const { node, pos } of nodesWithoutId) {
69
74
  tr = tr.setNodeAttribute(pos, attributeName, generateID({ node, pos }))
70
75
  }
package/src/unique-id.ts CHANGED
@@ -61,7 +61,9 @@ const resolveTypes = (types: UniqueIDOptions['types'], extensions: Extensions):
61
61
 
62
62
  const { nodeExtensions } = splitExtensions(extensions)
63
63
 
64
- return nodeExtensions.map(extension => extension.name).filter(type => type !== 'doc' && type !== 'text')
64
+ return nodeExtensions
65
+ .map(extension => extension.name)
66
+ .filter(type => type !== 'doc' && type !== 'text')
65
67
  }
66
68
 
67
69
  export const UniqueID = Extension.create<UniqueIDOptions>({
@@ -124,8 +126,12 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
124
126
  return
125
127
  }
126
128
 
127
- const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')
128
- const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')
129
+ const collaboration = this.editor.extensionManager.extensions.find(
130
+ ext => ext.name === 'collaboration',
131
+ )
132
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
133
+ ext => ext.name === 'collaborationCaret',
134
+ )
129
135
 
130
136
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)
131
137
  const collab = collabExtensions.find(ext => ext?.options?.provider)
@@ -164,6 +170,10 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
164
170
  if (collaboration) {
165
171
  if (provider) {
166
172
  provider.on('synced', createIds)
173
+ // Detach on destroy too, in case the editor is destroyed before the
174
+ // provider syncs — otherwise `createIds` (and the editor it closes over)
175
+ // stays referenced by the shared provider and leaks.
176
+ this.storage.cleanupSyncedListener = () => provider.off('synced', createIds)
167
177
  }
168
178
  // When collaboration is present but no provider is in extension options,
169
179
  // needsInitialIdGeneration was already set in addProseMirrorPlugins
@@ -174,6 +184,10 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
174
184
  }
175
185
  },
176
186
 
187
+ onDestroy() {
188
+ this.storage.cleanupSyncedListener?.()
189
+ },
190
+
177
191
  addProseMirrorPlugins() {
178
192
  if (!this.options.updateDocument) {
179
193
  return []
@@ -188,8 +202,12 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
188
202
  // onCreate runs on a deferred setTimeout(0), so a y-sync$ transaction could
189
203
  // be applied before it. Setting the flag here (synchronous during editor
190
204
  // construction) ensures the first y-sync$ is always handled.
191
- const collaboration = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaboration')
192
- const collaborationCaret = this.editor.extensionManager.extensions.find(ext => ext.name === 'collaborationCaret')
205
+ const collaboration = this.editor.extensionManager.extensions.find(
206
+ ext => ext.name === 'collaboration',
207
+ )
208
+ const collaborationCaret = this.editor.extensionManager.extensions.find(
209
+ ext => ext.name === 'collaborationCaret',
210
+ )
193
211
  const collabExtensions = [collaboration, collaborationCaret].filter(Boolean)
194
212
  const collabWithProvider = collabExtensions.find(ext => ext?.options?.provider)
195
213
 
@@ -207,9 +225,11 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
207
225
 
208
226
  appendTransaction: (transactions, oldState, newState) => {
209
227
  const hasDocChanges =
210
- transactions.some(transaction => transaction.docChanged) && !oldState.doc.eq(newState.doc)
228
+ transactions.some(transaction => transaction.docChanged) &&
229
+ !oldState.doc.eq(newState.doc)
211
230
  const filterTransactions =
212
- this.options.filterTransaction && transactions.some(tr => !this.options.filterTransaction?.(tr))
231
+ this.options.filterTransaction &&
232
+ transactions.some(tr => !this.options.filterTransaction?.(tr))
213
233
 
214
234
  const isCollabTransaction = transactions.find(tr => tr.getMeta('y-sync$'))
215
235
 
@@ -272,7 +292,9 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
272
292
  return types.includes(node.type.name)
273
293
  })
274
294
 
275
- const newIds = newNodes.map(({ node }) => node.attrs[attributeName]).filter(id => id !== null)
295
+ const newIds = newNodes
296
+ .map(({ node }) => node.attrs[attributeName])
297
+ .filter(id => id !== null)
276
298
 
277
299
  newNodes.forEach(({ node, pos }, i) => {
278
300
  // instead of checking `node.attrs[attributeName]` directly
@@ -294,7 +316,10 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
294
316
 
295
317
  if (nextNode && node.content.size === 0) {
296
318
  const nextNodeInTr = tr.doc.nodeAt(nextNode.pos)
297
- if (nextNodeInTr?.attrs[attributeName] && nextNodeInTr.attrs[attributeName] !== id) {
319
+ if (
320
+ nextNodeInTr?.attrs[attributeName] &&
321
+ nextNodeInTr.attrs[attributeName] !== id
322
+ ) {
298
323
  return
299
324
  }
300
325