@tiptap/extension-unique-id 3.2.1 → 3.3.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
@@ -98,7 +98,7 @@ var UniqueID = import_core.Extension.create({
98
98
  nodesWithoutId.forEach(({ node, pos }) => {
99
99
  tr.setNodeMarkup(pos, void 0, {
100
100
  ...node.attrs,
101
- [attributeName]: generateID()
101
+ [attributeName]: generateID({ node, pos })
102
102
  });
103
103
  });
104
104
  tr.setMeta("addToHistory", false);
@@ -151,7 +151,7 @@ var UniqueID = import_core.Extension.create({
151
151
  if (id === null) {
152
152
  tr.setNodeMarkup(pos, void 0, {
153
153
  ...node.attrs,
154
- [attributeName]: generateID()
154
+ [attributeName]: generateID({ node, pos })
155
155
  });
156
156
  return;
157
157
  }
@@ -165,7 +165,7 @@ var UniqueID = import_core.Extension.create({
165
165
  if (nextNode.node.attrs[attributeName]) {
166
166
  return;
167
167
  }
168
- const generatedId = generateID();
168
+ const generatedId = generateID({ node, pos });
169
169
  tr.setNodeMarkup(pos, void 0, {
170
170
  ...node.attrs,
171
171
  [attributeName]: generatedId
@@ -179,7 +179,7 @@ var UniqueID = import_core.Extension.create({
179
179
  if (newNode) {
180
180
  tr.setNodeMarkup(pos, void 0, {
181
181
  ...node.attrs,
182
- [attributeName]: generateID()
182
+ [attributeName]: generateID({ node, pos })
183
183
  });
184
184
  }
185
185
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/unique-id.ts","../src/helpers/removeDuplicates.ts","../src/helpers/findDuplicates.ts"],"sourcesContent":["import { UniqueID } from './unique-id.js'\n\nexport * from './unique-id.js'\n\nexport default UniqueID\n","import { combineTransactionSteps, Extension, findChildren, findChildrenInRange, getChangedRanges } 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 interface UniqueIDOptions {\n attributeName: string\n types: string[]\n generateID: () => any\n filterTransaction: ((transaction: Transaction) => boolean) | null\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 }\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.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 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 collab = collaboration || collaborationCaret\n const provider = collab?.options ? collab.options.provider : undefined\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const { types, 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(),\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 (collab) {\n if (!provider) {\n return createIds()\n }\n\n provider.on('synced', createIds)\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n let dragSourceElement: Element | null = null\n let transformPasted = false\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 return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { types, 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(),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n if (nextNode.node.attrs[attributeName]) {\n return\n }\n\n const generatedId = generateID()\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(),\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 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 { types, 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 * Removes duplicated values within an array.\n * Supports numbers, strings and objects.\n */\nexport function removeDuplicates<T>(array: T[], by = JSON.stringify): T[] {\n const seen: Record<any, any> = {}\n\n return array.filter(item => {\n const key = by(item)\n\n return Object.prototype.hasOwnProperty.call(seen, key) ? false : (seen[key] = true)\n })\n}\n","import { removeDuplicates } from './removeDuplicates.js'\n\n/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const filtered = items.filter((el, index) => items.indexOf(el) !== index)\n const duplicates = removeDuplicates(filtered)\n\n return duplicates\n}\n"],"mappings":"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","names":["uuidv4","tr"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/unique-id.ts","../src/helpers/removeDuplicates.ts","../src/helpers/findDuplicates.ts"],"sourcesContent":["import { UniqueID } from './unique-id.js'\n\nexport * from './unique-id.js'\n\nexport default UniqueID\n","import { combineTransactionSteps, Extension, findChildren, findChildrenInRange, getChangedRanges } 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 attributeName: string\n types: string[]\n generateID: (ctx: UniqueIDGenerationContext) => any\n filterTransaction: ((transaction: Transaction) => boolean) | null\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 }\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.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 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 collab = collaboration || collaborationCaret\n const provider = collab?.options ? collab.options.provider : undefined\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const { types, 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 (collab) {\n if (!provider) {\n return createIds()\n }\n\n provider.on('synced', createIds)\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n let dragSourceElement: Element | null = null\n let transformPasted = false\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 return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { types, 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 tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n if (nextNode.node.attrs[attributeName]) {\n return\n }\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 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 { types, 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 * Removes duplicated values within an array.\n * Supports numbers, strings and objects.\n */\nexport function removeDuplicates<T>(array: T[], by = JSON.stringify): T[] {\n const seen: Record<any, any> = {}\n\n return array.filter(item => {\n const key = by(item)\n\n return Object.prototype.hasOwnProperty.call(seen, key) ? false : (seen[key] = true)\n })\n}\n","import { removeDuplicates } from './removeDuplicates.js'\n\n/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const filtered = items.filter((el, index) => items.indexOf(el) !== index)\n const duplicates = removeDuplicates(filtered)\n\n return duplicates\n}\n"],"mappings":"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","names":["uuidv4","tr"]}
package/dist/index.d.cts CHANGED
@@ -1,12 +1,17 @@
1
1
  import { Extension } from '@tiptap/core';
2
+ import { Node } from '@tiptap/pm/model';
2
3
  import { Transaction } from '@tiptap/pm/state';
3
4
 
5
+ type UniqueIDGenerationContext = {
6
+ node: Node;
7
+ pos: number;
8
+ };
4
9
  interface UniqueIDOptions {
5
10
  attributeName: string;
6
11
  types: string[];
7
- generateID: () => any;
12
+ generateID: (ctx: UniqueIDGenerationContext) => any;
8
13
  filterTransaction: ((transaction: Transaction) => boolean) | null;
9
14
  }
10
15
  declare const UniqueID: Extension<UniqueIDOptions, any>;
11
16
 
12
- export { UniqueID, type UniqueIDOptions, UniqueID as default };
17
+ export { UniqueID, type UniqueIDGenerationContext, type UniqueIDOptions, UniqueID as default };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  import { Extension } from '@tiptap/core';
2
+ import { Node } from '@tiptap/pm/model';
2
3
  import { Transaction } from '@tiptap/pm/state';
3
4
 
5
+ type UniqueIDGenerationContext = {
6
+ node: Node;
7
+ pos: number;
8
+ };
4
9
  interface UniqueIDOptions {
5
10
  attributeName: string;
6
11
  types: string[];
7
- generateID: () => any;
12
+ generateID: (ctx: UniqueIDGenerationContext) => any;
8
13
  filterTransaction: ((transaction: Transaction) => boolean) | null;
9
14
  }
10
15
  declare const UniqueID: Extension<UniqueIDOptions, any>;
11
16
 
12
- export { UniqueID, type UniqueIDOptions, UniqueID as default };
17
+ export { UniqueID, type UniqueIDGenerationContext, type UniqueIDOptions, UniqueID as default };
package/dist/index.js CHANGED
@@ -71,7 +71,7 @@ var UniqueID = Extension.create({
71
71
  nodesWithoutId.forEach(({ node, pos }) => {
72
72
  tr.setNodeMarkup(pos, void 0, {
73
73
  ...node.attrs,
74
- [attributeName]: generateID()
74
+ [attributeName]: generateID({ node, pos })
75
75
  });
76
76
  });
77
77
  tr.setMeta("addToHistory", false);
@@ -124,7 +124,7 @@ var UniqueID = Extension.create({
124
124
  if (id === null) {
125
125
  tr.setNodeMarkup(pos, void 0, {
126
126
  ...node.attrs,
127
- [attributeName]: generateID()
127
+ [attributeName]: generateID({ node, pos })
128
128
  });
129
129
  return;
130
130
  }
@@ -138,7 +138,7 @@ var UniqueID = Extension.create({
138
138
  if (nextNode.node.attrs[attributeName]) {
139
139
  return;
140
140
  }
141
- const generatedId = generateID();
141
+ const generatedId = generateID({ node, pos });
142
142
  tr.setNodeMarkup(pos, void 0, {
143
143
  ...node.attrs,
144
144
  [attributeName]: generatedId
@@ -152,7 +152,7 @@ var UniqueID = Extension.create({
152
152
  if (newNode) {
153
153
  tr.setNodeMarkup(pos, void 0, {
154
154
  ...node.attrs,
155
- [attributeName]: generateID()
155
+ [attributeName]: generateID({ node, pos })
156
156
  });
157
157
  }
158
158
  });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/unique-id.ts","../src/helpers/removeDuplicates.ts","../src/helpers/findDuplicates.ts","../src/index.ts"],"sourcesContent":["import { combineTransactionSteps, Extension, findChildren, findChildrenInRange, getChangedRanges } 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 interface UniqueIDOptions {\n attributeName: string\n types: string[]\n generateID: () => any\n filterTransaction: ((transaction: Transaction) => boolean) | null\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 }\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.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 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 collab = collaboration || collaborationCaret\n const provider = collab?.options ? collab.options.provider : undefined\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const { types, 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(),\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 (collab) {\n if (!provider) {\n return createIds()\n }\n\n provider.on('synced', createIds)\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n let dragSourceElement: Element | null = null\n let transformPasted = false\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 return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { types, 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(),\n })\n\n return\n }\n\n const nextNode = newNodes[i + 1]\n\n if (nextNode && node.content.size === 0) {\n tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n if (nextNode.node.attrs[attributeName]) {\n return\n }\n\n const generatedId = generateID()\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(),\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 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 { types, 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 * Removes duplicated values within an array.\n * Supports numbers, strings and objects.\n */\nexport function removeDuplicates<T>(array: T[], by = JSON.stringify): T[] {\n const seen: Record<any, any> = {}\n\n return array.filter(item => {\n const key = by(item)\n\n return Object.prototype.hasOwnProperty.call(seen, key) ? false : (seen[key] = true)\n })\n}\n","import { removeDuplicates } from './removeDuplicates.js'\n\n/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const filtered = items.filter((el, index) => items.indexOf(el) !== index)\n const duplicates = removeDuplicates(filtered)\n\n return duplicates\n}\n","import { UniqueID } from './unique-id.js'\n\nexport * from './unique-id.js'\n\nexport default UniqueID\n"],"mappings":"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","names":["tr"]}
1
+ {"version":3,"sources":["../src/unique-id.ts","../src/helpers/removeDuplicates.ts","../src/helpers/findDuplicates.ts","../src/index.ts"],"sourcesContent":["import { combineTransactionSteps, Extension, findChildren, findChildrenInRange, getChangedRanges } 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 attributeName: string\n types: string[]\n generateID: (ctx: UniqueIDGenerationContext) => any\n filterTransaction: ((transaction: Transaction) => boolean) | null\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 }\n },\n\n addGlobalAttributes() {\n return [\n {\n types: this.options.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 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 collab = collaboration || collaborationCaret\n const provider = collab?.options ? collab.options.provider : undefined\n\n const createIds = () => {\n const { view, state } = this.editor\n const { tr, doc } = state\n const { types, 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 (collab) {\n if (!provider) {\n return createIds()\n }\n\n provider.on('synced', createIds)\n } else {\n return createIds()\n }\n },\n\n addProseMirrorPlugins() {\n let dragSourceElement: Element | null = null\n let transformPasted = false\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 return\n }\n\n if (!hasDocChanges || filterTransactions) {\n return\n }\n\n const { tr } = newState\n\n const { types, 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 tr.setNodeMarkup(nextNode.pos, undefined, {\n ...nextNode.node.attrs,\n [attributeName]: id,\n })\n newIds[i + 1] = id\n\n if (nextNode.node.attrs[attributeName]) {\n return\n }\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 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 { types, 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 * Removes duplicated values within an array.\n * Supports numbers, strings and objects.\n */\nexport function removeDuplicates<T>(array: T[], by = JSON.stringify): T[] {\n const seen: Record<any, any> = {}\n\n return array.filter(item => {\n const key = by(item)\n\n return Object.prototype.hasOwnProperty.call(seen, key) ? false : (seen[key] = true)\n })\n}\n","import { removeDuplicates } from './removeDuplicates.js'\n\n/**\n * Returns a list of duplicated items within an array.\n */\nexport function findDuplicates(items: any[]): any[] {\n const filtered = items.filter((el, index) => items.indexOf(el) !== index)\n const duplicates = removeDuplicates(filtered)\n\n return duplicates\n}\n","import { UniqueID } from './unique-id.js'\n\nexport * from './unique-id.js'\n\nexport default UniqueID\n"],"mappings":"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","names":["tr"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tiptap/extension-unique-id",
3
3
  "description": "unique id extension for tiptap",
4
- "version": "3.2.1",
4
+ "version": "3.3.0",
5
5
  "homepage": "https://tiptap.dev/api/extensions/unique-id",
6
6
  "keywords": [
7
7
  "tiptap",
@@ -31,16 +31,16 @@
31
31
  "dist"
32
32
  ],
33
33
  "peerDependencies": {
34
- "@tiptap/core": "^3.2.1",
35
- "@tiptap/pm": "^3.2.1"
34
+ "@tiptap/core": "^3.3.0",
35
+ "@tiptap/pm": "^3.3.0"
36
36
  },
37
37
  "dependencies": {
38
38
  "uuid": "^10.0.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/uuid": "^10.0.0",
42
- "@tiptap/core": "^3.2.1",
43
- "@tiptap/pm": "^3.2.1"
42
+ "@tiptap/core": "^3.3.0",
43
+ "@tiptap/pm": "^3.3.0"
44
44
  },
45
45
  "repository": {
46
46
  "type": "git",
package/src/unique-id.ts CHANGED
@@ -7,10 +7,15 @@ import { v4 as uuidv4 } from 'uuid'
7
7
 
8
8
  import { findDuplicates } from './helpers/findDuplicates.js'
9
9
 
10
+ export type UniqueIDGenerationContext = {
11
+ node: ProseMirrorNode
12
+ pos: number
13
+ }
14
+
10
15
  export interface UniqueIDOptions {
11
16
  attributeName: string
12
17
  types: string[]
13
- generateID: () => any
18
+ generateID: (ctx: UniqueIDGenerationContext) => any
14
19
  filterTransaction: ((transaction: Transaction) => boolean) | null
15
20
  }
16
21
 
@@ -71,7 +76,7 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
71
76
  nodesWithoutId.forEach(({ node, pos }) => {
72
77
  tr.setNodeMarkup(pos, undefined, {
73
78
  ...node.attrs,
74
- [attributeName]: generateID(),
79
+ [attributeName]: generateID({ node, pos }),
75
80
  })
76
81
  })
77
82
 
@@ -150,7 +155,7 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
150
155
  if (id === null) {
151
156
  tr.setNodeMarkup(pos, undefined, {
152
157
  ...node.attrs,
153
- [attributeName]: generateID(),
158
+ [attributeName]: generateID({ node, pos }),
154
159
  })
155
160
 
156
161
  return
@@ -169,7 +174,7 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
169
174
  return
170
175
  }
171
176
 
172
- const generatedId = generateID()
177
+ const generatedId = generateID({ node, pos })
173
178
 
174
179
  tr.setNodeMarkup(pos, undefined, {
175
180
  ...node.attrs,
@@ -190,7 +195,7 @@ export const UniqueID = Extension.create<UniqueIDOptions>({
190
195
  if (newNode) {
191
196
  tr.setNodeMarkup(pos, undefined, {
192
197
  ...node.attrs,
193
- [attributeName]: generateID(),
198
+ [attributeName]: generateID({ node, pos }),
194
199
  })
195
200
  }
196
201
  })