387 lines
22 KiB
Markdown
387 lines
22 KiB
Markdown
# GTD Calendar — a Joplin plugin
|
||
|
||
**Your notes and to-dos, on a calendar, inside a note.**
|
||
|
||
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.
|
||
|
||

|
||
|
||
## How it works
|
||
|
||
Put a `gtd-calendar` fenced code block in a note. That note becomes a calendar of the notes and to-dos around it:
|
||
|
||
```gtd-calendar
|
||
view: month-grid
|
||
title: GTD Calendar
|
||
todos: all
|
||
```
|
||
|
||

|
||
|
||
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:
|
||
|
||
```gtd
|
||
date: 2026-06-19
|
||
fg-colour: red
|
||
text: Seen on hover
|
||
```
|
||
|
||

|
||
|
||
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):
|
||
|
||

|
||
|
||
## Installation
|
||
|
||
Search for **GTD Calendar** in *Tools → Options → Plugins* (desktop).
|
||
|
||
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`, `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`, a number, or `all` | `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. **`scope: all` scans every notebook in your entire profile** — powerful for a global dashboard, but potentially slow on large profiles; a footnote reports how much was scanned, and a ⚠ appears past ~2000 notes. `notebook:` is ignored under `scope: all`. |
|
||
| `notebook` | notebook name, `Parent/Child` path, or folder id | this note's folder | Root the scan at a specific notebook instead of the one holding this note — even one in a different tree. `scope` then applies relative to it. A bare name must be unique; if several notebooks share it, qualify with a path (`Work/Archive`). Unknown/ambiguous → ⚠ warning + falls back to this note's folder. |
|
||
| `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. |
|
||
|
||
Invalid values fall back to defaults and show a ⚠ warning above the calendar.
|
||
|
||
## The `gtd` block
|
||
|
||
Goes in any note or to-do that should participate in calendar, kanban, or matrix views. **An empty block is valid**—it is the minimal opt-in. All properties are optional:
|
||
|
||
| Property | Example | What it does |
|
||
|---|---|---|
|
||
| `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. |
|
||
|
||
### 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.
|
||
|
||
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.
|
||
|
||
## Repeating to-dos
|
||
|
||
GTD Calendar plays nicely with the **[Repeating To-Dos](https://joplinapp.org/plugins/plugin/com.github.TheScriptingGuy.joplin-plugin-repeating-todos/)** plugin (v2). Install it, set a to-do to repeat (e.g. every Tuesday), and that to-do appears on the calendar at its next due date like any other — no extra setup. Recurring to-dos are marked with a ↻ symbol so you can tell them apart at a glance.
|
||
|
||
Because that plugin keeps a recurring to-do as a single item at its *next* due date, the calendar shows the next occurrence rather than every future one. Recurrence is detected via the `recurring` tag the plugin maintains; if you use a different recurrence tool that tags its to-dos `recurring`, the ↻ will appear for those too.
|
||
|
||
## Kanban board
|
||
|
||
Alongside calendars, GTD Calendar can render a **kanban board** from your to-dos and opted-in notes. Add a `gtd-kanban` block — on its own, or stacked beneath a calendar in the same dashboard note:
|
||
|
||
````
|
||
```gtd-kanban
|
||
title: Editorial Board
|
||
scope: this-folder
|
||
todos: all
|
||
sort-type: due-date
|
||
in-progress-tag: in-progress
|
||
done-tag: done
|
||
card-detail: hover
|
||
page-size: 10
|
||
done-window: 7
|
||
```
|
||
````
|
||
|
||
Ordinary notes opt in by containing a `gtd` block—the same block used by the
|
||
calendar. An empty block is valid. For example:
|
||
|
||
````
|
||
```gtd
|
||
title: Research brief
|
||
date: 2026-08-15
|
||
text: Supporting material for the next review
|
||
```
|
||
````
|
||
|
||
The board has three columns:
|
||
|
||
- **Backlog** — every eligible ordinary note or incomplete to-do without the
|
||
in-progress tag.
|
||
- **In Progress** — eligible notes and incomplete to-dos carrying the
|
||
in-progress tag (default `in-progress`; configurable).
|
||
- **Done** — opted-in ordinary notes carrying the configured done tag, plus
|
||
completed to-dos within `done-window` days (default 7; set `all` for full
|
||
to-do history).
|
||
|
||
Completion always wins over the in-progress tag. Ordinary notes use `done`;
|
||
native to-dos remain governed by native Joplin completion. Completed notes are
|
||
not filtered by `done-window` because they have no completion timestamp. The
|
||
`todos:` option still controls only to-dos.
|
||
|
||
| Option | Values | Default | Description |
|
||
|---|---|---|---|
|
||
| `title` | text | — | Heading above the board. |
|
||
| `scope` | `this-folder`, `children`, integer, `all` | `this-folder` | Same folder-scanning rules as the calendar, including `all` (every notebook — see the calendar table's caution). |
|
||
| `notebook` | notebook name, `Parent/Child` path, or folder id | this note's folder | Root the scan at a specific notebook (see the calendar table for the full resolution rules). `scope` applies relative to it. |
|
||
| `group` | `notebook` | — | With the literal `scope: children`, render a separate board for the root notebook and every non-empty descendant instead of one aggregated board. Other scope forms warn and remain aggregated. |
|
||
| `notes` | `all`, `none` | `all` | Which project notes appear. `all` means every ordinary note in scope that explicitly opts in with a `gtd` block; it never includes plain notes without that block. |
|
||
| `todos` | `gtd-only`, `all`, `none` | `gtd-only` | Which to-dos appear. `gtd-only` requires a `gtd` block; `all` includes every to-do in scope; `none` excludes all to-dos. This option does not affect opted-in ordinary notes. |
|
||
| `sort-type` | `due-date`, `title`, `modified-date` | `due-date` | Order of cards within each column. Under `due-date`, cards with no due date sort last. |
|
||
| `sort` | `asc`, `desc` | `asc` | Sort direction. |
|
||
| `in-progress-tag` | text | `in-progress` | The tag that places an eligible note or incomplete to-do in the In Progress column. |
|
||
| `done-tag` | text | `done` | Marks an opted-in ordinary note complete. Done takes priority over `in-progress-tag`; native to-dos still use Joplin completion. |
|
||
| `card-detail` | `hover`, `always`, `none` | `hover` | Whether each card's due date / hover text shows on hover, always, or never. |
|
||
| `page-size` | positive integer | `10` | Cards initially shown in each column and revealed by each **List more** click. Invalid values warn and fall back to 10. |
|
||
| `done-window` | integer or `all` | `7` | How many days back the Done column reaches. |
|
||
|
||
Use the two independent type filters to build focused views:
|
||
|
||
```yaml
|
||
# Projects only
|
||
notes: all
|
||
todos: none
|
||
```
|
||
|
||
```yaml
|
||
# Tasks only
|
||
notes: none
|
||
todos: all
|
||
```
|
||
|
||
```yaml
|
||
# Projects and tasks
|
||
notes: all
|
||
todos: all
|
||
```
|
||
|
||
Omitting `notes` preserves existing behavior (`notes: all`). Project notes still
|
||
require a `gtd` block and continue through the normal Backlog, In Progress, and
|
||
Done rules. `notes: none` filters them before warnings, tags, totals, grouping,
|
||
sorting, and pagination.
|
||
|
||
Each column applies `page-size` independently after filtering and sorting. When a column has more cards, **List more** reveals the next batch; hovering over it or focusing it with the keyboard shows exactly how many entries remain. Expansion lasts only in the current rendered view and resets when the note is reloaded. Column headings and board statistics always show full totals.
|
||
|
||
### Separate views for child notebooks
|
||
|
||
Kanban and matrix blocks can split a notebook tree into complete per-notebook
|
||
views. Use the literal `scope: children` together with `group: notebook`:
|
||
|
||
````
|
||
```gtd-kanban
|
||
title: Project boards
|
||
notebook: Projects
|
||
scope: children
|
||
group: notebook
|
||
todos: all
|
||
page-size: 10
|
||
```
|
||
````
|
||
|
||
For this tree:
|
||
|
||
```text
|
||
Projects
|
||
├── Home
|
||
│ ├── Archive (empty)
|
||
│ └── Repairs
|
||
└── Work
|
||
└── Release
|
||
```
|
||
|
||
the block renders a separate three-column board for cards owned directly by
|
||
`Projects`, `Projects/Home`, `Projects/Home/Repairs`, `Projects/Work`, and
|
||
`Projects/Work/Release`. `Projects/Home/Archive` is omitted when it has no
|
||
eligible cards. An empty parent is also omitted without hiding a non-empty
|
||
grandchild.
|
||
|
||
Cards are never rolled up into ancestor views: every eligible note or to-do
|
||
appears exactly once under the full path of its owning notebook. Full paths also
|
||
distinguish duplicate notebook names. Views are ordered root-first, then
|
||
depth-first with siblings sorted by title. Each generated column or quadrant has
|
||
its own `page-size` and **List more** state, and reloading resets all of them.
|
||
Malformed-item warnings appear beside their owning notebook view; scan and card
|
||
statistics appear once for the overall block.
|
||
|
||
The same options and ownership rules apply to `gtd-matrix`; every non-empty
|
||
notebook receives a complete four-quadrant matrix in the selected mode. Opted-in
|
||
ordinary notes group by their owning notebook exactly like to-dos.
|
||
|
||
Grouping is deliberately limited to the exact combination above. Plain
|
||
`scope: children` remains one aggregated view. `this-folder`, numeric depths,
|
||
and `scope: all` also remain aggregated; combining any of them with
|
||
`group: notebook` shows a warning and disables grouping.
|
||
|
||
Cards are compact and click through to their source note. To-dos use checkbox glyphs and may show recurrence ↻; ordinary notes use 📄 and never become recurring. Completed notes keep 📄 and use completed title styling. A custom `icon` overrides the fallback glyph. Note dates come only from the `gtd` block; to-do dates use the block first, then the native due date. Empty blocks opt in with defaults, while malformed blocks remain visible with fallback values and a warning. The board is **read-only** and never changes tags or completion. (Drag-and-drop is planned.)
|
||
|
||
## The Matrix (Skeleton & Eisenhower)
|
||
|
||
A third view: a 2×2 prioritisation matrix for incomplete to-dos and ordinary notes containing a `gtd` block. Add a `gtd-matrix` block anywhere:
|
||
|
||
````
|
||
```gtd-matrix
|
||
title: Priorities
|
||
todos: all
|
||
mode: skeleton
|
||
page-size: 8
|
||
done-tag: done
|
||
```
|
||
````
|
||
|
||
Ordinary notes use the same explicit opt-in as kanban. For example, this note
|
||
has a matrix date without becoming a to-do:
|
||
|
||
````
|
||
```gtd
|
||
date: 2026-08-15
|
||
text: Review before the planning meeting
|
||
```
|
||
````
|
||
|
||
Two modes:
|
||
|
||
### `mode: skeleton` (default) — the Skeleton Matrix
|
||
|
||
Built to complement the kanban and calendar, using the tags and dates you already maintain. Rows ask *am I on this?* (the in-progress tag); columns ask *is the clock running?* (due within `urgent-window` days — overdue counts — or carrying the urgent tag as a manual override):
|
||
|
||
| | **Due soon** | **Not due soon** |
|
||
|---|---|---|
|
||
| **Active** | Do Next | Scheduled |
|
||
| **Not active** | On Deck | Backlog |
|
||
|
||
**On Deck** is the quadrant to watch: due soon, not yet started. Nothing is ever labelled "Eliminate" — a Backlog item is simply low priority, not condemned. Dateless in-progress work sits in Scheduled (active, no clock). Eligible ordinary notes use their `gtd` date and the same tag rules; completed to-dos and notes carrying `done-tag` do not appear at all.
|
||
|
||
### `mode: eisenhower` — the classic
|
||
|
||
Quadrants from the urgent/important tags: Do First (both), Schedule (important), Delegate (urgent), Eliminate (neither).
|
||
|
||
### Options
|
||
|
||
| Option | Values | Default | Description |
|
||
|---|---|---|---|
|
||
| `mode` | `skeleton`, `eisenhower` | `skeleton` | Which matrix semantics to use. |
|
||
| `title` | text | — | Heading above the matrix. |
|
||
| `scope` | `this-folder`, `children`, integer, `all` | `this-folder` | Same folder-scanning rules as the calendar and kanban, including `all` (every notebook — see the calendar table's caution). |
|
||
| `notebook` | notebook name, `Parent/Child` path, or folder id | this note's folder | Root the scan at a specific notebook (see the calendar table for the full resolution rules). `scope` applies relative to it. |
|
||
| `group` | `notebook` | — | With the literal `scope: children`, render a separate complete matrix for the root notebook and every non-empty descendant. Other scope forms warn and remain aggregated. |
|
||
| `notes` | `all`, `none` | `all` | Which project notes appear. `all` includes only ordinary notes explicitly opted in with a `gtd` block; `none` produces a task-only matrix. |
|
||
| `todos` | `gtd-only`, `all`, `none` | `gtd-only` | Which to-dos appear. This option does not affect ordinary notes, which appear only when they contain a `gtd` block. |
|
||
| `sort-type` | `due-date`, `title`, `modified-date` | `due-date` | Order within each quadrant; dateless cards sort last under `due-date`. |
|
||
| `sort` | `asc`, `desc` | `asc` | Sort direction. |
|
||
| `urgent-tag` | any tag name | `urgent` | Eisenhower's urgent axis; the Skeleton mode's manual due-soon override. |
|
||
| `important-tag` | any tag name | `important` | Eisenhower's important axis (unused in Skeleton mode). |
|
||
| `in-progress-tag` | any tag name | `in-progress` | Skeleton mode's active row — same tag as the kanban's In Progress column. |
|
||
| `urgent-window` | integer (days) | `3` | Skeleton mode: how close a due date must be to count as "due soon". |
|
||
| `card-detail` | `hover`, `always`, `none` | `hover` | Card detail line behaviour, as on the kanban. |
|
||
| `page-size` | positive integer | `10` | Cards initially shown in each quadrant and revealed by each **List more** click. Invalid values warn and fall back to 10. |
|
||
|
||
Each quadrant expands independently in `page-size` batches. The **List more** hover/focus popup reports the number still hidden, and reloading the note resets all expanded quadrants. Quadrant headings and matrix statistics continue to show full totals.
|
||
|
||
Cards behave like kanban cards: compact, clickable, and read-only. Ordinary notes use the same `gtd` title/date/colour/icon/text overrides, use 📄 when no icon is set, and never show completion or recurrence styling. Empty and malformed blocks follow the same opt-in and warning behavior as on kanban.
|
||
|
||
## Gantt chart
|
||
|
||
A timeline of **projects** built from items across your notes. Unlike the other views, gantt items opt in with their **own** ` ```gtd-gantt ` block (not the `gtd` block), and every item declares a `project:` — that's what groups items into swimlanes.
|
||
|
||
The same fence name does two jobs:
|
||
|
||
- A ` ```gtd-gantt ` block **with** a `project:` key is an **item declaration**. It renders in place as a small inline badge (`gantt: <project>`), and the item appears on any gantt chart that scans it.
|
||
- A ` ```gtd-gantt ` block **without** a `project:` key is the **chart** itself.
|
||
|
||
### Item blocks
|
||
|
||
Put one in any note or to-do you want on the timeline:
|
||
|
||
````
|
||
```gtd-gantt
|
||
project: bluesky
|
||
begin-date: 2026-07-01 # notes only — the bar's span
|
||
end-date: 2026-07-14
|
||
bg-colour: teal # optional, same styling keys as the gtd block
|
||
fg-colour: white
|
||
title: Design phase # optional; defaults to the note title
|
||
text: Hover detail # optional
|
||
```
|
||
````
|
||
|
||
- **Notes become bars** — a span from `begin-date` to `end-date`. Both are required and must be valid, with `end-date` on or after `begin-date`; otherwise the item is excluded with a ⚠ warning.
|
||
- **To-dos become milestones** — a ◆ marker on the to-do's **native due date**. `begin-date`/`end-date` on a to-do are ignored (with a warning). A to-do with no due date is excluded with a warning. Completed milestones render dimmed and struck through; recurring to-dos get a ↻.
|
||
- Dates accept `yyyy-mm-dd` (and `mm-dd-yyyy`), same as everywhere else.
|
||
|
||
There is no "Unscheduled" section — an item with no usable date simply doesn't appear (the warning tells you why).
|
||
|
||
### Chart block
|
||
|
||
Put this where you want the timeline drawn:
|
||
|
||
````
|
||
```gtd-gantt
|
||
title: Roadmap
|
||
scope: children # this-folder | children | N | all
|
||
filter-project: bluesky # optional — limit the chart to one project
|
||
```
|
||
````
|
||
|
||
| Option | Values | Default | What it does |
|
||
|---|---|---|---|
|
||
| `title` | text | — | Heading above the chart. |
|
||
| `scope` | `this-folder`, `children`, a number, or `all` | `this-folder` | Same folder-scanning rules as every other view. |
|
||
| `notebook` | notebook name, `Parent/Child` path, or folder id | this note's folder | Root the scan at a specific notebook (see the calendar table). Ignored under `scope: all`. |
|
||
| `filter-project` | project name | — | Limit the chart to a single project (case-insensitive). |
|
||
| `sort-type` | `begin-date`, `title`, `modified-date` | `begin-date` | Row order **within** each project. |
|
||
| `sort` | `asc`, `desc` | `asc` | Sort direction. |
|
||
| `card-detail` | `hover`, `always`, `none` | `hover` | `none` suppresses the hover tooltip on bars/milestones. |
|
||
|
||
Note there is **no `notes`/`todos` option** — items opt in via their `project:` block, so those keys aren't used (and warn as unknown if set). Projects are shown alphabetically; the chart draws a month tick header, a red "today" line when today falls in range, and scrolls horizontally for wide ranges. Bars and milestones are clickable — they open the source note. Read-only.
|
||
|
||
## Drilldown
|
||
|
||
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.
|
||
|
||
## Limitations
|
||
|
||
- **Desktop only.** See the note below.
|
||
- `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.
|
||
|
||
### Mobile / Android
|
||
|
||
This plugin is built for **Joplin desktop** and is not expected to work on the Android or iOS apps. It depends on rendering custom code blocks in the note viewer and on two-way messaging between that view and the plugin — capabilities Joplin's mobile apps have historically not supported, or supported only partially. On mobile, a `gtd-calendar` block will most likely render as plain text or not at all, and drilldown won't work. The companion *Repeating To-Dos* plugin is desktop-only as well.
|
||
|
||
Your data is unaffected, though: the calendar is only a *view*. Your to-dos and their due dates are ordinary Joplin items that sync to mobile normally and appear in Joplin's built-in to-do list and search, with alarms intact — you just won't see the calendar rendering there. If you want a quick scheduled-items view on mobile, pair the calendar with Joplin's native to-do view or the *Embed Search* plugin on desktop.
|
||
|
||
## Bugs & feedback
|
||
|
||
Issues and ideas: [the repository](https://gitea.skeletonworks.online/vwiebe/joplin-plugin-gtd-calendar/issues), or find me on the Joplin forum.
|
||
|
||
## Development
|
||
|
||
```bash
|
||
npm install
|
||
npm test # 251 unit tests
|
||
npm run dist # builds publish/*.jpl
|
||
```
|
||
|
||
Design notes and the full specification live in [SPEC.md](SPEC.md). Release history is in [CHANGELOG.md](CHANGELOG.md).
|
||
|
||
### Cutting a release
|
||
|
||
1. Bump `version` in `src/manifest.json`, `package.json`, and `package-lock.json` (the manifest and package versions must match).
|
||
2. Add an entry to `CHANGELOG.md`.
|
||
3. `npm publish` (the `prepare` script rebuilds the `.jpl` fresh from source, so you can't accidentally publish a stale build).
|
||
|
||
If `npm publish` fails with `E404` on the `PUT`, check `npm whoami` first — a 401 there means your login session has expired, which npm surfaces as a misleading 404 on publish. Fix with `npm login --auth-type=legacy`, or avoid it entirely with a [granular access token](https://docs.npmjs.com/creating-and-viewing-access-tokens) so publishing never needs an interactive session.
|
||
|
||
## Credits & license
|
||
|
||
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.
|
||
|
||
Made by [Victor Wiebe](https://victorwiebe.com). Creative work for an uncaring world.
|