diff --git a/package-lock.json b/package-lock.json index d9522c9..46773ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "joplin-plugin-gtd-calendar", - "version": "0.1.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "joplin-plugin-gtd-calendar", - "version": "0.1.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "date-fns": "^2.29.3", diff --git a/package.json b/package.json index e686e03..603543c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "joplin-plugin-gtd-calendar", - "version": "0.3.1", + "version": "0.3.2", "scripts": { "test": "jest", "dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive", diff --git a/src/Gtd/parseCalendarConfig.ts b/src/Gtd/parseCalendarConfig.ts index c5fadce..7bdc665 100644 --- a/src/Gtd/parseCalendarConfig.ts +++ b/src/Gtd/parseCalendarConfig.ts @@ -30,6 +30,8 @@ export default function parseCalendarConfig(raw: any): CalendarConfig { "todos", "sort", "sort-type", + "unscheduled-todos", + "unscheduled-notes", ]; for (const key of Object.keys(input)) { if (!knownKeys.includes(key)) warnings.push(`Unknown option "${key}"`); @@ -95,7 +97,49 @@ export default function parseCalendarConfig(raw: any): CalendarConfig { } } - return { view, title, scopeDepth, notes, todos, sort, sortType, warnings }; + // Unscheduled visibility toggles (default: shown). + const unscheduledTodos = parseBoolean( + input["unscheduled-todos"], + "unscheduled-todos", + warnings + ); + const unscheduledNotes = parseBoolean( + input["unscheduled-notes"], + "unscheduled-notes", + warnings + ); + + return { + view, + title, + scopeDepth, + notes, + todos, + sort, + sortType, + unscheduledTodos, + unscheduledNotes, + warnings, + }; +} + +/** + * Parse a permissive boolean: yes/no, true/false, show/hide (case- + * insensitive). Absent → true (sections shown by default). Invalid → + * true, with a warning. + */ +function parseBoolean( + value: any, + optionName: string, + warnings: string[] +): boolean { + if (value === undefined || value === null) return true; + if (typeof value === "boolean") return value; + const candidate = String(value).trim().toLowerCase(); + if (["yes", "true", "show", "on"].includes(candidate)) return true; + if (["no", "false", "hide", "off"].includes(candidate)) return false; + warnings.push(`Invalid ${optionName} "${value}" (using "yes")`); + return true; } function parseInclusion( diff --git a/src/Gtd/types.ts b/src/Gtd/types.ts index b2f35ff..6ca13ba 100644 --- a/src/Gtd/types.ts +++ b/src/Gtd/types.ts @@ -8,6 +8,9 @@ export interface CalendarConfig { todos: InclusionMode; sort: "asc" | "desc"; sortType: "title" | "modified_date"; + /** Whether to show the Unscheduled to-dos / notes sub-sections. */ + unscheduledTodos: boolean; + unscheduledNotes: boolean; /** Non-fatal problems found while parsing, surfaced in the UI. */ warnings: string[]; } diff --git a/src/gtd-calendar-webview.js b/src/gtd-calendar-webview.js index 8446482..96d2cd4 100644 --- a/src/gtd-calendar-webview.js +++ b/src/gtd-calendar-webview.js @@ -148,9 +148,19 @@ wrapper.appendChild(empty); } - if (calendar.unscheduled.length > 0) { + const showTodos = payload.unscheduledTodos !== false; + const showNotes = payload.unscheduledNotes !== false; + if ( + calendar.unscheduled.length > 0 && + (showTodos || showNotes) + ) { wrapper.appendChild( - renderUnscheduled(calendar.unscheduled, contentScriptId) + renderUnscheduled( + calendar.unscheduled, + contentScriptId, + showTodos, + showNotes + ) ); } @@ -370,7 +380,7 @@ // Unscheduled section (GTD bucket) // ------------------------------------------------------------------ - function renderUnscheduled(events, contentScriptId) { + function renderUnscheduled(events, contentScriptId, showTodos, showNotes) { const section = document.createElement("div"); section.className = "gtd-unscheduled"; @@ -382,8 +392,9 @@ }); // To-dos first (actionable), notes second (reference). Each - // sub-section is omitted entirely when it has no items. - if (todos.length > 0) { + // sub-section is omitted when empty OR when toggled off via the + // unscheduled-todos / unscheduled-notes config options. + if (showTodos && todos.length > 0) { section.appendChild( renderUnscheduledGroup( "Unscheduled to-dos", @@ -392,7 +403,7 @@ ) ); } - if (notes.length > 0) { + if (showNotes && notes.length > 0) { section.appendChild( renderUnscheduledGroup( "Unscheduled notes", diff --git a/src/index.ts b/src/index.ts index 9ec09ef..547498c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -144,6 +144,8 @@ async function handleGetEvents(message: { rawConfig?: string }) { warnings: [...config.warnings, ...result.warnings], sourceNote, calendar, + unscheduledTodos: config.unscheduledTodos, + unscheduledNotes: config.unscheduledNotes, stats: { scannedFolders: result.scannedFolders, scannedNotes: result.scannedNotes, diff --git a/src/manifest.json b/src/manifest.json index eb940e4..6319877 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 1, "id": "com.victorwiebe.joplin.plugin.gtd-calendar", "app_min_version": "2.7", - "version": "0.3.1", + "version": "0.3.2", "name": "GTD Calendar", "description": "Day, week, and month calendars populated by your notes and to-dos, with click-through to the source note. Configure with simple YAML blocks. A GTD-friendly fork of Event Calendar by Franco Speziali.", "author": "Victor Wiebe", diff --git a/src/tests/Gtd/gtd.test.ts b/src/tests/Gtd/gtd.test.ts index 96906e3..4e4af8f 100644 --- a/src/tests/Gtd/gtd.test.ts +++ b/src/tests/Gtd/gtd.test.ts @@ -411,3 +411,40 @@ describe("collectEvents — recurrence detection", () => { expect(note.isRecurring).toBe(false); }); }); + +describe("parseCalendarConfig — unscheduled toggles", () => { + test("default to shown when absent", () => { + const c = parseCalendarConfig({}); + expect(c.unscheduledTodos).toBe(true); + expect(c.unscheduledNotes).toBe(true); + }); + + test("yes/no are honoured", () => { + const c = parseCalendarConfig({ + "unscheduled-todos": "yes", + "unscheduled-notes": "no", + }); + expect(c.unscheduledTodos).toBe(true); + expect(c.unscheduledNotes).toBe(false); + }); + + test("true/false and show/hide are also accepted", () => { + expect( + parseCalendarConfig({ "unscheduled-todos": "false" }).unscheduledTodos + ).toBe(false); + expect( + parseCalendarConfig({ "unscheduled-notes": "hide" }).unscheduledNotes + ).toBe(false); + expect( + parseCalendarConfig({ "unscheduled-todos": true }).unscheduledTodos + ).toBe(true); + }); + + test("invalid value warns and defaults to shown", () => { + const c = parseCalendarConfig({ "unscheduled-todos": "maybe" }); + expect(c.unscheduledTodos).toBe(true); + expect(c.warnings.some((w) => w.includes("unscheduled-todos"))).toBe( + true + ); + }); +});