branch: externals/denote-review
commit b38d8daca0c328ac5a33ad79afe16bf0266edf7d
Author: Matto Fransen <[email protected]>
Commit: Matto Fransen <[email protected]>
Initial commit
---
README.md | 3 -
README.org | 145 ++++++++++++++++++
images/denote-review-frontmatter.png | Bin 0 -> 26532 bytes
images/denote-review-tabulated-view.png | Bin 0 -> 204606 bytes
my-denote-review.el | 250 ++++++++++++++++++++++++++++++++
5 files changed, 395 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
deleted file mode 100644
index e08b918073..0000000000
--- a/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# my-denote-review
-
-Implements a simple review process for denote notes.
\ No newline at end of file
diff --git a/README.org b/README.org
new file mode 100644
index 0000000000..30cd5527ec
--- /dev/null
+++ b/README.org
@@ -0,0 +1,145 @@
+* my-denote-review
+
+`my-denote-review' aims to provide a practical and simple manner to
+implement a review process for some of your denote notes.
+
+It is made for notes in org mode format.
+
+`my-denote-review' adds a single line to the frontmatter:
+
+~#+reviewdate: [2024-06-12]~
+
+** Usage
+
+The aim is to implement a process for reviewing your denote notes in a
+practical and simple manner.
+
+Not all notes have to be part of this system. Only notes that contain the
+~#+reviewdate~ field in the frontmatter will be part of it.
+The ~#+reviewdate~ frontmatter can be inserted or updated with a single
+key binding.
+
+To get started, select a number of denote notes in Dired and bulk
+insert the ~#+reviewdate~ frontmatter (see below). Default this
+inserts a review date derived from the identifier in the file name.
+With the Universal Argument (~C-u~) this current date is used as
+review date.
+
+On a regular bases, review some notes:
+
+- Request a list of notes sorted by review date.
+- Review one or more notes and set or update their review date.
+
+*** Add or update the review date of a note
+Open and revew a denote note. Add or update the date in the the
+frontmatter field ~reviewdate~. with the command
+~M-x my-denote-review-set-date~.
+
+To make this easy, bind this command to a key, f.e.:
+
+~define-key global-map (kbd "C-c n x") #'my-denote-review-set-date)~
+
+- When the frontmatter of the note doesn't contain the ~reviewdate~
+ field, it will be inserted, with the current date as review date.
+- When the ~reviewdate~ field is already part of the frontmatter,
+ the review date will be updated with the current date.
+
+Example screenshot of the frontmatter of a note:
+
+#+CAPTION: /Example note showing frontmatter with the latest review date/
+[[./images/denote-review-frontmatter.png]]
+
+*** List notes with a review date
+The command ~M-x my-denote-review-display-list~ creates a tabulated list of the
+notes that contain the ~reviewdate~ frontmatter. Notes without a ~reviewdate~
+are ignored.
+
+The script reads value of the variable ~denote-directory~. When this
+is a list, the script prompts the user to select a directory, using
+completion.
+
+Example screenshot of the list with some notes:
+
+#+CAPTION: /Tabulated list to select a note to review/
+[[./images/denote-review-tabulated-view.png]]
+
+Default the list is ordered by review date, from the oldest to the
+newest. Click on a column header to change the order.
+
+- Click on a column to order according to the column contents.
+- Click again to reverse the order.
+
+Move point up or down to select a note. Open or view the note with
+one of the keys ~RET~, ~e~, or ~o~. Or request a random file from
+the list with ~r~. After editing and/or updating the review date,
+refresh the list with ~g~.
+
+Key bindings in the tabulated list:
+
+- ~RET~ : open and edit the note in another window.
+- ~e~ : open and edit the note.
+- ~o~ : open the note in read only mode in another window.
+- ~r~ : open a random note from the list in another window.
+- ~g~ : update the tabulated list.
+- ~q~ : close the buffer with the tabulated list.
+
+*** Bulk insert a review date according to the identifier
+
+- Open the denote directory in Dired.
+- Mark one or more notes.
+- Issue the command ~M-x my-denote-review-set-date-dired-marked-files~.
+
+This will insert the ~reviewdate~ frontmatter in all selected notes,
+with a date according to the identifier in the filename.
+
+Notes where the ~reviewdate~ field is already part of the
+frontmatter, will be left untouched.
+
+For example, the note
+~20240117T203111--add-a-query-link__demo_denote.org~ will get
+~2024-01-17~ as review date.
+
+*** Bulk insert the current date as review date
+How to bulk insert the current date as review date:
+
+- Open the denote directory in Dired.
+- Mark one or more notes.
+- Enter the Universal Argument ~C-u~
+- Issue the command ~M-x my-denote-review-set-date-dired-marked-files~.
+
+This will insert the ~reviewdate~ frontmatter in all selected notes,
+with the current date as review date.
+
+Notes where the ~reviewdate~ field is already part of the
+frontmatter, will be left untouched.
+
+** ~my-denote-review-max-search-point~
+The custom variable ~my-denote-review-max-search-point~ defines the point
+where the search-forward command stops.
+
+Not all notes have to contain the ~#+reviewdate~ frontmatter.
+
+`my-denote-review' uses the `re-search-forward' command to search
+for the ~#+reviewdate~ frontmatter. To prevent needless searching until the
+end of the file, the command is stopped at the point defined by
+~my-denote-review-max-search-point~.
+
+Default this is set at ~300~.
+
+If you use additional frontmatter fields, or for some other reason
+have a large frontmatter, a higher number might be needed. Set the
+point a few lines below your frontmatter and issue the command
+~C-x =~ to see what a better value for
+~my-denote-review-max-search-point~ could be.
+
+** Source code. bugs and patches
+~my-denote-review~ is developed at
+https://codeberg.org/mattof/my-denote-review
+
+Please use the "Issues" option in the Codeberg repository.
+
+** Distribution
+~my-denote-review.el~ and all other source files in this
+directory are distributed under the GNU Public License, Version 3,
+or any later version.
+
diff --git a/images/denote-review-frontmatter.png
b/images/denote-review-frontmatter.png
new file mode 100644
index 0000000000..0a117a2ce2
Binary files /dev/null and b/images/denote-review-frontmatter.png differ
diff --git a/images/denote-review-tabulated-view.png
b/images/denote-review-tabulated-view.png
new file mode 100644
index 0000000000..e8e1815665
Binary files /dev/null and b/images/denote-review-tabulated-view.png differ
diff --git a/my-denote-review.el b/my-denote-review.el
new file mode 100644
index 0000000000..17676a5356
--- /dev/null
+++ b/my-denote-review.el
@@ -0,0 +1,250 @@
+;;; my-denote-review --- organize notes review -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2025 Matto Fransen
+
+;; Author: Matto Fransen <[email protected]>
+;; Maintainer: Matto Fransen <[email protected]>
+;; Version: 1.0.0
+;; Keywords: writing
+;; Package-Requires: ((emacs "28.0"))
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; `my-denote-review' aims to provide a practical and simple manner to
+;; implement a review process for your denote notes.
+
+;; It is soleley made for denote notes in org mode format with the
+;; default filenaming scheme.
+
+;; `my-denote-review' adds a single line to the frontmatter:
+;; #+reviewdate: [2024-06-12]
+
+;; In tabulated list mode the notes are shown with their last
+;; review date, sorted from oldest to newest review date.
+;; Click on a column header to change the order.
+
+;; See the README for full explanation.
+
+;;; Code:
+
+(require 'denote)
+(defcustom my-denote-review-max-search-point 300
+ "Point to stop `re-search-forward' after some lines."
+ :type 'natnum)
+
+;; Setting and getting the reviewdate
+
+(defun my-denote-review-insert-date (&optional thisdate)
+ "Insert current date as reviewdate.
+Or use THISDATE, when not nil."
+ (goto-char (point-min))
+ (let ((mydate (format-time-string "%F")))
+ (when (not (null thisdate))
+ (setq mydate thisdate))
+ (re-search-forward "^$" nil t)
+ (replace-match (format "#+reviewdate: [%s]" mydate))
+ (newline)))
+
+(defun my-denote-review-set-date ()
+"Set the reviewdate in the current buffer.
+Replace an existing reviewdate."
+(interactive)
+(save-excursion
+ (goto-char (point-min))
+ (if (re-search-forward "^#\\+reviewdate:[ \t][^\t\n]+"
+ my-denote-review-max-search-point t)
+ (replace-match
+ (format "#+reviewdate: [%s]" (format-time-string "%F")))
+ (my-denote-review-insert-date))))
+
+(defun my-denote-review-get-date ()
+ "Get the reviewdate from current buffer."
+ (save-excursion
+ (goto-char (point-min))
+ (when (re-search-forward "\\(^#\\+reviewdate:[
\t]\\[\\)\\([^\t\n]+\\)\\]"
+ my-denote-review-max-search-point t)
+ (match-string-no-properties 2))))
+
+;; Bulk operation, to be run from Dired.
+
+(defun my-denote-review-set-initial-date (thisdate)
+ "Insert reviewdate with THISDATE.
+Only do this when no reviewdate already exist."
+ (when (null (my-denote-review-get-date))
+ (my-denote-review-insert-date thisdate)))
+
+(defun my-denote-review-get-date-from-filename (filename)
+ "Convert identifier in FILENAME into a date."
+ (denote-id-to-date (substring filename 0 15)))
+
+(defun my-denote-review-bulk-set-date (filename current-date-p)
+ "Opens FILENAME and insert a reviewdate.
+When CURRENT-DATE-P is not null, use current date."
+ (let (fpath fname mybuffer len)
+ (setq fpath filename)
+ (setq fname (file-name-nondirectory fpath))
+ (setq mybuffer (find-file fpath))
+ (if (null current-date-p)
+ (my-denote-review-set-initial-date
+ (my-denote-review-get-date-from-filename fname))
+ (my-denote-review-set-initial-date (format-time-string "%F")))
+ (save-buffer)
+ (kill-buffer mybuffer)))
+
+(defun my-denote-review-set-date-dired-marked-files ()
+ "Insert a reviewdate in the marked files.
+Set a reviewdate according the identifier in the filename,
+when called with the Universal Argument use current date.
+Does not overwrite existing reviewdates."
+ (interactive)
+ (if (eq major-mode 'dired-mode)
+ (let ((marked-files (dired-get-marked-files)))
+ (mapcar (lambda (file)
+ (my-denote-review-bulk-set-date file current-prefix-arg))
+ marked-files))
+ (error (format "Command can only be used in a Dired buffer."))))
+
+;; Collect keywords and prompt for a keyword to filter by.
+
+(defun my-denote-review-get-path ()
+ "Prompt for a path when needed."
+ (if (listp denote-directory)
+ (completing-read "Select a directory (using completion): "
+ denote-directory)
+ denote-directory))
+
+(defun my-denote-review-get-keyword-list (denotepath)
+ "Fetch keywords from the filenames in directory DENOTEPATH."
+ (let ((keyword-list '()))
+ (mapc
+ (lambda (myfile)
+ (dolist (mykeyword
+ (denote-extract-keywords-from-path myfile))
+ (add-to-list 'keyword-list mykeyword)))
+ (directory-files denotepath t "\.org$" ))
+ (sort keyword-list)))
+
+(defun my-denote-review-select-keyword ()
+ "Select a keyword or `All' using completion."
+ (let ((denotepath (my-denote-review-get-path)))
+ (cons denotepath
+ (completing-read
+ "Select a keyword (using completion) :"
+ (append (list "All")
+ (my-denote-review-get-keyword-list denotepath))))))
+
+;; Collect data to fill the tabular mode list
+
+(defun my-denote-review-check-date-of-file (myfile)
+ "Get the reviewdate of MYFILE."
+ (let ((mybuffer (find-file myfile))
+ myreviewdate)
+ (setq myreviewdate (my-denote-review-get-date))
+ (kill-buffer mybuffer)
+ myreviewdate))
+
+(defun my-denote-review-collect-files (denotepath-and-keyword)
+ "Fetch reviewdate from the files in DENOTEPATH-AND-KEYWORD.
+Filter filenames according to DENOTEPATH-AND-KEYWORD.
+DENOTEPATH-AND-KEYWORD is a cons of a path and a keyword.
+Create a list in the format required by `tabulated-list-mode'."
+ (let ((list-of-files '()))
+ (save-excursion
+ (mapc
+ (lambda (myfile)
+ (when (or (string= (cdr denotepath-and-keyword) "All")
+ (string-match
+ (format "_%s" (cdr denotepath-and-keyword)) myfile))
+ (let ((reviewdate (my-denote-review-check-date-of-file myfile)))
+ (when (not (null reviewdate))
+ (push (list myfile
+ (vector
+ reviewdate
+ (file-name-nondirectory myfile)))
+ list-of-files)))))
+ (directory-files (car denotepath-and-keyword) t "\.org$" )))
+ (when (null list-of-files)
+ (error (format
+ "No files with a reviewdate found (filter: keyword %s)"
+ (cdr denotepath-and-keyword))))
+ list-of-files))
+
+;; Mode map for tabulated list and actions.
+
+(defun my-denote-review-goto-file ()
+ "Open the selected file in other window.
+Must be called from the tabulated list view."
+ (interactive nil my-denote-review-mode)
+ (find-file-other-window (tabulated-list-get-id)))
+
+(defun my-denote-review-edit-file ()
+ "Open the selected file in other window.
+Must be called from the tabulated list view."
+ (interactive nil my-denote-review-mode)
+ (find-file (tabulated-list-get-id)))
+
+(defun my-denote-review-read-only-goto-file ()
+ "Open the selected file in read-only mode in other window.
+Must be called from the tabulated list view"
+ (interactive nil my-denote-review-mode)
+ (find-file-read-only-other-window (tabulated-list-get-id)))
+
+(defun my-denote-review-goto-random-file ()
+ "Open a random file in other window.
+Must be called from the tabulated list view."
+ (interactive nil my-denote-review-mode)
+ (let ((k (random (length tabulated-list-entries))))
+ (find-file-other-window (car (nth k tabulated-list-entries)))))
+
+(defvar-keymap my-denote-review-mode-map
+ :doc "Keymap for `my-denote-review-mode-map'."
+ :parent tabulated-list-mode-map
+ "RET" #'my-denote-review-goto-file
+ "e" #'my-denote-review-edit-file
+ "o" #'my-denote-review-read-only-goto-file
+ "r" #'my-denote-review-goto-random-file)
+
+;; Tabulated list.
+
+(define-derived-mode my-denote-review-mode
+ tabulated-list-mode
+ "my-denote-review-mode"
+ "Display two-column tabulated list with the reviewdate per file.
+Initially sort by reviewdate."
+ (setq tabulated-list-format
+ [("Reviewdate" 12 t)
+ ("Filename" 60 t)])
+ (setq tabulated-list-sort-key (cons "Reviewdate" nil))
+ (tabulated-list-init-header))
+
+(defun my-denote-review-display-list (denotepath-and-keyword)
+ "Show buffer with reviewdates.
+DENOTEPATH-AND-KEYWORD is a cons of a path and a keyword.
+Filter by keyword."
+ (interactive (list (my-denote-review-select-keyword)))
+ (with-current-buffer (get-buffer-create "*my-denote-review-results*")
+ (my-denote-review-mode)
+ (setq tabulated-list-entries (my-denote-review-collect-files
denotepath-and-keyword))
+ (tabulated-list-print t)
+ (display-buffer (current-buffer))
+ (setq mode-line-buffer-identification
+ (format "*my-denote-review-results* [%s | %s]"
+ (car denotepath-and-keyword)
+ (cdr denotepath-and-keyword)))
+ (force-mode-line-update)))
+
+(provide 'my-denote-review)
+;;; my-denote-review.el ends here