From 6a8a9525c90f3d4160d412f3929922b1e2df354b Mon Sep 17 00:00:00 2001 From: mohamed yahia Date: Mon, 27 Nov 2023 11:13:17 +0200 Subject: [PATCH] [#60,tasks][m]: implement extract markdown tasks i.e. `- [ ]`. * extract tasks (find all list items and then only those with checked property or not) * fix linting * Add changeset * Run format --- .changeset/five-dots-speak.md | 6 +++ __mocks__/content/index.mdx | 6 +++ src/lib/databaseUtils.ts | 8 +--- src/lib/parseFile.ts | 37 +++++++++++++++++ src/tests/extractTasks.spec.ts | 72 ++++++++++++++++++++++++++++++++++ src/tests/parseFile.spec.ts | 10 +++++ src/tests/process.spec.ts | 14 +++++++ 7 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 .changeset/five-dots-speak.md create mode 100644 src/tests/extractTasks.spec.ts diff --git a/.changeset/five-dots-speak.md b/.changeset/five-dots-speak.md new file mode 100644 index 0000000..f5b1dbf --- /dev/null +++ b/.changeset/five-dots-speak.md @@ -0,0 +1,6 @@ +--- +"mddb": minor +--- + +[ #60 , extract tasks ] +Add tasks extraction from files. e.g `- [ ] task` diff --git a/__mocks__/content/index.mdx b/__mocks__/content/index.mdx index 56d9c80..fa8bb6b 100644 --- a/__mocks__/content/index.mdx +++ b/__mocks__/content/index.mdx @@ -6,3 +6,9 @@ tags: tag1, tag2, tag3 # Welcome [link](blog0.mdx) + +- [] uncompleted task 1 +- [ ] uncompleted task 2 + +- [x] completed task 1 +- [X] completed task 2 \ No newline at end of file diff --git a/src/lib/databaseUtils.ts b/src/lib/databaseUtils.ts index 03316f4..5292ff6 100644 --- a/src/lib/databaseUtils.ts +++ b/src/lib/databaseUtils.ts @@ -1,11 +1,5 @@ import { Knex } from "knex"; -import { - MddbFile, - MddbTag, - MddbLink, - MddbFileTag, - File, -} from "./schema.js"; +import { MddbFile, MddbTag, MddbLink, MddbFileTag, File } from "./schema.js"; import path from "path"; import { WikiLink } from "./parseFile.js"; diff --git a/src/lib/parseFile.ts b/src/lib/parseFile.ts index 3a26eee..1883240 100644 --- a/src/lib/parseFile.ts +++ b/src/lib/parseFile.ts @@ -24,6 +24,9 @@ export function parseFile(source: string, options?: ParsingOptions) { // Links const links = extractWikiLinks(ast, options); + const tasks = extractTasks(ast); + metadata.tasks = tasks; + return { metadata, links, @@ -155,6 +158,40 @@ export const extractWikiLinks = (ast: Root, options?: ParsingOptions) => { return wikiLinks; }; +export interface Task { + description: string; + checked: boolean; +} + +export const extractTasks = (ast: Root) => { + const nodes = selectAll("*", ast); + const tasks: Task[] = []; + nodes.map((node: any) => { + if (node.type === "listItem") { + const description = recursivelyExtractText(node).trim(); + const checked = node.checked; + if (checked !== null && checked !== undefined) { + tasks.push({ + description, + checked, + }); + } + } + }); + + return tasks; +}; + +function recursivelyExtractText(node: any) { + if (node.value) { + return node.value; + } else if (node.children) { + return node.children.map(recursivelyExtractText).join(" "); + } else { + return ""; + } +} + // links = extractWikiLinks({ // source, // // TODO pass slug instead of file path as hrefs/srcs are sluggified too diff --git a/src/tests/extractTasks.spec.ts b/src/tests/extractTasks.spec.ts new file mode 100644 index 0000000..cc3de8e --- /dev/null +++ b/src/tests/extractTasks.spec.ts @@ -0,0 +1,72 @@ +import { extractTasks, processAST } from "../lib/parseFile"; + +const getTasksFromSource = (source: string) => { + const ast = processAST(source, {}); + const tasks = extractTasks(ast); + return tasks; +}; + +describe("extractTasks", () => { + test("should extract uncompleted tasks from body", () => { + const tasks = getTasksFromSource( + "- [] uncompleted task 1\n- [ ] uncompleted task 2" + ); + const expectedTasks = [ + { description: "uncompleted task 2", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should extract completed tasks from body", () => { + const tasks = getTasksFromSource( + "- [x] completed task 1\n- [X] completed task 2" + ); + const expectedTasks = [ + { description: "completed task 1", checked: true }, + { description: "completed task 2", checked: true }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle mixed completed and uncompleted tasks", () => { + const tasks = getTasksFromSource( + "- [x] completed task\n- [ ] uncompleted task" + ); + const expectedTasks = [ + { description: "completed task", checked: true }, + { description: "uncompleted task", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with leading and trailing spaces", () => { + const tasks = getTasksFromSource( + "- [x] completed task \n- [ ] uncompleted task " + ); + const expectedTasks = [ + { description: "completed task", checked: true }, + { description: "uncompleted task", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with different checkbox formats", () => { + const tasks = getTasksFromSource( + "- [x] task 1\n- [X] task 2\n- [ ] task 3" + ); + const expectedTasks = [ + { description: "task 1", checked: true }, + { description: "task 2", checked: true }, + { description: "task 3", checked: false }, + ]; + expect(tasks).toEqual(expectedTasks); + }); + + test("should handle tasks with special characters", () => { + const tasks = getTasksFromSource("- [x] task with $pecial character$"); + const expectedTasks = [ + { description: "task with $pecial character$", checked: true }, + ]; + expect(tasks).toEqual(expectedTasks); + }); +}); diff --git a/src/tests/parseFile.spec.ts b/src/tests/parseFile.spec.ts index 482aaa0..fd77369 100644 --- a/src/tests/parseFile.spec.ts +++ b/src/tests/parseFile.spec.ts @@ -10,6 +10,8 @@ tags: a, b, c [[blog/Some Other Link]] [[blog/Some Other Link|Page Alias]] ![[Some Image.png]] +- [ ] uncompleted task +- [x] completed task `; describe("parseFile", () => { @@ -18,6 +20,10 @@ describe("parseFile", () => { title: "Hello World", authors: ["John Doe", "Jane Doe"], tags: ["a", "b", "c"], + tasks: [ + { description: "uncompleted task", checked: false }, + { description: "completed task", checked: true }, + ], }; const expectedLinks = [ { @@ -63,6 +69,10 @@ describe("parseFile", () => { title: "Hello World", authors: ["John Doe", "Jane Doe"], tags: ["a", "b", "c"], + tasks: [ + { description: "uncompleted task", checked: false }, + { description: "completed task", checked: true }, + ], }; const expectedLinks = [ { diff --git a/src/tests/process.spec.ts b/src/tests/process.spec.ts index 936195f..43fd910 100644 --- a/src/tests/process.spec.ts +++ b/src/tests/process.spec.ts @@ -21,6 +21,20 @@ describe("Can parse a file and get file info", () => { expect(fileInfo.metadata).toEqual({ title: "Homepage", tags: ["tag1", "tag2", "tag3"], + tasks: [ + { + checked: false, + description: "uncompleted task 2", + }, + { + checked: true, + description: "completed task 1", + }, + { + checked: true, + description: "completed task 2", + }, + ], }); expect(fileInfo.links).toEqual([ {