diff options
| author | Matto Fransen <matto@matto.nl> | 2026-01-07 16:16:43 +0100 |
|---|---|---|
| committer | Matto Fransen <matto@matto.nl> | 2026-01-07 16:16:43 +0100 |
| commit | b38d8daca0c328ac5a33ad79afe16bf0266edf7d (patch) | |
| tree | 0c9e60a30e5b0b8150a22ee4f0f5d99b2da7ed6c | |
| parent | 41440189017b9c81c7e7e4bb369493011fb4d201 (diff) | |
Initial commit
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | README.org | 145 | ||||
| -rw-r--r-- | images/denote-review-frontmatter.png | bin | 0 -> 26532 bytes | |||
| -rw-r--r-- | images/denote-review-tabulated-view.png | bin | 0 -> 204606 bytes | |||
| -rw-r--r-- | 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 e08b918..0000000 --- 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 0000000..30cd552 --- /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 Binary files differnew file mode 100644 index 0000000..0a117a2 --- /dev/null +++ b/images/denote-review-frontmatter.png diff --git a/images/denote-review-tabulated-view.png b/images/denote-review-tabulated-view.png Binary files differnew file mode 100644 index 0000000..e8e1815 --- /dev/null +++ b/images/denote-review-tabulated-view.png diff --git a/my-denote-review.el b/my-denote-review.el new file mode 100644 index 0000000..17676a5 --- /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 <matto@matto.nl> +;; Maintainer: Matto Fransen <matto@matto.nl> +;; 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 |
