mirror of https://github.com/usememos/memos.git
169 lines
4.1 KiB
TypeScript
169 lines
4.1 KiB
TypeScript
// Utilities for manipulating markdown strings (GitHub-style approach)
|
|
// These functions modify the raw markdown text directly without parsing to AST
|
|
|
|
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) {
|
|
return markdown;
|
|
}
|
|
|
|
const [, prefix, , suffix] = match;
|
|
const newCheckmark = checked ? "x" : " ";
|
|
lines[lineNumber] = `${prefix}[${newCheckmark}]${suffix}`;
|
|
|
|
return lines.join("\n");
|
|
}
|
|
export function isInsideCodeBlock(lines :string [] , index : number):boolean{
|
|
let inside = false;
|
|
for(let i = 0 ; i <= index ; i++){
|
|
if(lines[i].startsWith("```")){
|
|
inside = !inside;
|
|
}
|
|
}
|
|
return inside;
|
|
}
|
|
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++) {
|
|
if (isInsideCodeBlock(lines, i)) continue;
|
|
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");
|
|
}
|
|
|
|
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++;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
result.push(line);
|
|
}
|
|
|
|
return result.join("\n");
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|
|
|
|
export function hasCompletedTasks(markdown: string): boolean {
|
|
const completedTaskPattern = /^(\s*[-*+]\s+)\[([xX])\](\s+.*)$/m;
|
|
return completedTaskPattern.test(markdown);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|