v0.3.2: per-type Unscheduled visibility toggles
- New gtd-calendar options unscheduled-todos and unscheduled-notes (default yes), independently showing/hiding each Unscheduled sub-section. Accepts yes/no, true/false, show/hide. - Solves duplicate Unscheduled lists when two calendars share a note: e.g. show unscheduled to-dos under the week strip, unscheduled notes under the month grid. - Permissive parseBoolean helper; invalid values warn and default to yes. - Threaded through payload; webview gates each sub-section on its flag (empty OR toggled-off both omit cleanly). - Version 0.3.2; 4 new tests (69 total); README updated.
This commit is contained in:
parent
a3d7acde7f
commit
1cc6f184f0
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "joplin-plugin-gtd-calendar",
|
"name": "joplin-plugin-gtd-calendar",
|
||||||
"version": "0.1.0",
|
"version": "0.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "joplin-plugin-gtd-calendar",
|
"name": "joplin-plugin-gtd-calendar",
|
||||||
"version": "0.1.0",
|
"version": "0.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "joplin-plugin-gtd-calendar",
|
"name": "joplin-plugin-gtd-calendar",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
|
"dist": "webpack --env joplin-plugin-config=buildMain && webpack --env joplin-plugin-config=buildExtraScripts && webpack --env joplin-plugin-config=createArchive",
|
||||||
|
|||||||
@ -30,6 +30,8 @@ export default function parseCalendarConfig(raw: any): CalendarConfig {
|
|||||||
"todos",
|
"todos",
|
||||||
"sort",
|
"sort",
|
||||||
"sort-type",
|
"sort-type",
|
||||||
|
"unscheduled-todos",
|
||||||
|
"unscheduled-notes",
|
||||||
];
|
];
|
||||||
for (const key of Object.keys(input)) {
|
for (const key of Object.keys(input)) {
|
||||||
if (!knownKeys.includes(key)) warnings.push(`Unknown option "${key}"`);
|
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(
|
function parseInclusion(
|
||||||
|
|||||||
@ -8,6 +8,9 @@ export interface CalendarConfig {
|
|||||||
todos: InclusionMode;
|
todos: InclusionMode;
|
||||||
sort: "asc" | "desc";
|
sort: "asc" | "desc";
|
||||||
sortType: "title" | "modified_date";
|
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. */
|
/** Non-fatal problems found while parsing, surfaced in the UI. */
|
||||||
warnings: string[];
|
warnings: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,9 +148,19 @@
|
|||||||
wrapper.appendChild(empty);
|
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(
|
wrapper.appendChild(
|
||||||
renderUnscheduled(calendar.unscheduled, contentScriptId)
|
renderUnscheduled(
|
||||||
|
calendar.unscheduled,
|
||||||
|
contentScriptId,
|
||||||
|
showTodos,
|
||||||
|
showNotes
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,7 +380,7 @@
|
|||||||
// Unscheduled section (GTD bucket)
|
// Unscheduled section (GTD bucket)
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
function renderUnscheduled(events, contentScriptId) {
|
function renderUnscheduled(events, contentScriptId, showTodos, showNotes) {
|
||||||
const section = document.createElement("div");
|
const section = document.createElement("div");
|
||||||
section.className = "gtd-unscheduled";
|
section.className = "gtd-unscheduled";
|
||||||
|
|
||||||
@ -382,8 +392,9 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// To-dos first (actionable), notes second (reference). Each
|
// To-dos first (actionable), notes second (reference). Each
|
||||||
// sub-section is omitted entirely when it has no items.
|
// sub-section is omitted when empty OR when toggled off via the
|
||||||
if (todos.length > 0) {
|
// unscheduled-todos / unscheduled-notes config options.
|
||||||
|
if (showTodos && todos.length > 0) {
|
||||||
section.appendChild(
|
section.appendChild(
|
||||||
renderUnscheduledGroup(
|
renderUnscheduledGroup(
|
||||||
"Unscheduled to-dos",
|
"Unscheduled to-dos",
|
||||||
@ -392,7 +403,7 @@
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (notes.length > 0) {
|
if (showNotes && notes.length > 0) {
|
||||||
section.appendChild(
|
section.appendChild(
|
||||||
renderUnscheduledGroup(
|
renderUnscheduledGroup(
|
||||||
"Unscheduled notes",
|
"Unscheduled notes",
|
||||||
|
|||||||
@ -144,6 +144,8 @@ async function handleGetEvents(message: { rawConfig?: string }) {
|
|||||||
warnings: [...config.warnings, ...result.warnings],
|
warnings: [...config.warnings, ...result.warnings],
|
||||||
sourceNote,
|
sourceNote,
|
||||||
calendar,
|
calendar,
|
||||||
|
unscheduledTodos: config.unscheduledTodos,
|
||||||
|
unscheduledNotes: config.unscheduledNotes,
|
||||||
stats: {
|
stats: {
|
||||||
scannedFolders: result.scannedFolders,
|
scannedFolders: result.scannedFolders,
|
||||||
scannedNotes: result.scannedNotes,
|
scannedNotes: result.scannedNotes,
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"manifest_version": 1,
|
"manifest_version": 1,
|
||||||
"id": "com.victorwiebe.joplin.plugin.gtd-calendar",
|
"id": "com.victorwiebe.joplin.plugin.gtd-calendar",
|
||||||
"app_min_version": "2.7",
|
"app_min_version": "2.7",
|
||||||
"version": "0.3.1",
|
"version": "0.3.2",
|
||||||
"name": "GTD Calendar",
|
"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.",
|
"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",
|
"author": "Victor Wiebe",
|
||||||
|
|||||||
@ -411,3 +411,40 @@ describe("collectEvents — recurrence detection", () => {
|
|||||||
expect(note.isRecurring).toBe(false);
|
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
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user