diff --git a/README.md b/README.md index 7eca313..0fe5c67 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,108 @@ -# GTD Calendar — Project Specification (v1 Draft) +# GTD Calendar — a Joplin plugin -A fork of [WeMakeMachines/joplin-plugin-event-calendar](https://github.com/WeMakeMachines/joplin-plugin-event-calendar) (MIT) that inverts the plugin's data model: instead of events living as YAML inside the calendar note, the calendar is **populated by real Joplin notes and todos** in the surrounding folder tree, with clickable drilldown to the source note. +**Your notes and to-dos, on a calendar, inside a note.** -**Working name:** `joplin-plugin-gtd-calendar` -**Proposed manifest id:** `com.victorwiebe.joplin.plugin.gtd-calendar` (must differ from upstream's id so both plugins can coexist during development) +GTD Calendar renders day, week, and month calendars in any Joplin note — populated not by hand-maintained YAML, but by the actual notes and to-dos in the surrounding folder. Click an event and it opens the note. Items without a date collect in an **Unscheduled** bucket below the calendar, GTD-style. ---- +![Month grid view](docs/screenshots/month-grid.png) -## 1. Concept +## How it works -A note anywhere in a folder contains a ` ```gtd-calendar ` fenced block. That block renders as a Day/Week/Month calendar populated by notes and todos in scope. Items opt in (by default) via a ` ```gtd ` fenced block in their body. Clicking a calendar event opens the underlying note. +Put a `gtd-calendar` fenced code block in a note. That note becomes a calendar of the notes and to-dos around it: -Dateless items are not lost — they collect in an **Unscheduled** section below the calendar grid, functioning as a GTD next-actions / someday-maybe bucket. + ```gtd-calendar + view: month-grid + title: GTD Calendar + todos: all + ``` ---- +![Config and result side by side](docs/screenshots/month-grid-config.png) -## 2. Schema Reference +Notes and to-dos opt in to the calendar with a `gtd` fenced code block — even an empty one. The block can also set a date (for plain notes), colours, an icon, and hover text: -### 2.1 The `gtd-calendar` block (lives in the calendar note) + ```gtd + date: 2026-06-19 + fg-colour: red + text: Seen on hover + ``` -``` -```gtd-calendar -view: month # day | week | month (default: day, matching upstream) -title: Editorial Schedule -scope: this-folder # this-folder | children | (default: this-folder) -notes: gtd-only # gtd-only | all | none (default: gtd-only) -todos: gtd-only # gtd-only | all | none (default: gtd-only) -sort: asc # asc | desc (default: asc) -sort-type: title # title | modified_date (default: title) -``` +![A note with a gtd block](docs/screenshots/gtd-block.png) -| Key | Values | Default | Notes | +To-dos don't need any of that for scheduling — their normal Joplin **due date** places them on the calendar automatically (when admitted by your `todos:` setting): + +![A todo scheduled by its native due date](docs/screenshots/todo-due-date.png) + +## Installation + +Search for **GTD Calendar** in *Tools → Options → Plugins* (desktop). + +Manual install: download the `.jpl` from the [releases](https://gitea.skeletonworks.online/vwiebe/joplin-plugin-gtd-calendar/releases), then *Plugins → gear icon → Install from file*. + +Requires Joplin **2.7+** on desktop. + +## The `gtd-calendar` block + +Goes in the note that should display the calendar. All options are optional. + +| Option | Values | Default | What it does | |---|---|---|---| -| `view` | `day`, `week`, `month` (also `d`/`w`/`m`, as upstream) | `day` | Grouping granularity of the grid. | -| `title` | string | none | Rendered as a heading above the calendar. | -| `scope` | `this-folder`, `children`, or an integer | `this-folder` | `children` = unlimited recursion into subfolders. An integer *n* = this folder plus *n* levels of descendants (`0` ≡ `this-folder`). Documentation will caution that broad scopes scan every note body in the tree. | -| `notes` | `gtd-only`, `all`, `none` | `gtd-only` | Which plain notes participate. `all` includes notes without a `gtd` block (they receive default styling and are Unscheduled unless... see §3.2). | -| `todos` | `gtd-only`, `all`, `none` | `gtd-only` | Same semantics for todos. | -| `sort` | `asc`, `desc` | `asc` | Order of events **within** each day/week/month grouping and within Unscheduled. Groupings themselves always run chronologically. | -| `sort-type` | `title`, `modified_date` | `title` | Field used by `sort`. `modified_date` maps to Joplin's `updated_time`. | +| `view` | `day`, `week`, `month`, `month-grid` | `day` | `day`/`week`/`month` (or `d`/`w`/`m`) render a compact tile strip spanning all events. `month-grid` (or `grid`) renders the entire **current** calendar month as a classic 7-column grid with weekday headers. | +| `title` | text | — | Heading above the calendar. | +| `scope` | `this-folder`, `children`, or a number | `this-folder` | Which folders to scan: just this one, all descendants, or *n* levels down (`scope: 2`). Broad scopes scan every note body in the tree — fine for normal folders, worth knowing for huge ones. | +| `notes` | `gtd-only`, `all`, `none` | `gtd-only` | Which plain notes appear: only those with a `gtd` block, every note in scope, or none. | +| `todos` | `gtd-only`, `all`, `none` | `gtd-only` | Same, for to-dos. `todos: all` is handy — every to-do with a due date lands on the calendar with zero markup. | +| `sort` | `asc`, `desc` | `asc` | Order of events *within* each day/week/month and in Unscheduled. The timeline itself is always chronological. | +| `sort-type` | `title`, `modified_date` | `title` | What `sort` sorts by. | -Unknown keys are ignored with a console warning. A malformed block renders an inline error box rather than failing silently (improvement over upstream, which swallows parse errors into the console). +Invalid values fall back to defaults and show a ⚠ warning above the calendar. -### 2.2 The `gtd` block (lives in participating notes/todos) +## The `gtd` block -``` -```gtd -date: 2026-07-04 # required for notes to be scheduled; optional override for todos -bg-colour: teal # any CSS color value -fg-colour: white -icon: 🦴 -title: Override title # optional; defaults to the note's own title -text: Hover text # optional; tooltip detail, as upstream -``` +Goes in any note or to-do that should appear on the calendar. **An empty block is valid** — it's the minimal opt-in. All properties are optional: -| Key | Applies to | Behavior | +| Property | Example | What it does | |---|---|---| -| `date` | notes | Required for the item to appear **in the grid**; without it the note goes to Unscheduled. Accepts `yyyy-mm-dd` (and `mm-dd-yyyy` for upstream parity). | -| `date` | todos | Optional. If present, **overrides** `todo_due` (explicit beats implicit). | -| `bg-colour` / `bgColor` | both | Event tile background. Both spellings accepted; `bg-colour` is canonical for this fork. | -| `fg-colour` | both | Event tile text color (new vs. upstream). | -| `icon` | both | Emoji/short string, as upstream. Defaults: type indicator (see §4). | -| `title` | both | Overrides the note title on the tile. | -| `text` | both | Tooltip body. | +| `date` | `2026-07-04` | Schedules the item (`yyyy-mm-dd` or `mm-dd-yyyy`). Required for plain notes to appear in the grid; for to-dos it *overrides* the Joplin due date. | +| `bg-colour` | `teal`, `#264653` | Tile/chip background. Any CSS colour. (`bgColor` also accepted.) Without it, the item gets a stable pastel derived from its note ID. | +| `fg-colour` | `white` | Tile/chip text colour. | +| `icon` | `🦴` | Shown on the tile/chip instead of the first two characters of the title. | +| `title` | `Launch day` | Replaces the note's title on the calendar only. | +| `text` | `Seen on hover` | Detail line in the hover card / tooltip. | -An **empty** ` ```gtd ` block is valid and meaningful: it is the opt-in signal with all defaults. +### Where do dates come from? ---- +1. A `date:` in the `gtd` block always wins. +2. Otherwise, a to-do uses its Joplin **due date**. +3. No date at all → the item appears in the **Unscheduled** section below the calendar: your dateless next-actions, always in view, one click from their notes. -## 3. Data Model Rules +To-dos keep being real Joplin to-dos throughout — due dates still sort, alarm, and sync exactly as before. Completed to-dos render checked and struck through. -### 3.1 Date resolution +## Drilldown -1. **Todos:** `todo_due` from the Joplin API. A `date:` in the `gtd` block overrides it. -2. **Notes:** `date:` from the `gtd` block only. No fallback to created/updated time — those are not event dates and would produce misleading calendars. -3. **No resolvable date** → item is rendered in the **Unscheduled** section below the grid. +Everything is clickable: single-event tiles, every row of a multi-event hover card, every chip in the month grid, every Unscheduled chip. Clicking opens the source note. -### 3.2 Inclusion matrix +## Limitations -An item appears on the calendar iff its type's mode admits it: +- Desktop only (relies on plugin messaging and the `openNote` command; mobile plugin support is partial). +- `month-grid` always shows the current month; there is no month navigation yet. +- The calendar refreshes when the note re-renders (e.g. switching notes); edits to *other* notes don't live-update an open calendar. -| Mode | Has `gtd` block | No `gtd` block | -|---|---|---| -| `gtd-only` (default) | ✅ included | ❌ excluded | -| `all` | ✅ included | ✅ included (default styling) | -| `none` | ❌ excluded | ❌ excluded | +## Bugs & feedback -The calendar note itself is always excluded from results, as is any note whose only `gtd-calendar` block makes it a calendar (a note may, however, be both an event and contain a calendar if it has both block types — edge case, supported, documented). +Issues and ideas: [the repository](https://gitea.skeletonworks.online/vwiebe/joplin-plugin-gtd-calendar/issues), or find me on the Joplin forum. -### 3.3 Completed todos +## Development -**v1 decision:** completed todos (`todo_completed > 0`) render with strikethrough + checked indicator, and a future `completed: show | hide` calendar option is reserved. (Logged as decision D3 below; cheap to change.) - ---- - -## 4. Rendering - -- Reuses upstream's grouping/renderer architecture (`DayGrouping`/`WeekGrouping`/`MonthGrouping` + DOM renderers + CSS asset), including the empty-grouping placeholders and current-date highlight. -- **Fixes upstream bug:** `Calendar/index.ts` imports `WeekGrouping` from `Month/MonthGrouping`, so the week view silently groups by month. Corrected in the fork. -- **Type distinction:** todos render with a checkbox glyph (☐ / ☑ when completed); notes render with a document glyph. Glyphs are suppressed if the item supplies its own `icon`. A subtle border-style difference (solid vs. dashed, TBD in CSS) keeps the distinction visible under custom colours. -- **Unscheduled section:** pinned below the grid, with its own header, honoring `sort` / `sort-type`. -- **Drilldown:** each tile is clickable and opens the source note. - ---- - -## 5. Architecture - -Upstream is a single synchronous markdown-it content script with no data API access. The fork becomes a three-part plugin: - -``` -┌─────────────────────────────┐ -│ 1. Markdown-it content │ Intercepts ```gtd-calendar fences. -│ script (renderer) │ Parses YAML config, emits a placeholder -│ │
carrying the config as data-attrs. -└──────────────┬──────────────┘ - │ (rendered HTML) -┌──────────────▼──────────────┐ -│ 2. Webview asset script │ Finds placeholders, calls -│ (runs in rendered note) │ webviewApi.postMessage(config). -│ │ Receives event data, builds the -│ │ calendar DOM via the (reused) -│ │ grouping/renderer classes. -│ │ Click → postMessage({open: noteId}). -└──────────────┬──────────────┘ - │ postMessage / onMessage -┌──────────────▼──────────────┐ -│ 3. Main plugin process │ joplin.contentScripts.onMessage: -│ (index.ts) │ • resolve current note's folder -│ │ (joplin.workspace.selectedNote) -│ │ • build folder tree, apply scope -│ │ • fetch notes/todos (id, title, -│ │ is_todo, todo_due, todo_completed, -│ │ updated_time, body) -│ │ • parse ```gtd blocks from bodies -│ │ • apply inclusion matrix, return -│ │ structured event list -│ │ • handle open: joplin.commands -│ │ .execute('openNote', noteId) -└─────────────────────────────┘ +```bash +npm install +npm test # 47 unit tests +npm run dist # builds publish/*.jpl ``` -### Performance notes +Design notes and the full v1 specification live in [SPEC.md](SPEC.md). -- Folder tree and note list fetched per render request; paginated API calls (`page`/`has_more`). -- Body scanning is limited to items in scope. For large scopes, pre-filter with Joplin's search API (`body:"```gtd"`-style query) before fetching full bodies. Cache by `updated_time` if needed; not a v1 requirement. -- Refresh model: re-render occurs on note switch (Joplin behavior). `joplin.workspace.onNoteChange` live refresh is a stretch goal, not v1. +## Credits & license -### Platform caveats +A fork of [Event Calendar](https://github.com/WeMakeMachines/joplin-plugin-event-calendar) by **Franco Speziali** (WeMakeMachines), whose tile-strip design this plugin proudly keeps. Forked with gratitude under the MIT license; this plugin is MIT as well. -- Desktop is the target. Mobile plugin support is partial; `openNote` behavior there is unverified. The "embed search" pairing remains a documented fallback for mobile users. - ---- - -## 6. Implementation Phases - -1. **Phase 0 — Scaffolding.** Fork, rename (manifest id, plugin name, file names), build pipeline verified (`npm install && npm run dist`), upstream week-view bug fixed, upstream tests green. -2. **Phase 1 — Plumbing.** Placeholder rendering for `gtd-calendar` fences; webview ⇄ plugin message round-trip proven with a hardcoded payload. -3. **Phase 2 — Data layer.** Folder tree + scope resolution; note/todo fetching; `gtd` block parsing; inclusion matrix; date resolution; unit tests for all of it (the upstream Jest setup carries over). -4. **Phase 3 — Rendering.** Wire real data into the reused renderers; Unscheduled section; type glyphs; fg-colour support; sort options. -5. **Phase 4 — Drilldown & polish.** Click-to-open; error boxes for malformed YAML; docs/README; scope-performance warnings. - ---- - -## 7. Decision Log / Open Items - -| # | Decision | Status | -|---|---|---| -| D1 | Defaults: `notes: gtd-only`, `todos: gtd-only` | ⚠️ **Confirm** — earlier discussion said todos are *always* included (i.e. `todos: all` default); the final message specified `gtd-only` as default for both. Spec follows the final message. | -| D2 | `gtd` `date:` overrides `todo_due` (vs. emitting a warning) | Adopted; revisit if confusing in practice. | -| D3 | Completed todos: shown struck-through in v1; `completed:` option reserved | Proposed, unconfirmed. | -| D4 | `sort`/`sort-type` act within groupings only; grid is always chronological | Confirmed. | -| D5 | Colour key spelling: `bg-colour`/`fg-colour` canonical, `bgColor` accepted for upstream parity | Proposed. | -| D6 | Future options parked: `completed:`, tag-based filtering, week-start day, live refresh | Backlog. | - ---- - -## 8. Licensing & Attribution - -Upstream is MIT (© Franco Speziali / WeMakeMachines). The fork retains the MIT license, preserves the original copyright notice, and credits upstream in the README per standard practice. \ No newline at end of file +Made by [Victor Wiebe](https://victorwiebe.com). Creative work for an uncaring world. diff --git a/docs/screenshots/gtd-block.png b/docs/screenshots/gtd-block.png new file mode 100644 index 0000000..d5dd2a4 Binary files /dev/null and b/docs/screenshots/gtd-block.png differ diff --git a/docs/screenshots/month-grid-config.png b/docs/screenshots/month-grid-config.png new file mode 100644 index 0000000..25c461e Binary files /dev/null and b/docs/screenshots/month-grid-config.png differ diff --git a/docs/screenshots/month-grid.png b/docs/screenshots/month-grid.png new file mode 100644 index 0000000..4fb6c6d Binary files /dev/null and b/docs/screenshots/month-grid.png differ diff --git a/docs/screenshots/todo-due-date.png b/docs/screenshots/todo-due-date.png new file mode 100644 index 0000000..729e5c2 Binary files /dev/null and b/docs/screenshots/todo-due-date.png differ