This commit is contained in:
@@ -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 PROFILE’s assets dir.
|
||||
|
||||
(defun zq/org-insert-asset-image (&optional profile use-file-prefix)
|
||||
"Insert an Org link to an image chosen from a PROFILE’s 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:
|
||||
|
||||
Reference in New Issue
Block a user