From cf96c303c1feeb3821a20077bb6ffb80085ccc4e Mon Sep 17 00:00:00 2001 From: Yann Herklotz Date: Sun, 23 Apr 2023 17:55:50 +0100 Subject: Add introduction guide and persistent mapping index --- CHANGELOG.org | 2 + doc/org-zettelkasten-manual.org | 42 ++++++++++++++++++++ org-zettelkasten.el | 87 +++++++++++++++++++++++++++++++++++------ 3 files changed, 120 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.org b/CHANGELOG.org index 1ebca95..edec80f 100644 --- a/CHANGELOG.org +++ b/CHANGELOG.org @@ -6,6 +6,8 @@ - Added =org-zettelkasten-new-topic= to create a new topic file in the current Zettelkasten. +- Added persistent storage for =org-zettelkasten--mapping= in + =org-zettelkasten-mapping-file=. * Org Zettelkasten 0.7.0 (2022-12-27) diff --git a/doc/org-zettelkasten-manual.org b/doc/org-zettelkasten-manual.org index 18fd15d..61befd5 100644 --- a/doc/org-zettelkasten-manual.org +++ b/doc/org-zettelkasten-manual.org @@ -117,6 +117,48 @@ cannot keep track of all the possible links. - =org-zettelkasten-counsel-search-current-id= :: Alternative to =org-zettelkasten-search-current-id= using Ripgrep integration with Counsel. +* Getting Started +:PROPERTIES: +:DESCRIPTION: Functions used and provided to navigate org-zettelkasten links. +:END: +#+cindex: getting-started + +To install =org-zettelkasten=, first make sure that you have the melpa +repository set-up properly. Then, the following =use-package= declaration will +set-up =org-zettelkasten= including useful hooks to activate it when it enters a +zettelkasten file. + +#+begin_src emacs-lisp + (use-package org-zettelkasten + :after org + :config + (org-zettelkasten-setup)) +#+end_src + +Once =org-zettelkasten= is installed properly, then you can run +=org-zettelkasten-new-topic= to create your first topic file. Make sure that +the folder in =org-zettelkasten-directory= exists, which is set to +=~/org-zettelkasten= by default. + +This should open a new file with the name that was given in the prompt, and it +should to start experimenting with adding new notes. In the current file there +should already be a heading with an ID: + +#+begin_src org-mode + #+title: + + * First Note + :PROPERTIES: + :CUSTOM_ID: 1a + :END: +#+end_src + +Placing the point anywhere underneath the heading =* First Note=, and pressing +=C-c y n= will create a next note with a new ID, and jump to it. Going back to +=* First Note= and pressing the keybinding again will create another note, but +this time it is nested in the current note (branching) because there is already +a next note present. + * GNU Free Documentation License :PROPERTIES: :APPENDIX: t diff --git a/org-zettelkasten.el b/org-zettelkasten.el index 68582ae..30632ef 100644 --- a/org-zettelkasten.el +++ b/org-zettelkasten.el @@ -43,12 +43,6 @@ :type 'string :group 'org-zettelkasten) -(defcustom org-zettelkasten-mapping nil - "Main zettelkasten directory." - :type '(alist :key-type (natnum :tag "Value") - :value-type (string :tag "File name")) - :group 'org-zettelkasten) - (defcustom org-zettelkasten-prefix [(control ?c) ?y] "Prefix key to use for Zettelkasten commands in Zettelkasten minor mode. The value of this variable is checked as part of loading Zettelkasten mode. @@ -56,6 +50,70 @@ After that, changing the prefix key requires manipulating keymaps." :type 'key-sequence :group 'org-zettelkasten) +(defcustom org-zettelkasten-mapping-file + (expand-file-name "org-zettelkasten-index" org-zettelkasten-directory) + "The file which contains mappings from indices to file-names." + :type 'string + :group 'org-zettelkasten) + +(defvar org-zettelkasten--mapping :unset + "Main mapping from indices to filenames in the Zettelkasten directory.") + +(defun org-zettelkasten--read-mapping () + "Initialize `org-zettelkasten--mapping' using the contents of +`org-zettelkasten-mapping-file'." + (let ((filename org-zettelkasten-mapping-file)) + (setq org-zettelkasten--mapping + (when (file-exists-p filename) + (with-temp-buffer + (insert-file-contents filename) + (read (current-buffer))))) + (unless + (seq-every-p + (lambda (elt) + (and (numberp (car-safe elt)) (stringp (cdr-safe elt)))) + org-zettelkasten--mapping) + (warn "Contents of %s are in wrong format, resetting" filename) + (setq org-zettelkasten--mapping :unset)))) + +(defun org-zettelkasten--ensure-read-mapping () + "Initialise `org-zettelkasten--mapping' if it is not yet initialised." + (when (eq org-zettelkasten--mapping :unset) + (org-zettelkasten--read-mapping))) + +(defun org-zettelkasten--write-mapping () + "Save `org-zettelkasten--mapping' in `org-zettelkasten-mapping-file'." + (let ((filename org-zettelkasten-mapping-file)) + (with-temp-buffer + (insert ";;; -*- lisp-data -*-\n") + (let ((print-length nil) + (print-level nil)) + (pp org-zettelkasten--mapping (current-buffer))) + (write-region nil nil filename nil 'silent)))) + +(defun org-zettelkasten--add-topic (index file-name &optional no-write) + "Add a topic to `org-zettelkasten--mapping' and optionally save it to disk. +INDEX is the new index of the topic, it should not appear in +`org-zettelkasten--mapping' yet. FILE-NAME is the file name of +the topic to be added. NO-WRITE is an optional flag to to +control whether the mapping should be saved to the file." + (org-zettelkasten--ensure-read-mapping) + (if (and (numberp index) (stringp file-name)) + (add-to-list 'org-zettelkasten--mapping `(,index . ,file-name)) + (warn "Adding topics in wrong format")) + (unless no-write + (org-zettelkasten--write-mapping))) + +(defun org-zettelkasten--remove-topic (index &optional no-write) + "Remove a topic from `org-zettelkasten--mapping' given by INDEX. +Optionally, if NO-WRITE is set, write the new mapping to the +file." + (org-zettelkasten--ensure-read-mapping) + (setq org-zettelkasten--mapping + (assq-delete-all index org-zettelkasten--mapping)) + (unless no-write + (org-zettelkasten--write-mapping))) + (defun org-zettelkasten--abs-file (file) "Return FILE name relative to `org-zettelkasten-directory'." (expand-file-name file org-zettelkasten-directory)) @@ -67,11 +125,13 @@ This function assumes that IDs will start with a number." (when (string-match "^\\([0-9]*\\)" ident) (string-to-number (match-string 1 ident)))) +;;;###autoload (defun org-zettelkasten-goto-id (id) "Go to an ID automatically." (interactive "sID: #") + (org-zettelkasten--ensure-read-mapping) (let ((file (alist-get (org-zettelkasten--ident-prefix id) - org-zettelkasten-mapping))) + org-zettelkasten--mapping))) (org-link-open-from-string (concat "[[file:" (org-zettelkasten--abs-file file) "::#" id "]]")))) @@ -153,8 +213,9 @@ NEWHEADING: function used to create the heading and set the current POINT to (defun org-zettelkasten--all-files () "Return all files in the Zettelkasten with full path." + (org-zettelkasten--ensure-read-mapping) (mapcar #'org-zettelkasten--abs-file - (mapcar #'cdr org-zettelkasten-mapping))) + (mapcar #'cdr org-zettelkasten--mapping))) (defun org-zettelkasten-buffer () "Check if the current buffer belongs to the Zettelkasten." @@ -175,27 +236,31 @@ adds `org-zettelkasten--update-modified' to buffer local nil 'local) (org-zettelkasten-mode))))) +;;;###autoload (defun org-zettelkasten-search-current-id () "Search for references to ID in `org-zettelkasten-directory'." (interactive) (let ((current-id (org-entry-get nil "CUSTOM_ID"))) (lgrep (concat "[:[]." current-id "]") "*.org" org-zettelkasten-directory))) +;;;###autoload (defun org-zettelkasten-agenda-search-view () "Search for text using Org agenda in Zettelkasten files." (interactive) (let ((org-agenda-files (org-zettelkasten--all-files))) (org-search-view))) +;;;###autoload (defun org-zettelkasten-new-topic (file-name) "Create a new topic in a file named FILE-NAME." (interactive "sNew Topic Filename: ") + (org-zettelkasten--ensure-read-mapping) (let ((new-id - (if org-zettelkasten-mapping + (if org-zettelkasten--mapping (1+ (apply #'max (mapcar (lambda (val) (car val)) - org-zettelkasten-mapping))) + org-zettelkasten--mapping))) 1))) - (add-to-list 'org-zettelkasten-mapping `(,new-id . ,file-name)) + (org-zettelkasten--add-topic new-id file-name) (find-file file-name) (insert (format "#+title: -- cgit