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

Reply via email to