mirror of https://github.com/halo-dev/halo
fix: fix anchor positioning for identical table of contents names (#5101)
#### What type of PR is this? /kind bug /area editor /milestone 2.12.x #### What this PR does / why we need it: 重写了对默认编辑器标题的 id 生成逻辑。目前将会在对标题进行任意的修改之后,对所有的标题进行 id 计算,用以解决当标题名称具有重复时,生成了相同的 id. 需要注意的是,由于需要对任意标题进行修改之后才会进行生效,因此已经存在重名标题 id 的问题时,需要修改任意的标题使其生效。 #### How to test it? 在文章内新增多个相同内容的标题,查看是否可以正常跳转。 #### Which issue(s) this PR fixes: Fixes #5068 #### Does this PR introduce a user-facing change? ```release-note 解决默认编辑器中具有重名标题时,锚点只会跳转至首个的问题。 ```pull/5114/head
parent
a1fe8c3f6b
commit
e7789929ec
|
@ -15,17 +15,13 @@ import MdiFormatHeader6 from "~icons/mdi/format-header-6";
|
|||
import { markRaw } from "vue";
|
||||
import { i18n } from "@/locales";
|
||||
import type { ExtensionOptions } from "@/types";
|
||||
import { Decoration, DecorationSet, Plugin, PluginKey } from "@/tiptap";
|
||||
import { ExtensionHeading } from "..";
|
||||
import { generateAnchor } from "@/utils";
|
||||
import { AttrStep, Plugin, PluginKey } from "@/tiptap";
|
||||
import { generateAnchorId } from "@/utils";
|
||||
|
||||
const Blockquote = TiptapHeading.extend<ExtensionOptions & HeadingOptions>({
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
const hasLevel = this.options.levels.includes(node.attrs.level);
|
||||
const level = hasLevel ? node.attrs.level : this.options.levels[0];
|
||||
const id = generateAnchor(node.textContent);
|
||||
HTMLAttributes.id = id;
|
||||
|
||||
return [
|
||||
`h${level}`,
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes),
|
||||
|
@ -282,27 +278,39 @@ const Blockquote = TiptapHeading.extend<ExtensionOptions & HeadingOptions>({
|
|||
return [TiptapParagraph];
|
||||
},
|
||||
addProseMirrorPlugins() {
|
||||
let beforeComposition: boolean | undefined = undefined;
|
||||
return [
|
||||
new Plugin({
|
||||
key: new PluginKey("generate-heading-id"),
|
||||
props: {
|
||||
decorations: (state) => {
|
||||
const { doc } = state;
|
||||
const decorations: Decoration[] = [];
|
||||
doc.descendants((node, pos) => {
|
||||
if (node.type.name === ExtensionHeading.name) {
|
||||
const id = generateAnchor(node.textContent);
|
||||
if (node.attrs.id !== id) {
|
||||
decorations.push(
|
||||
Decoration.node(pos, pos + node.nodeSize, {
|
||||
id,
|
||||
})
|
||||
);
|
||||
}
|
||||
appendTransaction: (transactions, oldState, newState) => {
|
||||
const isChangeHeading = transactions.some((transaction) => {
|
||||
const composition = this.editor.view.composing;
|
||||
if (beforeComposition !== undefined && !composition) {
|
||||
beforeComposition = undefined;
|
||||
return true;
|
||||
}
|
||||
if (transaction.docChanged) {
|
||||
beforeComposition = composition;
|
||||
const selection = transaction.selection;
|
||||
const { $from } = selection;
|
||||
const node = $from.parent;
|
||||
return node.type.name === Blockquote.name && !composition;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (isChangeHeading) {
|
||||
const tr = newState.tr;
|
||||
const headingIds: string[] = [];
|
||||
newState.doc.descendants((node, pos) => {
|
||||
if (node.type.name === Blockquote.name) {
|
||||
const id = generateAnchorId(node.textContent, headingIds);
|
||||
tr.step(new AttrStep(pos, "id", id));
|
||||
headingIds.push(id);
|
||||
}
|
||||
});
|
||||
return DecorationSet.create(doc, decorations);
|
||||
},
|
||||
return tr;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -3,3 +3,18 @@ export function generateAnchor(text: string) {
|
|||
String(text).trim().toLowerCase().replace(/\s+/g, "-")
|
||||
);
|
||||
}
|
||||
|
||||
export const generateAnchorId = (text: string, ids: string[]) => {
|
||||
const originId = generateAnchor(text);
|
||||
let id = originId;
|
||||
while (ids.includes(id)) {
|
||||
const temporarySuffix = id.replace(originId, "");
|
||||
const match = temporarySuffix.match(/-(\d+)$/);
|
||||
if (match) {
|
||||
id = `${originId}-${Number(match[1]) + 1}`;
|
||||
} else {
|
||||
id = `${originId}-1`;
|
||||
}
|
||||
}
|
||||
return id;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue