29-05
All checks were successful
Build Roam Site / build (push) Successful in 31s

This commit is contained in:
2026-05-29 08:41:15 +01:00
parent 5d30c8f19e
commit a7285933c3
37 changed files with 1210 additions and 137 deletions

View File

@@ -23,6 +23,172 @@
#+end_src
** Saving images in epdf mode (org roam):
#+begin_src emacs-lisp
;; epdf stuff
(require 'seq)
(require 'subr-x)
(defvar zaine/pdf-image-save-dir
"d:/_nextcloud/master-folder/org_files/org_roam/assets/_books/"
"Folder where cropped PDF images should be saved.")
(defvar zaine/org-roam-root
"d:/_nextcloud/master-folder/org_files/org_roam/"
"Root folder used for relative Org image links.")
(defvar zaine/pdf-image-kill-buffer-after-save t
"If non-nil, close the *PDF image* buffer after saving.")
(defvar zaine/pdf-image-insert-link-into-org t
"If non-nil, insert the image link into a visible Org buffer.")
(defun zaine/pdf-image--slugify (s)
"Turn S into a safe filename slug."
(let* ((s (downcase (string-trim (or s ""))))
(s (replace-regexp-in-string "['`]" "" s))
(s (replace-regexp-in-string "[^[:alnum:]]+" "-" s))
(s (replace-regexp-in-string "-+" "-" s))
(s (replace-regexp-in-string "\\`-+\\|-+\\'" "" s)))
(if (string-empty-p s) "pdf-image" s)))
(defun zaine/pdf-image--current-pdf-title ()
"Try to get the title from the most recently used PDF buffer."
(when-let ((buf
(seq-find
(lambda (b)
(with-current-buffer b
(derived-mode-p 'pdf-view-mode)))
(delq (current-buffer) (buffer-list)))))
(with-current-buffer buf
(if buffer-file-name
(file-name-base buffer-file-name)
(buffer-name buf)))))
(defun zaine/pdf-image--unique-file (dir stem ext)
"Return a unique filename in DIR using STEM and EXT."
(let ((file (expand-file-name (concat stem ext) dir))
(n 1))
(while (file-exists-p file)
(setq file
(expand-file-name
(format "%s-%02d%s" stem n ext)
dir))
(setq n (1+ n)))
file))
(defun zaine/pdf-image--org-link (file)
"Create a relative Org file link for FILE."
(format "[[file:%s]]"
(file-relative-name
(expand-file-name file)
(file-name-as-directory (expand-file-name zaine/org-roam-root)))))
(defun zaine/pdf-image--visible-org-buffer ()
"Find a visible Org buffer, usually your org-noter notes buffer."
(seq-find
(lambda (buf)
(with-current-buffer buf
(derived-mode-p 'org-mode)))
(delete-dups (mapcar #'window-buffer (window-list)))))
(defun zaine/pdf-image--insert-link-into-org (link)
"Insert LINK into a visible Org buffer, if one exists."
(when-let ((buf (zaine/pdf-image--visible-org-buffer)))
(with-current-buffer buf
(goto-char (point))
(unless (bolp)
(insert "\n"))
(insert link "\n")
(when (fboundp 'org-display-inline-images)
(org-display-inline-images)))
t))
(defun zaine/pdf-image--visible-org-window ()
"Find a visible Org window, usually the org-noter notes window."
(seq-find
(lambda (win)
(with-current-buffer (window-buffer win)
(derived-mode-p 'org-mode)))
(window-list nil 'no-minibuf)))
(defun zaine/pdf-image-save-and-insert (&optional ask-title)
"Save the current *PDF image* buffer into `_books` and insert an Org link.
With prefix argument, prompt for the title/name instead of using the PDF filename.
Also closes the temporary *PDF image* window afterwards."
(interactive "P")
(unless (or (derived-mode-p 'image-mode)
(string-match-p "\\`\\*PDF image\\*" (buffer-name)))
(user-error "Run this from the *PDF image* buffer"))
(let* ((image-buffer (current-buffer))
(image-windows (get-buffer-window-list image-buffer nil t))
(org-window (zaine/pdf-image--visible-org-window))
(default-title (or (zaine/pdf-image--current-pdf-title)
"pdf-image"))
(title (if ask-title
(read-string "Image/article name: " default-title)
default-title))
(stem (concat (format-time-string "%Y-%m-%d")
"-"
(zaine/pdf-image--slugify title)))
(file (zaine/pdf-image--unique-file
zaine/pdf-image-save-dir
stem
".png"))
(link (zaine/pdf-image--org-link file)))
(make-directory (file-name-directory file) t)
;; Save the raw image data from the *PDF image* buffer.
(let ((coding-system-for-write 'binary)
(require-final-newline nil))
(with-current-buffer image-buffer
(save-restriction
(widen)
(write-region (point-min) (point-max) file nil 'silent))
(set-buffer-modified-p nil)))
;; Copy the link just in case.
(kill-new link)
;; Insert the link into the visible Org notes window.
(when org-window
(with-selected-window org-window
(unless (bolp)
(insert "\n"))
(insert link "\n")
(when (fboundp 'org-display-inline-images)
(org-display-inline-images))))
;; Close the temporary *PDF image* window instead of letting Emacs reuse it.
(dolist (win image-windows)
(when (window-live-p win)
(ignore-errors
(delete-window win))))
;; Kill the temporary image buffer after its window has gone.
(when (buffer-live-p image-buffer)
(kill-buffer image-buffer))
;; Put focus back on the notes window if it still exists.
(when (and org-window (window-live-p org-window))
(select-window org-window))
(message "Saved %s and inserted Org link"
(file-name-nondirectory file))))
(with-eval-after-load 'image-mode
(define-key image-mode-map (kbd "C-c i p") #'zaine/pdf-image-save-and-insert))
#+end_src
* CALDAV and org agenda calendars:
@@ -91,57 +257,62 @@
* Org image inserter:
#+begin_src emacs-lisp
;;; --- Org image inserter (improved search UX) -----------------------------
(use-package vertico
:init (vertico-mode))
;;; --- Org image inserter (improved search UX) -----------------------------
(use-package vertico
:init (vertico-mode))
(use-package orderless
:custom
(completion-styles '(orderless basic))
(completion-category-defaults nil))
(use-package orderless
:custom
(completion-styles '(orderless basic))
(completion-category-defaults nil))
(defgroup zq/org-assets nil
"Insert image links from predefined asset profiles."
:group 'org)
(defgroup zq/org-assets nil
"Insert image links from predefined asset profiles."
:group 'org)
(defcustom zq/org-asset-profiles
'(("org-roam" . "D:\\_nextcloud\\master-folder\\org_files\\org_roam\\assets")
("org-web" . "D:\\_nextcloud\\master-folder\\org_files\\org_web\\assets"))
"Alist of (PROFILE-NAME . ASSETS-DIR)."
:type '(alist :key-type string :value-type directory)
:group 'zq/org-assets)
(defcustom zq/org-asset-profiles
'(("org-roam" . "D:\\_nextcloud\\master-folder\\org_files\\org_roam\\assets")
("org-web" . "D:\\_nextcloud\\master-folder\\org_files\\org_web\\assets"))
"Alist of (PROFILE-NAME . ASSETS-DIR)."
:type '(alist :key-type string :value-type directory)
:group 'zq/org-assets)
(defcustom zq/org-image-extensions
'("png" "jpg" "jpeg" "gif" "svg" "webp" "mov" "mp4" "mp3")
"File extensions considered when offering candidates."
:type '(repeat string)
:group 'zq/org-assets)
(defcustom zq/org-image-extensions
'("png" "jpg" "jpeg" "gif" "svg" "webp" "mov" "mp4" "mp3")
"File extensions considered when offering candidates."
:type '(repeat string)
:group 'zq/org-assets)
(defun zq/org--image-regexp ()
(concat "\\." (regexp-opt zq/org-image-extensions t) "\\'"))
(defun zq/org--image-regexp ()
(concat "\\." (regexp-opt zq/org-image-extensions t) "\\'"))
(defun zq/org--image-files (dir)
(when (and dir (file-directory-p dir))
(directory-files-recursively dir (zq/org--image-regexp))))
(defun zq/org--image-files (dir)
"Return image/media files in DIR, newest modified first."
(when (and dir (file-directory-p dir))
(sort
(directory-files-recursively dir (zq/org--image-regexp))
#'file-newer-than-file-p)))
(defun zq/org--parse-name (filename)
"Split FILENAME into (DATE . REST)."
(if (string-match "^\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-\\(.*\\)" filename)
(cons (match-string 1 filename)
(match-string 2 filename))
(cons nil filename)))
(defun zq/org--parse-name (filename)
"Split FILENAME into (DATE . REST)."
(if (string-match "^\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)-\\(.*\\)" filename)
(cons (match-string 1 filename)
(match-string 2 filename))
(cons nil filename)))
(defun zq/org--display-name (path)
(let* ((name (file-name-nondirectory path))
(dir (file-name-nondirectory (directory-file-name (file-name-directory path))))
(parsed (zq/org--parse-name name))
(date (car parsed))
(rest (cdr parsed)))
(format "%s (%s | %s)" rest (or date "no-date") dir)))
(defun zq/org--display-name (path)
(let* ((name (file-name-nondirectory path))
(dir (file-name-nondirectory (directory-file-name (file-name-directory path))))
(parsed (zq/org--parse-name name))
(date (car parsed))
(rest (cdr parsed)))
(format "%s (%s | %s)" rest (or date "no-date") dir)))
(defun zq/org--build-candidates (files)
"Return an alist of (DISPLAY . FULLPATH), handling duplicates."
"Return an alist of (DISPLAY . FULLPATH), handling duplicates.
Preserves the order of FILES, so newest files can appear first."
(let ((table (make-hash-table :test 'equal))
result)
(dolist (path files)
@@ -152,49 +323,48 @@
(push (cons (file-name-nondirectory path) path) result)
(puthash display t table)
(push (cons display path) result))))
result))
(nreverse result)))
(defun zq/org-insert-asset-image (&optional profile use-file-prefix)
"Insert an Org link to an image chosen from a PROFILEs assets dir.
(defun zq/org-insert-asset-image (&optional profile use-file-prefix)
"Insert an Org link to an image chosen from a PROFILEs assets dir.
Features:
- Fuzzy search (with Vertico/Orderless/Ivy/Helm)
- Clean names (date stripped)
- Relative paths inserted
Features:
- Fuzzy search (with Vertico/Orderless/Ivy/Helm)
- Clean names (date stripped)
- Relative paths inserted
With prefix arg (C-u), use file: links."
(interactive
(list (completing-read
"Profile: "
(mapcar #'car zq/org-asset-profiles)
nil t nil nil "org-roam")
current-prefix-arg))
(let* ((base-dir (cdr (assoc profile zq/org-asset-profiles)))
(_ (unless (and base-dir (file-directory-p base-dir))
(user-error "Invalid directory for profile '%s'" profile)))
With prefix arg (C-u), use file: links."
(interactive
(list (completing-read
"Profile: "
(mapcar #'car zq/org-asset-profiles)
nil t nil nil "org-roam")
current-prefix-arg))
(let* ((base-dir (cdr (assoc profile zq/org-asset-profiles)))
(_ (unless (and base-dir (file-directory-p base-dir))
(user-error "Invalid directory for profile '%s'" profile)))
(files (or (zq/org--image-files base-dir)
(user-error "No assets found in %s" base-dir)))
(files (or (zq/org--image-files base-dir)
(user-error "No assets found in %s" base-dir)))
(candidates (zq/org--build-candidates files))
(candidates (zq/org--build-candidates files))
;; user selects clean name, we resolve full path
(selected (completing-read "Image: " candidates nil t))
(abs-path (cdr (assoc selected candidates)))
;; user selects clean name, we resolve full path
(selected (completing-read "Image: " candidates nil t))
(abs-path (cdr (assoc selected candidates)))
(here (or (buffer-file-name) default-directory))
(rel (file-relative-name abs-path (file-name-directory here)))
(here (or (buffer-file-name) default-directory))
(rel (file-relative-name abs-path (file-name-directory here)))
(link (format "[[%s%s]]"
(if use-file-prefix "file:" "")
rel)))
(link (format "[[%s%s]]"
(if use-file-prefix "file:" "")
rel)))
(insert link)
(insert link)
(when (derived-mode-p 'org-mode)
(org-display-inline-images t t))))
(when (derived-mode-p 'org-mode)
(org-display-inline-images t t))))
#+end_src
* Weekly org review file: