Update files

This commit is contained in:
Mohamed Boudra
2026-02-10 16:26:51 +07:00
parent 7e5537bcc5
commit ec7ea4b55a
15 changed files with 712 additions and 446 deletions
@@ -0,0 +1,52 @@
import { describe, expect, it } from "vitest";
import { getMarkdownListMarker } from "./markdown-list";
describe("getMarkdownListMarker", () => {
it("returns a bullet marker for unordered list items", () => {
expect(
getMarkdownListMarker(
{ index: 0 },
[{ type: "bullet_list" }]
)
).toEqual({
isOrdered: false,
marker: "•",
});
});
it("returns numbered markers for ordered list items", () => {
expect(
getMarkdownListMarker(
{ index: 1, markup: "." },
[{ type: "ordered_list" }]
)
).toEqual({
isOrdered: true,
marker: "2.",
});
});
it("respects ordered list start attribute", () => {
expect(
getMarkdownListMarker(
{ index: 2, markup: ")" },
[{ type: "ordered_list", attributes: { start: "5" } }]
)
).toEqual({
isOrdered: true,
marker: "7)",
});
});
it("prefers the nearest list ancestor in nested lists", () => {
expect(
getMarkdownListMarker(
{ index: 0, markup: "." },
[{ type: "ordered_list" }, { type: "bullet_list" }]
)
).toEqual({
isOrdered: true,
marker: "1.",
});
});
});
+87
View File
@@ -0,0 +1,87 @@
type MarkdownNode = {
type?: string;
index?: number;
markup?: string;
attributes?: {
start?: number | string;
};
children?: MarkdownNode[];
};
const LIST_BULLET = "•";
const DEFAULT_ORDERED_LIST_MARKUP = ".";
function toParentNodes(parent: unknown): MarkdownNode[] {
if (Array.isArray(parent)) {
return parent;
}
if (parent && typeof parent === "object") {
return [parent as MarkdownNode];
}
return [];
}
function getNearestListParent(parent: unknown): MarkdownNode | undefined {
return toParentNodes(parent).find(
(ancestor) =>
ancestor?.type === "ordered_list" || ancestor?.type === "bullet_list"
);
}
function getOrderedListItemIndex(node: MarkdownNode, listParent: MarkdownNode): number {
if (typeof node.index === "number" && Number.isFinite(node.index) && node.index >= 0) {
return node.index;
}
if (Array.isArray(listParent.children)) {
const fallbackIndex = listParent.children.indexOf(node);
if (fallbackIndex >= 0) {
return fallbackIndex;
}
}
return 0;
}
function parseOrderedListStart(node: MarkdownNode): number {
const rawStart = node.attributes?.start;
if (typeof rawStart === "number" && Number.isFinite(rawStart)) {
return rawStart;
}
if (typeof rawStart === "string") {
const parsed = Number.parseInt(rawStart, 10);
if (Number.isFinite(parsed)) {
return parsed;
}
}
return 1;
}
export function getMarkdownListMarker(node: MarkdownNode, parent: unknown): {
isOrdered: boolean;
marker: string;
} {
const listParent = getNearestListParent(parent);
if (!listParent || listParent.type !== "ordered_list") {
return {
isOrdered: false,
marker: LIST_BULLET,
};
}
const orderedIndex = getOrderedListItemIndex(node, listParent);
const orderedStart = parseOrderedListStart(listParent);
const orderedMarkup =
typeof node.markup === "string" && node.markup.length > 0
? node.markup
: DEFAULT_ORDERED_LIST_MARKUP;
return {
isOrdered: true,
marker: `${orderedStart + orderedIndex}${orderedMarkup}`,
};
}