snapshot of https://github.com/ueberdosis/tiptap.git (main, 2026-09-15; blobs > 131072B omitted)
Some checks failed
Verify / Install node dependencies (push) Failing after 2s
Verify / Build packages (push) Has been skipped
Verify / Check linting & formatting (push) Has been skipped
Verify / Run unit tests (push) Has been skipped
Verify / Run e2e tests (shard 1/4) (push) Has been skipped
Verify / Run e2e tests (shard 2/4) (push) Has been skipped
Verify / Run e2e tests (shard 3/4) (push) Has been skipped
Verify / Run e2e tests (shard 4/4) (push) Has been skipped
Publish / Resolve publish config (push) Failing after 2s
Publish / Send notification to Slack (push) Has been skipped
Publish / Send failure notification to Slack (push) Failing after 8s
Verify / Merge e2e reports (push) Has been skipped
Publish / Build (push) Has been skipped
Publish / Bump Versions OR Release to Registry (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:11:02 +05:00
commit 083a46e93b
2115 changed files with 245958 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
import type { Extensions, JSONContent } from '@tiptap/core'
import { getSchema } from '@tiptap/core'
import { Node as PMNode } from '@tiptap/pm/model'
import { EditorState } from '@tiptap/pm/state'
import { v4 as uuidv4 } from 'uuid'
import type { TableOfContents } from './tableOfContents.js'
/**
* Creates a new document with `id` and `data-toc-id` attributes added to
* anchor-type nodes (headings by default). Does the same job as the
* `TableOfContents` ProseMirror plugin, but without needing an `Editor`
* instance — so it can run on the server before `renderToHTMLString`.
*
* The configuration from the `TableOfContents` extension (in particular
* `anchorTypes` and `getId`) is picked up automatically. Nodes that already
* have a unique `data-toc-id` are left untouched; duplicates and missing IDs
* are re-generated to match the plugin's behavior.
*
* @throws {Error} If the `TableOfContents` extension is not in the array.
*
* @example
* const doc = {
* type: 'doc',
* content: [
* { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Intro' }] },
* ],
* }
* const docWithIds = generateTocIds(doc, [StarterKit, TableOfContents])
* // → heading now has `id` and `data-toc-id` attributes
* const html = renderToHTMLString({ content: docWithIds, extensions: [...] })
*
* @param doc - A Tiptap JSON document to add anchor IDs to.
* @param extensions - The extensions to use. Must include the `TableOfContents` extension (or one extending it).
* @param extensionName - Optional name of the `TableOfContents` extension to look up. Defaults to `'tableOfContents'`. Override this if you have extended the base extension and changed its `name`.
* @returns The updated Tiptap JSON document.
*/
export function generateTocIds(
doc: JSONContent,
extensions: Extensions,
extensionName = 'tableOfContents',
): JSONContent {
const tocExtension = extensions.find(ext => ext.name === extensionName) as
| typeof TableOfContents
| undefined
if (!tocExtension) {
throw new Error(
`TableOfContents extension (name: "${extensionName}") not found in the extensions array`,
)
}
const schema = getSchema([...extensions.filter(ext => ext.name !== extensionName), tocExtension])
const anchorTypes = tocExtension.options.anchorTypes ?? ['heading']
const getId = tocExtension.options.getId ?? (() => uuidv4())
const contentNode = PMNode.fromJSON(schema, doc)
// Mirrors the TableOfContents plugin's appendTransaction in `plugin.ts`:
// an anchor node gets a fresh id when its `data-toc-id` is missing or
// already seen earlier in the document. We track original ids — including
// null — to match the plugin's behavior exactly.
type Update = { pos: number; attrs: Record<string, unknown> }
const updates: Update[] = []
const existingIds: Array<string | null | undefined> = []
contentNode.descendants((node, pos) => {
if (!anchorTypes.includes(node.type.name) || node.textContent.length === 0) {
return
}
const currentId = node.attrs['data-toc-id'] as string | null | undefined
if (currentId == null || existingIds.includes(currentId)) {
const id = getId(node.textContent)
updates.push({ pos, attrs: { ...node.attrs, id, 'data-toc-id': id } })
}
existingIds.push(currentId)
})
const tr = EditorState.create({ doc: contentNode }).tr
updates.forEach(({ pos, attrs }) => {
tr.setNodeMarkup(pos, undefined, attrs)
})
return tr.doc.toJSON()
}