分享一下用双链管理文章或者其他 node 的配置~
最近要读很多文献,需要做一些简练的笔记,并且对这些文献进行分类,所以想到了用双链来管理这些文献和笔记。
例如我当前调研的方向是 A 综述,那么我会创建一个文件 a-survey.org ,内容大致如下
* Content
A is ...
A is not ...
A has the following characteristics ...
* Categories
** PAPER TAG A-1
:PROPERTIES:
:ID: xxxx
:END:
** PAPER TAG A-2
:PROPERTIES:
:ID: xxxx
:END:
* Papers
** PAPER T1
[[id:yyyy][Paper T1 Short Name]]
这篇文章 T1 可以通过自定义的 org-roam-capture 从 zotero 经过 zotxt 拉过来,会放到对应的文件夹里
:PROPERTIES:
:ID: yyyy
:ZOTERO_LINK: [[zotero://select/items/1_xxx][@zotero-cite-key]]
:ROAM_ALIASES: Short Title of Paper T1
:END:
#+TITLE: PAPER: Efficient Item ID Generation for Large-Scale LLM-based Recommendation
* Meta Info
[[id:xxxx][PAPER TAG A-1]]
这样,在逻辑上就有了这样的一个链接图,将分类这些 tag 直接体现在了链接图中,可以方便的通过 org-roam-ui 进行观察。
survey
| - PAPER TAG A-1
| | - PAPER T1
| - PAPER TAG A-2
...
这里是一个可视化的结果:
此外,还有个点是可以自定义 org-export-latex 对链接的处理,如果发现插入的链接带有 ZOTERO_LINK 这个属性,那么就通过 zotxt 的 API 去查询实际的 bibkey ,省去了手动维护 bib 文件的麻烦。
最后附上代码,小白代码请轻喷~
:desc "org roam capture paper" "p" #'jump-to-zotxt-note-by-search
(defun jump-to-zotxt-note-by-search ()
"Go to Zotero note via searching. Create the note file if it does not exist"
(interactive)
(deferred:$
;; step1: search for Zotero items
(zotxt-search-deferred :title-creator-year)
(deferred:nextc it
(lambda (items)
(when (null items)
(error "No Zotero items found."))
(car items)))
;; step2: get the full item details
(deferred:nextc it
(lambda (item)
(zotxt-get-item-deferred item :full)))
;; step3: generate note file path and pass context
(deferred:nextc it
(lambda (full-item)
(let* ((item-key (plist-get full-item :key))
(note-directory org-zotxt-notes-directory)
(note-file (concat note-directory item-key ".org")))
(make-directory note-directory t)
(cons full-item note-file))))
;; step4: create or open the note file
(deferred:nextc it
(lambda (full-item-note-file)
(let* (
(full-item (car full-item-note-file))
(note-file (cdr full-item-note-file))
(json-object-type 'hash-table)
(json-array-type 'list)
(json-key-type 'string)
(json-data (json-read-from-string (plist-get full-item :full)))
(item-data (if (listp json-data) (car json-data) json-data))
(item-title (gethash "title" item-data))
)
(make-directory org-zotxt-notes-directory t)
(if (file-exists-p note-file)
(find-file note-file)
(with-temp-file note-file
(insert (format "#+TITLE: %s\n" (concat "PAPER: " item-title)))
(insert (format "#+DATE: %s\n" (format-time-string "%Y-%m-%d %H:%M")))
(insert "* Meta Info\n")
))
(find-file note-file)
(setq org-roam-zotero-note--file-name note-file)
full-item
)
))
;; step5: get the properties and paths
(deferred:nextc it
(lambda (item)
(zotxt-get-item-deferred item :paths)))
(deferred:nextc it
(lambda (item)
;; (message (prin1-to-string item))
(org-zotxt-get-item-link-text-deferred item)))
(deferred:nextc it
(lambda (resp)
(find-file org-roam-zotero-note--file-name)
(goto-char (point-min))
(if-let ((id (org-entry-get (point) "ID")))
(message "id found in the DB, jump to it directly")
(progn
(org-entry-put (point) "ID" (org-id-new))
(org-entry-put (point) org-zotxt-noter-zotero-link (org-zotxt-make-item-link resp))
(org-roam-alias-add
(cdr (assq 'title-short
(aref (json-read-from-string (plist-get resp :full)) 0))))
(org-roam-db-sync)
)
)
))
;; step6: error handling
(deferred:error it
(lambda (err)
(message "Zotxt Failed for : %s" (error-message-string err))
;; (signal 'user-error (list "Canceled"))
))
))
(defun extract-zotero-link-from-path (zotero-link-with-desc)
;; "[[zotero://select/items/1_BYJ73MHG][@llama3-grattafioriLlama3Herd2024]]"
;; "[[zotero://select/items/1_BYJ73MHG]]"
(if (string-match "\\(zotero://[^]]+\\)" zotero-link-with-desc)
(match-string 1 zotero-link-with-desc)
nil)
)
(defun extract-zotero-desc-from-path (zotero-link-with-desc)
"Extract description from Zotero link.
Example:
\"[[zotero://select/items/1_BYJ73MHG][@llama3-grattafioriLlama3Herd2024]]\"
-> \"@llama3-grattafioriLlama3Herd2024\"
If no description exists (e.g. \"[[zotero://select/items/1_BYJ73MHG]]\"),
return nil."
(when (string-match "\\[\\[zotero://.*\\]\\[\\(.*\\)\\]\\]" zotero-link-with-desc)
(match-string 1 zotero-link-with-desc)))
(defun org-id-of-zotero-note-export-maybe (path desc format)
"Export function for org-id links that may contain Zotero links."
(when (eq format 'latex)
(let ((file-name (car (org-roam-id-find path))))
(if (and file-name (file-exists-p file-name))
(with-current-buffer (find-file-noselect file-name)
(save-excursion
(goto-char (point-min))
(let* (
(zotero-link-prop (org-entry-get (point) org-zotxt-noter-zotero-link))
)
(if zotero-link-prop
(progn
(org-zotxt--link-export (substring (extract-zotero-link-from-path zotero-link-prop) 8) (or (extract-zotero-desc-from-path zotero-link-prop) desc) format)
)
nil
))
))
nil
)))
)
(defun org-zotxt-get-cite-key-from-zotero-id (zurl)
(let* ((zurl-id (substring zurl 22))
(response (request
(format "%s/items" zotxt-url-base)
:params `(("key" . ,zurl-id)
("format" . "citekey"))
:sync t
:parser 'json-read))
(data (request-response-data response))
(result (if (and (vectorp data) (> (length data) 0))
(aref data 0)
(error "Unexpected response: empty or not a vector"))))
(message "Citation key: %s" result)
result))
(defun org-zotero-update-citekey-inline (&optional filename)
"Update the Zotero citekey of the current org file."
(interactive)
(let ((file (or filename (buffer-file-name))))
(unless file
(error "No file is associated with this buffer"))
(message "Updating Zotero citekey for file: %s" file)
(with-current-buffer (find-file-noselect file)
(save-excursion
(goto-char (point-min))
(let* ((zurl-with-desc (org-entry-get nil org-zotxt-noter-zotero-link))
(zurl (when zurl-with-desc
(extract-zotero-link-from-path zurl-with-desc)))
(cite-key (when zurl
(org-zotxt-get-cite-key-from-zotero-id zurl)))
(new-link (when cite-key
(org-link-make-string zurl (concat "@" cite-key)))))
(if cite-key
(progn
(message "Zotero link: %s" zurl)
(message "Cite key: %s" cite-key)
(org-entry-put nil org-zotxt-noter-zotero-link new-link)
(save-buffer)
(message "Citekey updated to: %s" cite-key))
(message "No cite-key found for Zotero link: %s" zurl)))))))
(add-hook 'org-zotxt-mode-hook
(lambda ()
(progn
(org-link-set-parameters "zotero" :follow #'org-zotero-open-via-macos :export #'org-zotxt--link-export)
(org-link-set-parameters "id" :follow #'org-id-open :store #'org-id-store-link-maybe :export #'org-id-of-zotero-note-export-maybe)
)
))
