memos/web/src/utils/markdown-list-detection.ts

68 lines
1.7 KiB
TypeScript

export interface ListItemInfo {
type: "task" | "unordered" | "ordered" | null;
symbol?: string; // For task/unordered lists: "- ", "* ", "+ "
number?: number; // For ordered lists: 1, 2, 3, etc.
indent?: string; // Leading whitespace
}
// Detect the list item type of the last line before cursor
export function detectLastListItem(contentBeforeCursor: string): ListItemInfo {
const lines = contentBeforeCursor.split("\n");
const lastLine = lines[lines.length - 1];
// Extract indentation
const indentMatch = lastLine.match(/^(\s*)/);
const indent = indentMatch ? indentMatch[1] : "";
// Task list: - [ ] or - [x] or - [X]
const taskMatch = lastLine.match(/^(\s*)([-*+])\s+\[([ xX])\]\s+/);
if (taskMatch) {
return {
type: "task",
symbol: taskMatch[2], // -, *, or +
indent,
};
}
// Unordered list: - foo or * foo or + foo
const unorderedMatch = lastLine.match(/^(\s*)([-*+])\s+/);
if (unorderedMatch) {
return {
type: "unordered",
symbol: unorderedMatch[2],
indent,
};
}
// Ordered list: 1. foo or 2) foo
const orderedMatch = lastLine.match(/^(\s*)(\d+)[.)]\s+/);
if (orderedMatch) {
return {
type: "ordered",
number: parseInt(orderedMatch[2]),
indent,
};
}
return {
type: null,
indent,
};
}
// Generate the text to insert when pressing Enter on a list item
export function generateListContinuation(listInfo: ListItemInfo): string {
const indent = listInfo.indent || "";
switch (listInfo.type) {
case "task":
return `${indent}${listInfo.symbol} [ ] `;
case "unordered":
return `${indent}${listInfo.symbol} `;
case "ordered":
return `${indent}${(listInfo.number || 0) + 1}. `;
default:
return indent;
}
}