From a1f3676217372e373d6824d531ef5ac41efbe483 Mon Sep 17 00:00:00 2001 From: Mirone Date: Mon, 9 Dec 2024 23:29:45 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20prevent=20duplicated=20i?= =?UTF-8?q?ds=20for=20headings=20(#1580)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 prevent duplicated ids for headings ✅ Closes: #1567 * test: add e2e --- e2e/tests/input/heading.spec.ts | 18 +++++++++++++++++- .../src/plugin/sync-heading-id-plugin.ts | 9 ++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/e2e/tests/input/heading.spec.ts b/e2e/tests/input/heading.spec.ts index a7f9dfbd21d..3c61234a1a4 100644 --- a/e2e/tests/input/heading.spec.ts +++ b/e2e/tests/input/heading.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test' -import { focusEditor, getMarkdown } from '../misc' +import { focusEditor, getMarkdown, waitNextFrame } from '../misc' test.beforeEach(async ({ page }) => { await page.goto('/preset-commonmark/') @@ -21,3 +21,19 @@ test('heading', async ({ page }) => { const markdown2 = await getMarkdown(page) expect(markdown2).toBe('# Heading1\n\n## Heading 2\n') }) + +test('should generate different id for same heading', async ({ page }) => { + const editor = page.locator('.editor') + await focusEditor(page) + await page.keyboard.type('# Heading1') + await page.keyboard.press('Enter') + await page.keyboard.type('# Heading1') + await page.keyboard.press('Enter') + await page.keyboard.type('## Heading1') + await page.keyboard.press('Enter') + + await waitNextFrame(page) + await expect(editor.locator('h1').first()).toHaveAttribute('id', 'heading1') + await expect(editor.locator('h1').last()).toHaveAttribute('id', 'heading1-#2') + await expect(editor.locator('h2')).toHaveAttribute('id', 'heading1-#3') +}) diff --git a/packages/plugins/preset-commonmark/src/plugin/sync-heading-id-plugin.ts b/packages/plugins/preset-commonmark/src/plugin/sync-heading-id-plugin.ts index 0b668e0cc36..f96a0fbc7d3 100644 --- a/packages/plugins/preset-commonmark/src/plugin/sync-heading-id-plugin.ts +++ b/packages/plugins/preset-commonmark/src/plugin/sync-heading-id-plugin.ts @@ -16,13 +16,20 @@ export const syncHeadingIdPlugin = $prose((ctx) => { const tr = view.state.tr.setMeta('addToHistory', false) let found = false + const idMap: Record = {} view.state.doc.descendants((node, pos) => { if (node.type === headingSchema.type(ctx)) { if (node.textContent.trim().length === 0) return const attrs = node.attrs - const id = getId(node) + let id = getId(node) + if (idMap[id]) { + idMap[id]! += 1 + id += `-#${idMap[id]}` + } else { + idMap[id] = 1 + } if (attrs.id !== id) { found = true