diff --git a/README.md b/README.md index 07c9893..40d4de7 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Minimal self-hosted personal wiki. Folders are pages. - **Wikilinks**: `[[query]]` or `[[query::label]]`. They open a search, so links still work after a page is renamed. Typing `[[` in the editor suggests page names. Embed files with `![caption](photo.jpg "left")`. - **Movie import**: create a page from OMDb metadata. Asks once for a free [OMDb API key](https://www.omdbapi.com/apikey.aspx) and keeps it in the browser. - **Special folders**: a [photo diary](#diary) and a [fitness dashboard](#fitness). +- **Calendars**: Nextcloud calendars mirrored into the wiki. They add events and weight to the diary, and a [maintenance widget](#calendars) to every page. - **Quick-add bookmarklet**: save the current tab to a wiki page. - **Companion app**: an optional desktop helper, downloadable from the wiki footer. It opens wiki files in local apps and folders in the file manager. @@ -33,6 +34,8 @@ GOOS=linux GOARCH=arm go build -o datascape . # QNAP NAS | `-cache` | `./cache` | Thumbnail cache | | `-user`, `-pass` | | Basic auth (omit to disable) | | `-reindex-interval` | `30m` | Search index rebuild (`0` = off) | +| `-calendars` | `Calendars` | [Calendars](#calendars) folder, relative to `-dir` (empty = off) | +| `-tz` | system | Time zone for calendars and the diary, e.g. `Europe/Berlin` | ## `.page-settings` @@ -43,7 +46,7 @@ Per-folder `key = value` file. The **view** button in the `Files` header sets th | `view` | `list`, `thumbnail` | | `sort` | `name`, `modified`, `size` | | `order` | `asc`, `desc` | -| `type` | `diary`, `fitness` | +| `type` | `diary`, `fitness`, `calendars` | ## Keyboard shortcuts @@ -51,7 +54,7 @@ All use Alt+Shift. | Where | Keys | |-------|------| -| Anywhere | `E` edit, `N` new page, `M` move, `F` search | +| Anywhere | `E` edit, `N` new page, `M` move, `F` search, `H` maintenance | | Editor | `S` save, `1`–`3` heading, `B`/`I` bold/italic, `C`/`K` code/code block, `L` link, `P` wikilink, `G` embed, `U`/`O`/`X` bullet/numbered/task list, `Q` quote, `R` rule, `T` format table, `D`/`W` date (ISO/long), `V` movie, `Y` delete line | ## Diary @@ -72,6 +75,46 @@ Bookmarkable shortcuts: `/today/`, `/this-month/`, `/this-y With `type = fitness`, a folder shows weight charts (daily and weekly mean, with your goal) below its content. Copy the [Waistline](https://github.com/davidhealey/waistline) app's export into the folder as `waistline_export.json`. Each new export replaces the old file. +## Calendars + +``` +Calendars/ + .page-settings ← type = calendars, roles, sync credentials + Personal.ics ← one file per calendar + Gewicht.ics + … +``` + +Pages only read these local files and never contact the calendar server. The built-in sync downloads all calendars of a CalDAV calendar home in the background. Or you can put the `.ics` files there yourself. + +``` +type = calendars + +# Roles: file names without .ics +diary = Personal, Feiertage +weight = Gewicht +maintenance = Maintainance + +# Sync (Nextcloud) +caldav-url = https://cloud.example/remote.php/dav/calendars/me/ +caldav-user = me +caldav-pass = xxxxx-xxxxx-xxxxx-xxxxx-xxxxx +sync-interval = 15m +``` + +| Key | | +|-----|-| +| `diary` | Events shown under each diary day: all-day first, then by start time | +| `weight` | Titles like `95,6kg`, shown under the diary day's events | +| `maintenance` | Tasks in the header widget (with the number due today) and on the calendars page | +| `caldav-url` | Calendar home. Empty = no sync | +| `caldav-user`, `caldav-pass` | Use a Nextcloud app password (Settings → Security), not the account password | +| `sync-interval` | Default `15m`. `0` = only at startup and with **Sync now** | + +Changes apply without a restart. Each calendar is saved as `.ics`. A download that fails or is not valid iCalendar keeps the old file. Datascape never serves dotfiles, so the password in `.page-settings` cannot be downloaded. It is still in every backup of the wiki folder. + +The calendars page lists the maintenance tasks (repeat rule, last date, next date) and the sync status of each calendar. + ## Quick-add bookmarklet Replace `wiki.host` and `/Topics/Bookmarks/` with your host and target page: diff --git a/assets/calendar/page.html b/assets/calendar/page.html new file mode 100644 index 0000000..5496766 --- /dev/null +++ b/assets/calendar/page.html @@ -0,0 +1,45 @@ +

Maintenance

+{{if .Maintenance}} +
+ + + {{range .Maintenance}} + + {{end}} + +
TaskRepeatsLastNext
{{.Summary}}{{.Rule}}{{.Last}}{{.Next}}
+{{else if .HasMaintenance}} +

No maintenance tasks within a year of today.

+{{else}} +

No maintenance calendar. Set maintenance = <name> in this folder's .page-settings.

+{{end}} + +

Calendars

+{{if .Calendars}} +
+ + + {{range .Calendars}} + + + + + + + {{end}} + +
CalendarRoleEventsUpdated
{{.Name}}{{if .Error}}
{{.Error}}
{{end}}
{{.Roles}}{{.Events}}{{.Updated}}
+{{else}} +

No .ics files in this folder yet.

+{{end}} +{{if .IsSyncDir}} +{{if .SyncEnabled}} +
+ + {{if .LastRun}}Last sync: {{.LastRun}}{{else}}Not synced yet{{end}} +
+{{if .LastError}}

{{.LastError}}

{{end}} +{{else}} +

Sync is off. Set caldav-url, caldav-user and caldav-pass in this folder's .page-settings.

+{{end}} +{{end}} diff --git a/assets/calendar/widget.html b/assets/calendar/widget.html new file mode 100644 index 0000000..c6b6489 --- /dev/null +++ b/assets/calendar/widget.html @@ -0,0 +1,16 @@ + + + + diff --git a/assets/diary/content.html b/assets/diary/content.html index 4a5bcd4..283a843 100644 --- a/assets/diary/content.html +++ b/assets/diary/content.html @@ -10,4 +10,8 @@ {{end}} {{end}} +{{if or .Events .Weight}}
+{{if .Events}}

{{.Events}}

{{end}} +{{if .Weight}}

{{.Weight}}

{{end}} +
{{end}} {{end}} diff --git a/assets/global-shortcuts.js b/assets/global-shortcuts.js index 75ea9e1..d748d1d 100644 --- a/assets/global-shortcuts.js +++ b/assets/global-shortcuts.js @@ -16,6 +16,12 @@ e.preventDefault(); if (window.location.pathname !== '/' && typeof movePage === 'function') movePage(); break; + case 'H': + var maint = document.querySelector('[data-action="maint-menu"]'); + if (!maint) return; + e.preventDefault(); + maint.click(); + break; case 'F': var input = document.querySelector('.search-input'); if (!input) return; @@ -58,12 +64,14 @@ document.addEventListener('keydown', function (e) { }); document.addEventListener('DOMContentLoaded', function () { - var trigger = document.querySelector('[data-action="actions-menu"]'); - if (!trigger) return; - wireDropdown(trigger); - var menu = trigger.parentElement.querySelector('.dropdown-menu'); - if (!menu) return; - trigger.addEventListener('click', function () { - trigger.setAttribute('aria-expanded', menu.classList.contains('is-open') ? true : false); + ['actions-menu', 'maint-menu'].forEach(function (action) { + var trigger = document.querySelector('[data-action="' + action + '"]'); + if (!trigger) return; + wireDropdown(trigger); + var menu = trigger.parentElement.querySelector('.dropdown-menu'); + if (!menu) return; + trigger.addEventListener('click', function () { + trigger.setAttribute('aria-expanded', menu.classList.contains('is-open') ? true : false); + }); }); }); diff --git a/assets/icons/delete.svg b/assets/icons/delete.svg new file mode 100644 index 0000000..05d88ba --- /dev/null +++ b/assets/icons/delete.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/edit.svg b/assets/icons/edit.svg new file mode 100644 index 0000000..ea65ed2 --- /dev/null +++ b/assets/icons/edit.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/maintenance.svg b/assets/icons/maintenance.svg new file mode 100644 index 0000000..58051dd --- /dev/null +++ b/assets/icons/maintenance.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/move.svg b/assets/icons/move.svg new file mode 100644 index 0000000..25bcbb9 --- /dev/null +++ b/assets/icons/move.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/new.svg b/assets/icons/new.svg new file mode 100644 index 0000000..8aef0c0 --- /dev/null +++ b/assets/icons/new.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/rebuild.svg b/assets/icons/rebuild.svg new file mode 100644 index 0000000..4b5b721 --- /dev/null +++ b/assets/icons/rebuild.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/icons/reveal.svg b/assets/icons/reveal.svg new file mode 100644 index 0000000..e6b7e99 --- /dev/null +++ b/assets/icons/reveal.svg @@ -0,0 +1,4 @@ + + + diff --git a/assets/layout.html b/assets/layout.html index 23c1a51..9a27888 100644 --- a/assets/layout.html +++ b/assets/layout.html @@ -31,6 +31,7 @@ {{end}}
{{if .EditMode}}{{block "headerActions" .}}{{end}}{{else}} + {{maintenanceWidget}}