尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

The lunatic is on the grass

The lunatic is on the grass The lunatic is on the grass【免费下载链接】milkdown Plugin driven WYSIWYG markdown editor framework.项目地址: https://gitcode.com/GitHub_Trending/mi/milkdownThe lunatic is on the grassThe lunatic is on the grassThe lunatic is on the grass该文件覆盖了 Markdown 最常用的四级标题# 到 ####对应 HTML 的 h1 到 h4并且刻意让每级标题使用**相同的文本内容** The lunatic is on the grass。这种设计并非随意为之它让测试能够在“文本内容完全一致”的前提下专注于验证标题**级别level属性**、**DOM 标签h1/h2/h3/h4**以及**标题 id 去重机制**的行为差异从而精确暴露标题节点独有的处理逻辑。 需要说明的是e2e/tests/data 下的夹具大多由测试用例通过 loadFixture 统一加载见 [e2e/tests/misc/index.ts](https://link.gitcode.com/i/a74c742b469094e0e57409df487c53b9)而 heading.md 同时被输入类、快捷键类测试直接引用是理解 Milkdown 标题节点行为的核心数据源。 ## Markdown 解析阶段标题如何进入编辑器 Milkdown 通过 milkdown/transformer 将 Markdown 文本解析为 ProseMirror 文档。标题的解析入口位于 preset-commonmark 的 heading 节点定义 [packages/plugins/preset-commonmark/src/node/heading.ts](https://link.gitcode.com/i/a12110c4b3c2af7092be5cf5cddc6e64) ts parseMarkdown: { match: ({ type }) type heading, runner: (state, node, type) { const depth node.depth as number state.openNode(type, { level: depth }) state.next(node.children) state.closeNode() }, },解析逻辑非常直白当 Markdown 抽象语法树中出现heading类型节点时直接取其depth即#的个数作为 ProseMirror 节点属性level然后递归处理子节点标题内的行内内容如加粗、斜体、链接等最后关闭节点。对应的序列化方向同样在同一个文件中toMarkdown: { match: (node) node.type.name heading, runner: (state, node) { state.openNode(heading, undefined, { depth: node.attrs.level }) serializeText(state, node) state.closeNode() }, },toMarkdown将编辑器内的 heading 节点重新写回 Markdown以node.attrs.level作为depth再序列化标题的行内文本。这样# The lunatic is on the grass在“Markdown → 编辑器 DOM → Markdown”的往返过程中可以保持语义一致。Heading 节点 Schemalevel 与 id 双属性建模标题节点的 ProseMirror Schema 定义在同一文件 heading.ts其核心结构如下export const headingSchema $nodeSchema(heading, (ctx) { const getId ctx.get(headingIdGenerator.key) return { content: inline*, group: block, defining: true, attrs: { id: { default: , validate: string }, level: { default: 1, validate: number }, }, parseDOM: headingIndex.map((x) ({ tag: h${x}, getAttrs: (node) ({ level: x, id: node.id }), })), toDOM: (node) [ h${node.attrs.level}, { ...ctx.get(headingAttr.key)(node), id: node.attrs.id || getId(node) }, 0, ], } })值得注意的细节content: inline*标题内只允许行内内容文本、强调、代码、链接等不允许嵌套块级节点defining: true声明标题是“定义性”块在块删除/提升等操作中会保留其结构边界level标题级别16对应h1h6id标题锚点 idtoDOM中当id为空时会调用getId(node)自动生成详见下文。parseDOM由headingIndex动态生成Array(6).fill(0).map((_, i) i 1)即同时注册h1h6六个 HTML 标签的解析规则粘贴 HTML 内容时也会被识别为标题节点。标题与段落等节点一起在 composed/schema.ts 中被注册进preset-commonmark的 schema 组合中。标题 id 的生成与去重机制标题 id 是 Milkdown 标题节点的一大特色默认根据文本内容生成 slug 形式的锚点 id。生成器通过$ctx暴露可在初始化编辑器时自定义function defaultHeadingIdGenerator(node: Node) { return node.textContent.toLowerCase().trim().replace(/\s/g, -) } export const headingIdGenerator $ctx( defaultHeadingIdGenerator, headingIdGenerator )默认规则为取标题文本 → 转小写 → 去首尾空白 → 将连续空白替换为-。例如# The lunatic is on the grass会生成the-lunatic-is-on-the-grass。重复 id 的处理由syncHeadingIdPlugin完成它被注册在 composed/plugins.ts 中。其行为在 e2e/tests/input/heading.spec.ts 中有精确断言# Heading1 → id: heading1 # Heading1 → id: heading1-#2 ## Heading1 → id: heading1-#3即当出现同文本的重复标题时后续标题会在基础 id 后追加-#2、-#3等后缀以保证唯一性。这意味着即使标题文本完全相同每个标题仍拥有可稳定引用的锚点 id——这正是 heading.md 夹具让四行标题文本全部相同的原因之一它便于在“文本相同”的前提下单独验证 id 去重逻辑。输入规则输入#即可生成标题wrapInHeadingInputRule提供了 Markdown 风格的快捷输入在行首输入若干#并跟一个空格当前块即被转换为对应级别的标题heading.tstextblockTypeInputRule( /^(?hashes#)\s$/, headingSchema.type(ctx), (match) { const x match.groups?.hashes?.length || 0 const view ctx.get(editorViewCtx) const { $from } view.state.selection const node $from.node() if (node.type.name heading) { let level Number(node.attrs.level) Number(x) if (level 6) level 6 return { level } } return { level: x } } )【免费下载链接】milkdown Plugin driven WYSIWYG markdown editor framework.项目地址: https://gitcode.com/GitHub_Trending/mi/milkdown创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表