From 58f6fa4fe88ff012a88494d5aef027476d0c87b2 Mon Sep 17 00:00:00 2001 From: Levi Neely <141506390+lneely@users.noreply.github.com> Date: Tue, 25 Nov 2025 13:53:33 +0100 Subject: [PATCH] Reorganization, renaming. Readme updates. Better feature parity in Djournal. --- README.md | 68 +++++++++++++++++-------- mkfile | 5 +- scripts/Djournal | 128 +++++++++++++++++++++++++++++++++++++++++++++++ scripts/Dmerge | 66 ++++++++++++++++++++++++ scripts/Journal | 69 ------------------------- 5 files changed, 244 insertions(+), 92 deletions(-) create mode 100755 scripts/Djournal create mode 100755 scripts/Dmerge delete mode 100755 scripts/Journal diff --git a/README.md b/README.md index 895acd9..8eab8f0 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,14 @@ The goal of this program is to provide an implementation of prot's [denote](http This README assumes you already have [plan9port](https://github.com/9fans/plan9port) installed. -### Install ```sh mk install ``` +This installs `Denote` and all its extensions to $HOME/bin. + ### Setup + Start the Denote program by middle-clicking `Denote` anywhere in acme. This will open the `/Denote/` window. Notes are stored in `~/doc` by default, simply edit this in `main.go` to change your denote base directory. ## Usage @@ -26,7 +28,6 @@ In Acme, you can highlight something like: Pass it as input to the `New` tag with the `2-1` chord. This will create a new Acme window with Denote frontmatter and an appropriate file path. **Important:** the actual file is not created until the `Put` command is executed. (Tags are optional but recommended). -#### Create notes in subdirectories You can organize notes into subdirectories using the `/:` separator in the title: ``` 'journal/:today's entry' journal @@ -40,6 +41,10 @@ The text before `/:` becomes the subdirectory path (e.g., `journal/`, `projects/ First, be sure to set up the [plumbing rules](./PLUMBING.md). Then, simply right-click on any identifier in the `/Denote/` window. +### Edit a note + +Edit a note just like any other text file. Use `Put` to save. The Denote metadata will be refreshed automatically. + ### Delete a note To delete a note, highlight its identifier in the `/Denote/` window: @@ -62,25 +67,9 @@ tag1 Highlight this and pass it as input to the `Look` command with the `2-1` chord. This will filter the list of entries to those that match the search query. Executing `Look` without arguments resets the search filter. You may also right-click in the Denote window on titles or tags to jump between matches. -### Journal - -Create or open today's journal: -``` -Journal -``` - -Journal for other times: -``` -Journal +1d # tomorrow -Journal -7d # 7 days ago -Journal +3h # 3 hours from now -Journal -30m # 30 minutes ago -``` - -Supports: `d` (days), `h` (hours), `m` (minutes). You can use this to create multiple entries on the same day. To find an existing journal entry, use the search function in `Denote` described above. You can also pass the args in with the `2-1` chord. - ### Metadata Editing -You may edit metadata directly in the Denote window. + +Quality of life feature: you may edit metadata directly in the Denote window. ``` ------------------------------------------------------- @@ -110,7 +99,7 @@ You may edit metadata directly in the Denote window. Middle-click `Put` to write all metadata changes. This will rename files and, when possible, update front matter. -## Sync +### Sync Sometimes it's necessary to update the Denote metadata from the filesystem. Middle-click `Sync` to do this. @@ -126,3 +115,40 @@ You may change this by changing `ftype` in main.go. ## Encryption Support This program supports encrypted notes by integrating with [acme-crypt](https://github.com/lneely/acme-crypt). Set up the [plumbing rules](./PLUMBING.md) to read encrypted notes, and use `CryptPut` to write them. + +## Extensions + +Some extensions have also been ported. + +### Djournal + +Concept from prot's [denote-journal](https://github.com/protesilaos/denote-journal). While `Djournal` tries to stay as true as possible to the original program and be as feature-complete as possible, it is intentionally *not* an exact replica. + +Create or open today's journal entry (or this week's, month's, or year's, depending on `interval` configuration): + +``` +Djournal +``` + +Supports: `d` (days), `h` (hours), `m` (minutes). You can use this to create multiple entries on the same day. To find an existing journal entry, use the search function in `Denote` described above. You can also pass the args in with the `2-1` chord. + +Configure `Djournal` by editing the configuration options in the `Djournal` script. + +``` +# Journal interval: daily, weekly, monthly, yearly +interval=daily + +# Title format: full, date, day, year +# Examples: +# full: "wednesday 12 november 2025 22:11" +# date: "wednesday 12 november 2025" +# day: "wednesday" +# year: "2025" +titleformat=full + +# Signature (optional, added to filename as ==signature) +signature='' +``` + +### Merge + diff --git a/mkfile b/mkfile index 1ff77ca..ea4fef9 100644 --- a/mkfile +++ b/mkfile @@ -1,6 +1,7 @@ install:V: go build -o $HOME/bin/Denote . - cp scripts/Journal $HOME/bin/Journal + cp scripts/Djournal $HOME/bin/Djournal + cp scripts/Dmerge $HOME/bin/Dmerge clean:V: - rm -f $HOME/bin/Denote $HOME/bin/Journal \ No newline at end of file + rm -f $HOME/bin/Denote $HOME/bin/Djournal $HOME/bin/Dmerge \ No newline at end of file diff --git a/scripts/Djournal b/scripts/Djournal new file mode 100755 index 0000000..11cabb8 --- /dev/null +++ b/scripts/Djournal @@ -0,0 +1,128 @@ +#!/usr/local/plan9/bin/rc + +# Journal interval: daily, weekly, monthly, yearly +interval=daily + +# Title format: full, date, day, year +# Examples: +# full: "wednesday 12 november 2025 22:11" +# date: "wednesday 12 november 2025" +# day: "wednesday" +# year: "2025" +titleformat=full + +# Signature (optional, added to filename as ==signature) +signature='' + +# Parse time offset argument (e.g., +2d, -3h, +15m) +offset=0 +if(! ~ $#* 0) { + arg=$1 + # Extract components using awk + sign=`{echo $arg | awk '{print substr($0,1,1)}'} + num=`{echo $arg | awk '{s=substr($0,2); sub(/[a-z]$/,"",s); print s}'} + unit=`{echo $arg | awk '{print substr($0,length($0))}'} + + # Calculate offset in seconds + switch($unit) { + case d + offset=`{hoc -e 'int('$num' * 86400)'} + case h + offset=`{hoc -e 'int('$num' * 3600)'} + case m + offset=`{hoc -e 'int('$num' * 60)'} + } + + # Apply sign + if(~ $sign '-') { + offset=`{hoc -e 'int(-1 * '$offset')'} + } +} + +# Calculate reference timestamp based on interval +epoch=`{date -n} + +# Adjust epoch to interval start if no offset provided +if(~ $#* 0) { + switch($interval) { + case weekly + # Get start of week (Monday) + # date outputs: Sun Nov 24 07:30:00 EST 2025 + dow=`{date $epoch | awk '{print $1}'} + # Calculate days since Monday (Mon=0, Tue=1, ..., Sun=6) + daysback=0 + switch($dow) { + case Mon; daysback=0 + case Tue; daysback=1 + case Wed; daysback=2 + case Thu; daysback=3 + case Fri; daysback=4 + case Sat; daysback=5 + case Sun; daysback=6 + } + epoch=`{hoc -e 'int('$epoch' - ('$daysback' * 86400))'} + case monthly + # Get start of month (day 1) + # Extract year, month from current date + yearmonth=`{date $epoch | awk '{print $6 " " $2}'} + # Use date with format: Nov 1 2025 + epoch=`{date -n `{echo $yearmonth | awk '{print $2 " 1 " $1}'}} + case yearly + # Get start of year (Jan 1) + year=`{date $epoch | awk '{print $6}'} + epoch=`{date -n 'Jan 1 '^$year} + } +} + +targetepoch=`{hoc -e 'int('$epoch' + '$offset')'} + +# Get formatted date (e.g., "Wednesday 12 November 2025 22:11") +datefull=`{date $targetepoch | awk ' +BEGIN { + m["Jan"]="January"; m["Feb"]="February"; m["Mar"]="March" + m["Apr"]="April"; m["May"]="May"; m["Jun"]="June" + m["Jul"]="July"; m["Aug"]="August"; m["Sep"]="September" + m["Oct"]="October"; m["Nov"]="November"; m["Dec"]="December" + d["Sun"]="Sunday"; d["Mon"]="Monday"; d["Tue"]="Tuesday" + d["Wed"]="Wednesday"; d["Thu"]="Thursday"; d["Fri"]="Friday" + d["Sat"]="Saturday" +} +{ + split($4, t, ":") + print d[$1], $3, m[$2], $6, t[1]":"t[2] +}'} + +# Format title based on titleformat setting +switch($titleformat) { +case full + title=`{echo $datefull | awk '{print tolower($0)}'} +case date + title=`{echo $datefull | awk '{print tolower($1 " " $2 " " $3 " " $4)}'} +case day + title=`{echo $datefull | awk '{print tolower($1)}'} +case year + title=`{echo $datefull | awk '{print $4}'} +case * + # Default to full if invalid format specified + title=`{echo $datefull | awk '{print tolower($0)}'} +} + +# Apply filter +echo 'filter title:'''$"title'''' | 9p write denote/ctl + +# Check if there are any results +results=`{9p read denote/index} + +# If no results, create new journal entry +if(~ $#results 0) { + if(! ~ $signature '') { + echo '''journal/:'$"title'=='^$signature''' journal' | 9p write denote/new + } + if not { + echo '''journal/:'$"title''' journal' | 9p write denote/new + } +} +if not Denote 'denote:'$results(1) + +# Clear filter +echo 'filter' | 9p write denote/ctl diff --git a/scripts/Dmerge b/scripts/Dmerge new file mode 100755 index 0000000..6d1af73 --- /dev/null +++ b/scripts/Dmerge @@ -0,0 +1,66 @@ +#!/usr/local/plan9/bin/rc + +# Merge - Merge source note into destination note +# Usage: Merge + +if(~ $#* 2) { + src=$1 + dst=$2 +} +if not { + echo 'usage: Merge source-id dest-id' >[1=2] + exit 1 +} + +# Read paths and titles +srcpath=`{9p read denote/n/$src/path} +dstpath=`{9p read denote/n/$dst/path} +srctitle=`{9p read denote/n/$src/title} + +if(~ $#srcpath 0 || ~ $#dstpath 0) { + echo 'error: note not found' >[1=2] + exit 1 +} + +# Extract content without frontmatter (everything after first blank line) +awk ' +BEGIN { found_blank = 0 } +found_blank { print } +!found_blank && /^$/ { found_blank = 1 } +' $srcpath > /tmp/content.$pid + +# Append source content to destination with annotation +{ + cat $dstpath + echo '' + echo '* * *' + echo '' + echo 'MERGED FILE:' $srctitle + echo '' + cat /tmp/content.$pid +} > /tmp/merge.$pid + +rm /tmp/content.$pid + +# Update all backlinks pointing to source +9p read denote/n/$src/backlinks | awk -F'|' '{print $1}' | while(read linkid) { + linkid=`{echo $linkid | awk '{gsub(/^ +| +$/, ""); print}'} + if(! ~ $#linkid 0) { + linkpath=`{9p read denote/n/$linkid/path} + if(! ~ $#linkpath 0) { + # Replace denote:src with denote:dst in the file + sed 's/denote:'$src'/denote:'$dst'/g' $linkpath > /tmp/relink.$pid + cp /tmp/relink.$pid $linkpath + rm /tmp/relink.$pid + } + } +} + +# Write merged content to destination +cp /tmp/merge.$pid $dstpath +rm /tmp/merge.$pid + +# Delete source note +echo 'd' | 9p write denote/n/$src/ctl + +echo 'Merged' $src 'into' $dst diff --git a/scripts/Journal b/scripts/Journal deleted file mode 100755 index 7d15530..0000000 --- a/scripts/Journal +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/local/plan9/bin/rc - -# Parse time offset argument (e.g., +2d, -3h, +15m) -offset=0 -if(! ~ $#* 0) { - arg=$1 - # Extract components using awk - sign=`{echo $arg | awk '{print substr($0,1,1)}'} - num=`{echo $arg | awk '{s=substr($0,2); sub(/[a-z]$/,"",s); print s}'} - unit=`{echo $arg | awk '{print substr($0,length($0))}'} - - # Calculate offset in seconds - switch($unit) { - case d - offset=`{hoc -e 'int('$num' * 86400)'} - case h - offset=`{hoc -e 'int('$num' * 3600)'} - case m - offset=`{hoc -e 'int('$num' * 60)'} - } - - # Apply sign - if(~ $sign '-') { - offset=`{hoc -e 'int(-1 * '$offset')'} - } -} - -# Calculate target timestamp -epoch=`{date -n} -targetepoch=`{hoc -e 'int('$epoch' + '$offset')'} - -# Get formatted date (e.g., "Wednesday 12 November 2025 22:11") -datefull=`{date $targetepoch | awk ' -BEGIN { - m["Jan"]="January"; m["Feb"]="February"; m["Mar"]="March" - m["Apr"]="April"; m["May"]="May"; m["Jun"]="June" - m["Jul"]="July"; m["Aug"]="August"; m["Sep"]="September" - m["Oct"]="October"; m["Nov"]="November"; m["Dec"]="December" - d["Sun"]="Sunday"; d["Mon"]="Monday"; d["Tue"]="Tuesday" - d["Wed"]="Wednesday"; d["Thu"]="Thursday"; d["Fri"]="Friday" - d["Sat"]="Saturday" -} -{ - split($4, t, ":") - print d[$1], $3, m[$2], $6, t[1]":"t[2] -}'} - -# Extract date-only part (remove time) and convert to lowercase -dateonly=`{echo $datefull | awk '{print tolower($1 " " $2 " " $3 " " $4)}'} - -# Apply filter - use full datetime if offset arg provided, date-only otherwise -if(! ~ $#* 0) { - # With offset: filter by full datetime for precise matching - filterdate=`{echo $datefull | awk '{print tolower($0)}'} - echo 'filter title:'''$"filterdate'''' | 9p write denote/ctl -} -if not echo 'filter title:'''$"dateonly'''' | 9p write denote/ctl - -# Check if there are any results -results=`{9p read denote/index} - -# If no results, create new journal entry -if(~ $#results 0) { - echo '''journal/:'$"datefull''' journal' | 9p write denote/new -} -if not Denote 'denote:'$results(1) - -# Clear filter -echo 'filter' | 9p write denote/ctl