From 9e0901ce25be3038723e726d5ce20cf9dd6e8de7 Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 13 Sep 2026 17:24:54 +0000 Subject: [PATCH] feat(web): mark real document blocks without overlay Add data-testid=document-block plus block id on every wrapper so pure-mode editors stay addressable without changing visuals. Co-authored-by: Cursor --- web/src/components/doc/BlockRenderer.tsx | 2 ++ web/src/lib/documentBlockDom.test.ts | 28 ++++++++++++++++++++++++ web/src/lib/documentBlockDom.ts | 15 +++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 web/src/lib/documentBlockDom.test.ts create mode 100644 web/src/lib/documentBlockDom.ts diff --git a/web/src/components/doc/BlockRenderer.tsx b/web/src/components/doc/BlockRenderer.tsx index 5654ec64..4a55d77f 100644 --- a/web/src/components/doc/BlockRenderer.tsx +++ b/web/src/components/doc/BlockRenderer.tsx @@ -12,6 +12,7 @@ import { MathBlockView } from './blocks/MathBlockView'; import { ListBlockView } from './blocks/ListBlockView'; import { BlockContextMenu } from './BlockContextMenu'; import { canAddBlockComment } from '../../lib/commentPolicy'; +import { documentBlockDomAttrs } from '../../lib/documentBlockDom'; import { Plus, ArrowUp, ArrowDown, Trash2, MessageSquare } from 'lucide-react'; export interface BlockRendererProps { @@ -150,6 +151,7 @@ export const BlockRenderer: React.FC = ({
setHoveredIndex(idx)} onMouseLeave={() => setHoveredIndex(null)} diff --git a/web/src/lib/documentBlockDom.test.ts b/web/src/lib/documentBlockDom.test.ts new file mode 100644 index 00000000..a08c7418 --- /dev/null +++ b/web/src/lib/documentBlockDom.test.ts @@ -0,0 +1,28 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { + DOCUMENT_BLOCK_TESTID, + documentBlockDomAttrs, +} from './documentBlockDom'; + +describe('documentBlockDomAttrs', () => { + it('marks a real DocumentBlock in pure mode without overlay kind', () => { + const attrs = documentBlockDomAttrs({ id: 'blk_intake_p1' }); + assert.equal(attrs['data-testid'], DOCUMENT_BLOCK_TESTID); + assert.equal(attrs['data-testid'], 'document-block'); + assert.equal(attrs['data-document-block'], 'blk_intake_p1'); + assert.equal(attrs['data-block-id'], 'blk_intake_p1'); + assert.equal('data-overlay-kind' in attrs, false); + }); + + it('keeps the same stable attrs when overlay is later attached separately', () => { + const pure = documentBlockDomAttrs({ id: 'blk_h1' }); + const withOverlay = { + ...documentBlockDomAttrs({ id: 'blk_h1' }), + 'data-overlay-kind': 'modify', + }; + assert.equal(pure['data-document-block'], withOverlay['data-document-block']); + assert.equal(pure['data-testid'], withOverlay['data-testid']); + assert.notEqual(Object.prototype.hasOwnProperty.call(pure, 'data-overlay-kind'), true); + }); +}); diff --git a/web/src/lib/documentBlockDom.ts b/web/src/lib/documentBlockDom.ts new file mode 100644 index 00000000..3181c422 --- /dev/null +++ b/web/src/lib/documentBlockDom.ts @@ -0,0 +1,15 @@ +/** 真实 DocumentBlock wrapper 的稳定寻址标识,与 overlay 是否存在无关。 */ +export const DOCUMENT_BLOCK_TESTID = 'document-block'; + +export function documentBlockDomAttrs(block: { id?: string }): { + 'data-testid': typeof DOCUMENT_BLOCK_TESTID; + 'data-document-block': string; + 'data-block-id': string; +} { + const id = block.id || ''; + return { + 'data-testid': DOCUMENT_BLOCK_TESTID, + 'data-document-block': id, + 'data-block-id': id, + }; +}