From 1507d045b87bbf4ef39dbd10eee772dd14b048fe Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 18:03:24 -0800 Subject: [PATCH 1/6] Fix sub headings --- src/get-todos.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/get-todos.js b/src/get-todos.js index e59fa19..807915a 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -1,6 +1,6 @@ class TodoParser { // Support all unordered list bullet symbols as per spec (https://daringfireball.net/projects/markdown/syntax#list) - bulletSymbols = ["-", "*", "+"]; + bulletSymbols = ["-", "*", "+", "#"]; // List of strings that include the Markdown content #lines; From f9c8c67d68eec5ac649b3e4fca7f677c8180849d Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 18:49:55 -0800 Subject: [PATCH 2/6] Initial progress --- src/get-todos.js | 31 ++++++++++++++++++++++++------- src/get-todos.test.js | 42 ++++++++++++++++++++++++++++++++++++++++-- src/index.js | 2 ++ 3 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/get-todos.js b/src/get-todos.js index 807915a..0d59c7a 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -1,6 +1,6 @@ class TodoParser { // Support all unordered list bullet symbols as per spec (https://daringfireball.net/projects/markdown/syntax#list) - bulletSymbols = ["-", "*", "+", "#"]; + bulletSymbols = ["-", "*", "+"]; // List of strings that include the Markdown content #lines; @@ -8,17 +8,27 @@ class TodoParser { // Boolean that encodes whether nested items should be rolled over #withChildren; - constructor(lines, withChildren) { + // Boolean that encodes whether sub-headings of nested items should be rolled over. Only relevant when #withChildren is true. + #withSubheadings; + + constructor(lines, withChildren, withSubheadings) { this.#lines = lines; this.#withChildren = withChildren; + this.#withSubheadings = withChildren && withSubheadings; } // Returns true if string s is a todo-item #isTodo(s) { - const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] \\[[^xX-]\\].*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; + const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] \\[[^xX-]].*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; return r.test(s); } + // Returns true if the string is a heading + #isHeading(s) { + const h = new RegExp(`\\s*#+.*`, "g"); + return h.test(s); + } + // Returns true if line after line-number `l` is a nested item #hasChildren(l) { if (l + 1 >= this.#lines.length) { @@ -56,15 +66,22 @@ class TodoParser { return this.#lines[l].search(/\S/); } + isRelevant(line) { + if (this.#isTodo(line)) { + return true; + } + return this.#withSubheadings && this.#isHeading(line); + } + // Returns a list of strings that represents all the todos along with there potential children getTodos() { let todos = []; for (let l = 0; l < this.#lines.length; l++) { const line = this.#lines[l]; - if (this.#isTodo(line)) { + if (this.#isTodo(line) || (l !== 0 && this.#withSubheadings && this.#isHeading(line))) { todos.push(line); if (this.#withChildren && this.#hasChildren(l)) { - const cs = this.#getChildren(l); + const cs = this.#getChildren(l).filter(c => this.isRelevant(c)); todos = [...todos, ...cs]; l += cs.length; } @@ -75,7 +92,7 @@ class TodoParser { } // Utility-function that acts as a thin wrapper around `TodoParser` -export const getTodos = ({ lines, withChildren = false }) => { - const todoParser = new TodoParser(lines, withChildren); +export const getTodos = ({ lines, withChildren = false , withSubHeadings = false}) => { + const todoParser = new TodoParser(lines, withChildren, withSubHeadings); return todoParser.getTodos(); }; diff --git a/src/get-todos.test.js b/src/get-todos.test.js index cb30685..0d9bc2e 100644 --- a/src/get-todos.test.js +++ b/src/get-todos.test.js @@ -33,7 +33,7 @@ test("single done todo element should not return itself", () => { const result = getTodos({ lines }); // THEN - const todos = [""]; + const todos = []; expect(result).toStrictEqual(todos); }); @@ -45,7 +45,7 @@ test("single canceled todo element should not return itself", () => { const result = getTodos({ lines }); // THEN - const todos = [""]; + const todos = []; expect(result).toStrictEqual(todos); }); @@ -299,3 +299,41 @@ test("get todos doesn't add intermediate other elements", () => { ]; expect(todos).toStrictEqual(result); }); + +test("get todos adds sub headings when enabled", () => { + // GIVEN + const lines = [ + "# Some title", + "", + "- [ ] TODO", + " - [ ] Next", + " - some stuff", + "", + "## Some sub title", + "", + "Some text", + "...that continues here", + "", + "- Here is a bullet item", + "- Here is another bullet item", + "1. Here is a numbered list item", + "- [ ] Another one", + " - [ ] More children", + " - another child", + ]; + + // WHEN + const todos = getTodos({ lines, withChildren: true , withSubHeadings: true}); + + // THEN + const result = [ + "- [ ] TODO", + " - [ ] Next", + " - some stuff", + "## Some sub title", + "- [ ] Another one", + " - [ ] More children", + " - another child", + ]; + expect(todos).toStrictEqual(result); +}); diff --git a/src/index.js b/src/index.js index 49f3766..e06fb17 100644 --- a/src/index.js +++ b/src/index.js @@ -46,6 +46,7 @@ export default class RolloverTodosPlugin extends Plugin { removeEmptyTodos: false, rolloverChildren: false, rolloverOnFileCreate: true, + rolloverSubheadings: false, }; this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } @@ -126,6 +127,7 @@ export default class RolloverTodosPlugin extends Plugin { return getTodos({ lines: dnLines, withChildren: this.settings.rolloverChildren, + withSubHeadings: this.settings.rolloverSubheadings, }); } From afd2fcbb273754eed7abbf9636707ab3c10192a4 Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 19:45:19 -0800 Subject: [PATCH 3/6] Add a lot of configurable options to todo processsing --- src/get-todos.js | 49 ++++++++++++-- src/get-todos.test.js | 145 ++++++++++++++++++++++++++++++++++++++++-- src/index.js | 6 ++ 3 files changed, 190 insertions(+), 10 deletions(-) diff --git a/src/get-todos.js b/src/get-todos.js index 0d59c7a..61dfdd7 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -11,15 +11,39 @@ class TodoParser { // Boolean that encodes whether sub-headings of nested items should be rolled over. Only relevant when #withChildren is true. #withSubheadings; - constructor(lines, withChildren, withSubheadings) { + // Boolean that encodes whether bullets should be rolled over. + #withBullets; + + // Boolean that encodes whether children should have existing filters applied + #filterChildren; + + // Boolean that encodes whether completed children should be rolled over. Only relevant when #withChildren is true and #filterChildren is true. + #withCompletedChildren; + + constructor(lines, withChildren, withSubheadings, withBullets, filterChildren, withCompletedChildren) { this.#lines = lines; this.#withChildren = withChildren; this.#withSubheadings = withChildren && withSubheadings; + this.#withBullets = withBullets; + this.#filterChildren = filterChildren; + this.#withCompletedChildren = withCompletedChildren; } // Returns true if string s is a todo-item #isTodo(s) { - const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] \\[[^xX-]].*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; + const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] \\[[^xX-]\\].*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; + return r.test(s); + } + + // Returns true if the string is a completed todo-item + #isCompletedTodo(s) { + const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] \\[[xX-]\\].*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; + return r.test(s); + } + + // Returns true if the string is a bulleted list item (optionally included as a todo-item) + #isBullet(s) { + const r = new RegExp(`\\s*[${this.bulletSymbols.join("")}] (?!\\[[^xX]]).*`, "g"); // /\s*[-*+] \[[^xX-]\].*/g; return r.test(s); } @@ -70,6 +94,11 @@ class TodoParser { if (this.#isTodo(line)) { return true; } + + if (this.#withBullets) { + return this.#isBullet(line); + } + return this.#withSubheadings && this.#isHeading(line); } @@ -78,10 +107,18 @@ class TodoParser { let todos = []; for (let l = 0; l < this.#lines.length; l++) { const line = this.#lines[l]; - if (this.#isTodo(line) || (l !== 0 && this.#withSubheadings && this.#isHeading(line))) { + if (this.#isTodo(line) || (l !== 0 && this.#withSubheadings && this.#isHeading(line)) || (this.#withBullets && this.#isBullet(line))) { todos.push(line); if (this.#withChildren && this.#hasChildren(l)) { - const cs = this.#getChildren(l).filter(c => this.isRelevant(c)); + const cs = this.#getChildren(l).filter(c => { + if (!this.#withCompletedChildren && this.#isCompletedTodo(c)) { + return false; + } + if (this.#isCompletedTodo(c)) { + return true; + } + return (!this.#filterChildren || this.isRelevant(c)); + }); todos = [...todos, ...cs]; l += cs.length; } @@ -92,7 +129,7 @@ class TodoParser { } // Utility-function that acts as a thin wrapper around `TodoParser` -export const getTodos = ({ lines, withChildren = false , withSubHeadings = false}) => { - const todoParser = new TodoParser(lines, withChildren, withSubHeadings); +export const getTodos = ({ lines, withChildren = false , withSubHeadings = false, withBullets = false, filterChildren = false, withCompletedChildren = true}) => { + const todoParser = new TodoParser(lines, withChildren, withSubHeadings, withBullets, filterChildren, withCompletedChildren); return todoParser.getTodos(); }; diff --git a/src/get-todos.test.js b/src/get-todos.test.js index 0d9bc2e..2bee9ba 100644 --- a/src/get-todos.test.js +++ b/src/get-todos.test.js @@ -128,6 +128,30 @@ test("get todos (with alternate symbols and partially checked todos) with childr expect(todos).toStrictEqual(result); }); +test("get todos (with alternate symbols and partially checked todos) with children without completed children", function () { + // GIVEN + const lines = [ + "+ [x] Completed TODO", + " + [ ] Next", + " * some stuff", + "* [ ] Another one", + " - [x] Completed child", + " + another child", + "- this isn't copied", + ]; + + // WHEN + const todos = getTodos({ lines: lines, withChildren: true, withCompletedChildren: false}); + + // THEN + const result = [ + " + [ ] Next", + "* [ ] Another one", + " + another child", + ]; + expect(todos).toStrictEqual(result); +}); + test("get todos (with default dash prefix and finished todos) with children", function () { // GIVEN const lines = [ @@ -195,6 +219,38 @@ test("get todos with correct alternate checkbox children", function () { // WHEN const todos = getTodos({ lines: lines, withChildren: true }); + // THEN + const result = [ + "- [ ] TODO", + " - [ ] Next", + " - [x] Completed task", + " - some stuff", + "- [ ] Another one", + " - [ ] Another child", + " - [/] More children", + " - another child", + ]; + expect(todos).toStrictEqual(result); +}); + +test("get todos with correct alternate checkbox children without finished subtasks", function () { + // GIVEN + const lines = [ + "- [ ] TODO", + " - [ ] Next", + " - [x] Completed task", + " - some stuff", + "- [ ] Another one", + " - [ ] Another child", + " - [/] More children", + " - [x] Completed children", + " - another child", + "- this isn't copied", + ]; + + // WHEN + const todos = getTodos({ lines: lines, withChildren: true, withCompletedChildren: false}); + // THEN const result = [ "- [ ] TODO", @@ -207,6 +263,7 @@ test("get todos with correct alternate checkbox children", function () { ]; expect(todos).toStrictEqual(result); }); + test("get todos with children doesn't fail if child at end of list", () => { // GIVEN const lines = [ @@ -300,7 +357,7 @@ test("get todos doesn't add intermediate other elements", () => { expect(todos).toStrictEqual(result); }); -test("get todos adds sub headings when enabled", () => { +test("get todos doesn't adds subheadings", () => { // GIVEN const lines = [ "# Some title", @@ -309,7 +366,7 @@ test("get todos adds sub headings when enabled", () => { " - [ ] Next", " - some stuff", "", - "## Some sub title", + "## Some title", "", "Some text", "...that continues here", @@ -323,14 +380,94 @@ test("get todos adds sub headings when enabled", () => { ]; // WHEN - const todos = getTodos({ lines, withChildren: true , withSubHeadings: true}); + const todos = getTodos({ lines, withChildren: true, withBullets: false, filterChildren: true, withSubHeadings: true}); + + // THEN + const result = [ + "- [ ] TODO", + " - [ ] Next", + "## Some title", + "- [ ] Another one", + " - [ ] More children", + ]; + expect(todos).toStrictEqual(result); +}); + +test("get todos doesn't adds subheadings and sub bullets", () => { + // GIVEN + const lines = [ + "# Some title", + "", + "- [ ] TODO", + " - [ ] Next", + " - some stuff", + "", + "## Some title", + "", + "Some text", + "...that continues here", + "", + "- Here is a bullet item that is a valid child", + "- Here is another bullet item", + "1. Here is a numbered list item", + "- [ ] Another one", + " - [ ] More children", + " - another child", + ]; + + // WHEN + const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: true, withSubHeadings: true}); + + // THEN + const result = [ + "- [ ] TODO", + " - [ ] Next", + " - some stuff", + "## Some title", + "- Here is a bullet item that is a valid child", + "- Here is another bullet item", + "- [ ] Another one", + " - [ ] More children", + " - another child", + ]; + expect(todos).toStrictEqual(result); +}); + +test("get todos includes bullets", () => { + // GIVEN + const lines = [ + "# Some title", + "", + "- bullet", + "- [ ] TODO", + " - [ ] Next", + " - some stuff", + "", + "## Some title", + "", + "Some text", + "...that continues here", + "", + "- Here is a bullet item that is a valid child", + "- Here is another bullet item", + "1. Here is a numbered list item", + "- [ ] Another one", + " - [ ] More children", + " - another child", + ]; + + // WHEN + const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: false, withSubHeadings: true}); // THEN const result = [ + "- bullet", "- [ ] TODO", " - [ ] Next", " - some stuff", - "## Some sub title", + "## Some title", + "- Here is a bullet item that is a valid child", + "- Here is another bullet item", "- [ ] Another one", " - [ ] More children", " - another child", diff --git a/src/index.js b/src/index.js index e06fb17..7c4aee7 100644 --- a/src/index.js +++ b/src/index.js @@ -47,6 +47,9 @@ export default class RolloverTodosPlugin extends Plugin { rolloverChildren: false, rolloverOnFileCreate: true, rolloverSubheadings: false, + rolloverBullets: false, + filterChildren: false, + rolloverCompletedChildren: true, }; this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); } @@ -128,6 +131,9 @@ export default class RolloverTodosPlugin extends Plugin { lines: dnLines, withChildren: this.settings.rolloverChildren, withSubHeadings: this.settings.rolloverSubheadings, + withBullets: this.settings.rolloverBullets, + filterChildren: this.settings.filterChildren, + withCompletedChildren: this.settings.rolloverCompletedChildren, }); } From 627e1fee389011b6b437389a5ebe74ca809fbc67 Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 20:34:39 -0800 Subject: [PATCH 4/6] Remove uneccessary check for is completed todo --- src/get-todos.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/get-todos.js b/src/get-todos.js index 61dfdd7..e17f27c 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -114,9 +114,6 @@ class TodoParser { if (!this.#withCompletedChildren && this.#isCompletedTodo(c)) { return false; } - if (this.#isCompletedTodo(c)) { - return true; - } return (!this.#filterChildren || this.isRelevant(c)); }); todos = [...todos, ...cs]; From 58707d91f99e57c4cd5980a7daa65e296c299a7b Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 20:41:33 -0800 Subject: [PATCH 5/6] Fix tests --- src/get-todos.js | 20 ++++++++++++-------- src/get-todos.test.js | 13 ++++++++----- src/index.js | 4 ++-- 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/get-todos.js b/src/get-todos.js index e17f27c..e86e765 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -8,8 +8,8 @@ class TodoParser { // Boolean that encodes whether nested items should be rolled over #withChildren; - // Boolean that encodes whether sub-headings of nested items should be rolled over. Only relevant when #withChildren is true. - #withSubheadings; + // Boolean that encodes whether headings should be rolled over. + #withHeadings; // Boolean that encodes whether bullets should be rolled over. #withBullets; @@ -20,10 +20,10 @@ class TodoParser { // Boolean that encodes whether completed children should be rolled over. Only relevant when #withChildren is true and #filterChildren is true. #withCompletedChildren; - constructor(lines, withChildren, withSubheadings, withBullets, filterChildren, withCompletedChildren) { + constructor(lines, withChildren, withHeadings, withBullets, filterChildren, withCompletedChildren) { this.#lines = lines; this.#withChildren = withChildren; - this.#withSubheadings = withChildren && withSubheadings; + this.#withHeadings = withHeadings; this.#withBullets = withBullets; this.#filterChildren = filterChildren; this.#withCompletedChildren = withCompletedChildren; @@ -95,11 +95,15 @@ class TodoParser { return true; } + if (this.#withHeadings && this.#isHeading(line)) { + return true; + } + if (this.#withBullets) { return this.#isBullet(line); } - return this.#withSubheadings && this.#isHeading(line); + return false; } // Returns a list of strings that represents all the todos along with there potential children @@ -107,7 +111,7 @@ class TodoParser { let todos = []; for (let l = 0; l < this.#lines.length; l++) { const line = this.#lines[l]; - if (this.#isTodo(line) || (l !== 0 && this.#withSubheadings && this.#isHeading(line)) || (this.#withBullets && this.#isBullet(line))) { + if (this.isRelevant(line)) { todos.push(line); if (this.#withChildren && this.#hasChildren(l)) { const cs = this.#getChildren(l).filter(c => { @@ -126,7 +130,7 @@ class TodoParser { } // Utility-function that acts as a thin wrapper around `TodoParser` -export const getTodos = ({ lines, withChildren = false , withSubHeadings = false, withBullets = false, filterChildren = false, withCompletedChildren = true}) => { - const todoParser = new TodoParser(lines, withChildren, withSubHeadings, withBullets, filterChildren, withCompletedChildren); +export const getTodos = ({ lines, withChildren = false , withHeadings = false, withBullets = false, filterChildren = false, withCompletedChildren = true}) => { + const todoParser = new TodoParser(lines, withChildren, withHeadings, withBullets, filterChildren, withCompletedChildren); return todoParser.getTodos(); }; diff --git a/src/get-todos.test.js b/src/get-todos.test.js index 2bee9ba..c7303d4 100644 --- a/src/get-todos.test.js +++ b/src/get-todos.test.js @@ -357,7 +357,7 @@ test("get todos doesn't add intermediate other elements", () => { expect(todos).toStrictEqual(result); }); -test("get todos doesn't adds subheadings", () => { +test("get todos doesn't add headings", () => { // GIVEN const lines = [ "# Some title", @@ -380,10 +380,11 @@ test("get todos doesn't adds subheadings", () => { ]; // WHEN - const todos = getTodos({ lines, withChildren: true, withBullets: false, filterChildren: true, withSubHeadings: true}); + const todos = getTodos({ lines, withChildren: true, withBullets: false, filterChildren: true, withHeadings: true}); // THEN const result = [ + "# Some title", "- [ ] TODO", " - [ ] Next", "## Some title", @@ -393,7 +394,7 @@ test("get todos doesn't adds subheadings", () => { expect(todos).toStrictEqual(result); }); -test("get todos doesn't adds subheadings and sub bullets", () => { +test("get todos doesn't add headings and sub bullets", () => { // GIVEN const lines = [ "# Some title", @@ -416,10 +417,11 @@ test("get todos doesn't adds subheadings and sub bullets", () => { ]; // WHEN - const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: true, withSubHeadings: true}); + const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: true, withHeadings: true}); // THEN const result = [ + "# Some title", "- [ ] TODO", " - [ ] Next", " - some stuff", @@ -457,10 +459,11 @@ test("get todos includes bullets", () => { ]; // WHEN - const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: false, withSubHeadings: true}); + const todos = getTodos({ lines, withChildren: true, withBullets: true, filterChildren: false, withHeadings: true}); // THEN const result = [ + "# Some title", "- bullet", "- [ ] TODO", " - [ ] Next", diff --git a/src/index.js b/src/index.js index 7c4aee7..b64c66f 100644 --- a/src/index.js +++ b/src/index.js @@ -46,7 +46,7 @@ export default class RolloverTodosPlugin extends Plugin { removeEmptyTodos: false, rolloverChildren: false, rolloverOnFileCreate: true, - rolloverSubheadings: false, + rolloverHeadings: false, rolloverBullets: false, filterChildren: false, rolloverCompletedChildren: true, @@ -130,7 +130,7 @@ export default class RolloverTodosPlugin extends Plugin { return getTodos({ lines: dnLines, withChildren: this.settings.rolloverChildren, - withSubHeadings: this.settings.rolloverSubheadings, + withHeadings: this.settings.rolloverHeadings, withBullets: this.settings.rolloverBullets, filterChildren: this.settings.filterChildren, withCompletedChildren: this.settings.rolloverCompletedChildren, From a607a65932455050bbdd5efed91fc11c112c3d57 Mon Sep 17 00:00:00 2001 From: wallstop Date: Tue, 23 Jan 2024 20:51:04 -0800 Subject: [PATCH 6/6] Fix bug in children length calculation --- src/get-todos.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/get-todos.js b/src/get-todos.js index e86e765..618a722 100644 --- a/src/get-todos.js +++ b/src/get-todos.js @@ -114,14 +114,15 @@ class TodoParser { if (this.isRelevant(line)) { todos.push(line); if (this.#withChildren && this.#hasChildren(l)) { - const cs = this.#getChildren(l).filter(c => { + const children = this.#getChildren(l); + const cs = children.filter(c => { if (!this.#withCompletedChildren && this.#isCompletedTodo(c)) { return false; } return (!this.#filterChildren || this.isRelevant(c)); }); todos = [...todos, ...cs]; - l += cs.length; + l += children.length; } } }