docs(dumi-plugin):Improved changelog detail processing logic (#55302)

This commit is contained in:
𝑾𝒖𝒙𝒉
2025-10-14 19:53:00 +08:00
committed by GitHub
parent 017fea5bb4
commit ac515bc0a6
2 changed files with 79 additions and 17 deletions

View File

@@ -1,5 +1,6 @@
import type { UnifiedTransformer } from 'dumi';
import { unistUtilVisit } from 'dumi';
import semver from 'semver';
import set from 'lodash/set';
let hastToString: typeof import('hast-util-to-string').toString;
@@ -9,6 +10,24 @@ let hastToString: typeof import('hast-util-to-string').toString;
({ toString: hastToString } = await import('hast-util-to-string'));
})();
function isValidStrictVer(ver: string): boolean {
if (!semver.valid(ver)) {
return false;
}
const parts = ver.split('.');
if (parts.length !== 3) {
return false;
}
return parts.every((part) => /^\d+$/.test(part));
}
function isValidDate(dateStr: string): boolean {
// (YYYY-MM-DD)
return /^\d{4}-\d{2}-\d{2}$/.test(dateStr);
}
const COMPONENT_NAME = 'RefinedChangelog';
function rehypeChangelog(): UnifiedTransformer<any> {
@@ -23,32 +42,69 @@ function rehypeChangelog(): UnifiedTransformer<any> {
const nodesToWrap: { parent: any; startIdx: number }[] = [];
const WRAPPER_FLAG = 'data-changelog-wrapped'; // 包裹容器唯一标识
function checkLogSegment(node: any, strict = true) {
if (node && node.type === 'element' && node.tagName === 'h2') {
if (strict) {
const ver = hastToString(node);
return isValidStrictVer(ver) && semver.major(ver) >= 5;
}
return true;
}
return false;
}
unistUtilVisit.visit(tree, 'element', (node, idx, parent) => {
if (node.properties?.[WRAPPER_FLAG]) {
return unistUtilVisit.SKIP;
}
if (
idx !== undefined &&
parent &&
idx! + 2 < parent.children.length &&
node.tagName === 'h2' &&
parent.children[idx! + 1].tagName === 'p' &&
parent.children[idx! + 2].tagName === 'ul'
) {
if (idx !== undefined && parent && checkLogSegment(node)) {
nodesToWrap.push({ parent, startIdx: idx! });
}
});
nodesToWrap.reverse().forEach(({ parent, startIdx }) => {
const [heading, date, list] = parent.children.splice(startIdx, 3);
const totalNodesToWrap = nodesToWrap.length;
for (let i = totalNodesToWrap - 1; i >= 0; i--) {
const { parent, startIdx } = nodesToWrap[i];
let endIdx = -1;
const isEndOfWrap = i === totalNodesToWrap - 1;
for (let j = startIdx + 1; j < parent.children.length; j++) {
const nextNode = parent.children[j];
if (
(isEndOfWrap && checkLogSegment(nextNode, false)) || // 日志页通常还存在历史 major 版本
nextNode.properties?.[WRAPPER_FLAG] || // 已经被处理
checkLogSegment(nextNode) // 下一段日志
) {
endIdx = j;
break;
}
}
if (endIdx === -1) continue;
// Version
const heading = parent.children[startIdx];
// Find Date
let dateIdx = -1;
for (let j = startIdx + 1; j < endIdx; j++) {
const node = parent.children[j];
if (node.type === 'element' && isValidDate(hastToString(node))) {
dateIdx = j;
break;
}
}
if (dateIdx === -1) continue;
// Collect list nodes between dateIdx and endIdx
const version = hastToString(heading);
const date = parent.children[dateIdx];
const dateStr = hastToString(date);
const details = parent.children.slice(dateIdx + 1, endIdx);
const headingWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Version`,
// 为标签添加语义化 className (下面同理)
children: [set(heading, 'properties.className', 'changelog-version')],
};
@@ -58,10 +114,13 @@ function rehypeChangelog(): UnifiedTransformer<any> {
children: [set(date, 'properties.className', 'changelog-date')],
};
const listWrap = {
const detailWrap = {
type: 'element',
tagName: `${COMPONENT_NAME}.Details`,
children: [set(list, 'properties.className', 'changelog-details')],
properties: {
className: 'changelog-details',
},
children: details,
};
const wrapper = {
@@ -82,11 +141,11 @@ function rehypeChangelog(): UnifiedTransformer<any> {
value: JSON.stringify(dateStr),
},
],
children: [headingWrap, dateWrap, listWrap],
children: [headingWrap, dateWrap, detailWrap],
};
parent.children.splice(startIdx, 0, wrapper);
});
parent.children.splice(startIdx, endIdx - startIdx, wrapper);
}
};
}

View File

@@ -119,7 +119,10 @@ const Version: React.FC<React.PropsWithChildren> = ({ children }) => {
const DateComp: React.FC<React.PropsWithChildren> = (props) => props.children;
const DetailsComp: React.FC<React.PropsWithChildren> = (props) => props.children;
const DetailsComp: React.FC<React.PropsWithChildren<HTMLDivElement>> = (props) => {
const { children, className } = props;
return <div className={className}>{children}</div>;
};
export default Object.assign(RefinedChangelog, {
Version,