- Removed the `nodes` field from the `Memo` interface in `memo_service.ts`. - Updated the `createBaseMemo` function and the `Memo` message functions to reflect the removal of `nodes`. - Cleaned up the serialization and deserialization logic accordingly. chore: remove code-inspector-plugin from Vite configuration - Deleted the `codeInspectorPlugin` from the Vite configuration in `vite.config.mts`. - Simplified the plugins array to include only `react` and `tailwindcss`.
211 lines
5.2 KiB
TypeScript
211 lines
5.2 KiB
TypeScript
/**
|
|
* Utilities for manipulating markdown strings (GitHub-style approach)
|
|
*
|
|
* These functions modify the raw markdown text directly without parsing to AST.
|
|
* This is the same approach GitHub uses for task list updates.
|
|
*/
|
|
|
|
/**
|
|
* Toggle a task checkbox at a specific line number
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @param lineNumber - Zero-based line number
|
|
* @param checked - New checked state
|
|
* @returns Updated markdown string
|
|
*/
|
|
export function toggleTaskAtLine(markdown: string, lineNumber: number, checked: boolean): string {
|
|
const lines = markdown.split("\n");
|
|
|
|
if (lineNumber < 0 || lineNumber >= lines.length) {
|
|
return markdown;
|
|
}
|
|
|
|
const line = lines[lineNumber];
|
|
|
|
// Match task list patterns: - [ ], - [x], - [X], etc.
|
|
const taskPattern = /^(\s*[-*+]\s+)\[([ xX])\](\s+.*)$/;
|
|
const match = line.match(taskPattern);
|
|
|
|
if (!match) {
|
|
// Not a task list item
|
|
return markdown;
|
|
}
|
|
|
|
const [, prefix, , suffix] = match;
|
|
const newCheckmark = checked ? "x" : " ";
|
|
lines[lineNumber] = `${prefix}[${newCheckmark}]${suffix}`;
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
/**
|
|
* Toggle a task checkbox by its index (nth task in the document)
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @param taskIndex - Zero-based index of the task (0 = first task, 1 = second task, etc.)
|
|
* @param checked - New checked state
|
|
* @returns Updated markdown string
|
|
*/
|
|
export function toggleTaskAtIndex(markdown: string, taskIndex: number, checked: boolean): string {
|
|
const lines = markdown.split("\n");
|
|
const taskPattern = /^(\s*[-*+]\s+)\[([ xX])\](\s+.*)$/;
|
|
|
|
let currentTaskIndex = 0;
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
const match = line.match(taskPattern);
|
|
|
|
if (match) {
|
|
if (currentTaskIndex === taskIndex) {
|
|
const [, prefix, , suffix] = match;
|
|
const newCheckmark = checked ? "x" : " ";
|
|
lines[i] = `${prefix}[${newCheckmark}]${suffix}`;
|
|
break;
|
|
}
|
|
currentTaskIndex++;
|
|
}
|
|
}
|
|
|
|
return lines.join("\n");
|
|
}
|
|
|
|
/**
|
|
* Remove all completed tasks from markdown
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @returns Markdown with completed tasks removed
|
|
*/
|
|
export function removeCompletedTasks(markdown: string): string {
|
|
const lines = markdown.split("\n");
|
|
const completedTaskPattern = /^(\s*[-*+]\s+)\[([xX])\](\s+.*)$/;
|
|
const result: string[] = [];
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
// Skip completed tasks
|
|
if (completedTaskPattern.test(line)) {
|
|
// Also skip the following line if it's empty (preserve spacing)
|
|
if (i + 1 < lines.length && lines[i + 1].trim() === "") {
|
|
i++; // Skip next line
|
|
}
|
|
continue;
|
|
}
|
|
|
|
result.push(line);
|
|
}
|
|
|
|
return result.join("\n");
|
|
}
|
|
|
|
/**
|
|
* Count tasks in markdown
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @returns Object with task counts
|
|
*/
|
|
export function countTasks(markdown: string): {
|
|
total: number;
|
|
completed: number;
|
|
incomplete: number;
|
|
} {
|
|
const lines = markdown.split("\n");
|
|
const taskPattern = /^(\s*[-*+]\s+)\[([ xX])\](\s+.*)$/;
|
|
|
|
let total = 0;
|
|
let completed = 0;
|
|
|
|
for (const line of lines) {
|
|
const match = line.match(taskPattern);
|
|
if (match) {
|
|
total++;
|
|
const checkmark = match[2];
|
|
if (checkmark.toLowerCase() === "x") {
|
|
completed++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
total,
|
|
completed,
|
|
incomplete: total - completed,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if markdown has any completed tasks
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @returns True if there are completed tasks
|
|
*/
|
|
export function hasCompletedTasks(markdown: string): boolean {
|
|
const completedTaskPattern = /^(\s*[-*+]\s+)\[([xX])\](\s+.*)$/m;
|
|
return completedTaskPattern.test(markdown);
|
|
}
|
|
|
|
/**
|
|
* Get the line number of the nth task
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @param taskIndex - Zero-based task index
|
|
* @returns Line number, or -1 if not found
|
|
*/
|
|
export function getTaskLineNumber(markdown: string, taskIndex: number): number {
|
|
const lines = markdown.split("\n");
|
|
const taskPattern = /^(\s*[-*+]\s+)\[([ xX])\](\s+.*)$/;
|
|
|
|
let currentTaskIndex = 0;
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
if (taskPattern.test(lines[i])) {
|
|
if (currentTaskIndex === taskIndex) {
|
|
return i;
|
|
}
|
|
currentTaskIndex++;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Extract all task items with their metadata
|
|
*
|
|
* @param markdown - The full markdown content
|
|
* @returns Array of task metadata
|
|
*/
|
|
export interface TaskItem {
|
|
lineNumber: number;
|
|
taskIndex: number;
|
|
checked: boolean;
|
|
content: string;
|
|
indentation: number;
|
|
}
|
|
|
|
export function extractTasks(markdown: string): TaskItem[] {
|
|
const lines = markdown.split("\n");
|
|
const taskPattern = /^(\s*)([-*+]\s+)\[([ xX])\](\s+.*)$/;
|
|
const tasks: TaskItem[] = [];
|
|
|
|
let taskIndex = 0;
|
|
|
|
for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
|
|
const line = lines[lineNumber];
|
|
const match = line.match(taskPattern);
|
|
|
|
if (match) {
|
|
const [, indentStr, , checkmark, content] = match;
|
|
tasks.push({
|
|
lineNumber,
|
|
taskIndex: taskIndex++,
|
|
checked: checkmark.toLowerCase() === "x",
|
|
content: content.trim(),
|
|
indentation: indentStr.length,
|
|
});
|
|
}
|
|
}
|
|
|
|
return tasks;
|
|
}
|