├── .gitignore ├── docs ├── img │ ├── screenshot.png │ └── zetteldeft-graph.jpg ├── static │ ├── fonts │ │ ├── IBMPlexMono-Text.otf │ │ ├── IBMPlexSans-Bold.otf │ │ ├── IBMPlexSans-Regular.otf │ │ ├── Merriweather-Black.otf │ │ ├── Merriweather-Italic.otf │ │ └── Merriweather-Regular.otf │ ├── html-preamble.html │ └── style.css ├── CONTRIBUTING.html ├── README.html └── index.html ├── CONTRIBUTING.org ├── doc-export.org ├── README.org ├── introduction.org ├── LICENSE └── zetteldeft.el /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pdf 3 | *.tex -------------------------------------------------------------------------------- /docs/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/img/screenshot.png -------------------------------------------------------------------------------- /docs/img/zetteldeft-graph.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/img/zetteldeft-graph.jpg -------------------------------------------------------------------------------- /docs/static/fonts/IBMPlexMono-Text.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/IBMPlexMono-Text.otf -------------------------------------------------------------------------------- /docs/static/fonts/IBMPlexSans-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/IBMPlexSans-Bold.otf -------------------------------------------------------------------------------- /docs/static/fonts/IBMPlexSans-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/IBMPlexSans-Regular.otf -------------------------------------------------------------------------------- /docs/static/fonts/Merriweather-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/Merriweather-Black.otf -------------------------------------------------------------------------------- /docs/static/fonts/Merriweather-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/Merriweather-Italic.otf -------------------------------------------------------------------------------- /docs/static/fonts/Merriweather-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EFLS/zetteldeft/HEAD/docs/static/fonts/Merriweather-Regular.otf -------------------------------------------------------------------------------- /docs/static/html-preamble.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.org: -------------------------------------------------------------------------------- 1 | #+title: Contributing to Zetteldeft 2 | 3 | Comments and contributions to Zetteldeft are more than welcome. 4 | 5 | Reach out [[https://mastodon.social/@EFLS][via Mastodon]], file an issue over [[https://github.com/EFLS/zetteldeft][on Github]], or submit a pull request to get your code included. 6 | 7 | If you want to contribute code, please keep in mind that Zetteldeft is maintained in the =zetteldeft.org= file. 8 | The =zetteldeft.el= is then created from the =zetteldeft.org= -- or, in Org-mode jargon, "tangled". 9 | 10 | Please respect this order of things if you can, by making changes to the =.org= file (rather than to the =.el= file directly). 11 | Alter the code in the source blocks, or create new source blocks like so: 12 | 13 | #+begin_src org-mode 14 | #+begin_src emacs-lisp 15 | (message "Code goes here") 16 | #+end_src 17 | #+end_src 18 | 19 | You can add some documentation if you like, but this is not necessary. 20 | 21 | If you run into any issue, don't hesitate to reach out. 22 | -------------------------------------------------------------------------------- /doc-export.org: -------------------------------------------------------------------------------- 1 | #+title: Documentation Export 2 | #+subtitle: Setup for Zetteldeft documentation export 3 | #+author: EFLS 4 | 5 | Let's use the power of =org-publish= to our advantage. 6 | 7 | #+BEGIN_SRC emacs-lisp :results silent 8 | (add-to-list 'org-publish-project-alist 9 | `("zetteldeft-doc" 10 | ; default pages 11 | :base-directory ,default-directory 12 | :base-extension "org" 13 | :publishing-directory ,(expand-file-name "docs" default-directory) 14 | :publishing-function org-html-publish-to-html 15 | ; exclude setup files (regexp) 16 | :exclude "doc-export\.org" 17 | ; org config 18 | :headline-levels 4 19 | :with-tags nil 20 | :with-toc nil 21 | :with-date nil 22 | ; html head 23 | :html-head-extra "" 24 | ; pre- and postamble 25 | :html-preamble ,(with-temp-buffer 26 | (insert-file-contents 27 | (expand-file-name "docs/static/html-preamble.html" 28 | default-directory)) 29 | (buffer-string)) 30 | :html-postamble ,(with-temp-buffer 31 | (insert-file-contents 32 | (expand-file-name "docs/static/html-preamble.html" 33 | default-directory)) 34 | (buffer-string)))) 35 | #+END_SRC 36 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+title: Zetteldeft 2 | #+author: EFLS 3 | #+HTML_HEAD: 4 | 5 | [[http://melpa.org/#/zetteldeft][file:http://melpa.org/packages/zetteldeft-badge.svg]] 6 | 7 | ------ 8 | *UPDATE 2023*: Zetteldeft is no longer in active development, as its main author is moving away from a note-taking system based on Deft towards one based on Denote. 9 | The Emacs package and this repository will remain available, but might no longer receive updates. 10 | ------ 11 | 12 | The =zetteldeft.org= file in this repository contains documented code for a set of functions for =emacs=, which aims to extend the =deft= package and turn it into a (very very) basic Zettelkasten note-taking system. 13 | 14 | The best way to get to know Zetteldeft, is from within Zetteldeft. 15 | Get started by cloning the =zd-tutorial= repository, available at https://github.com/EFLS/zd-tutorial. 16 | Bootstrap =use-package= installation instructions included. 17 | 18 | If you'd rather start with an introduction of key concepts and basic functions, check out an introduction here: [[https://efls.github.io/zetteldeft/][efls.github.io/zetteldeft]]. 19 | 20 | Documentation, literate code and further technical details can be found in the =.org= file of this repository, or can be read in a more convenient format over at [[https://efls.github.io/zetteldeft/zetteldeft.html][efls.github.io/zetteldeft/zetteldeft.html]]. 21 | 22 | =zetteldeft= is available on [[https://melpa.org/#/zetteldeft][MELPA]]. 23 | If you use =use-package=, installing is as easy as 24 | 25 | #+begin_src emacs-lisp 26 | (use-package zetteldeft 27 | :after deft 28 | :config 29 | (zetteldeft-set-classic-keybindings)) 30 | #+end_src 31 | 32 | Please note that this was originally written for personal use and that I'm far from an =emacs lisp= expert. 33 | That said, contributions and feedback are more than welcome. 34 | -------------------------------------------------------------------------------- /docs/static/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | This stylesheet is based on Kungsgeten's personal page, 3 | and borrows some more from Hugo theme 'one'. 4 | */ 5 | 6 | /* ----- */ 7 | /* Fonts */ 8 | /* ----- */ 9 | 10 | @font-face { 11 | font-family: 'merriweather'; 12 | src: url(fonts/Merriweather-Regular.otf); 13 | font-style: normal; 14 | font-weight: 100; 15 | } 16 | 17 | @font-face { 18 | font-family: 'merriweather'; 19 | src: url(fonts/Merriweather-Italic.otf); 20 | font-style: italic; 21 | font-weight: 100; 22 | } 23 | 24 | @font-face { 25 | font-family: 'merriweather'; 26 | src: url(fonts/Merriweather-Black.otf); 27 | font-weight: bold; 28 | } 29 | 30 | @font-face { 31 | font-family: 'sans'; 32 | src: url(fonts/IBMPlexSans-Regular.otf); 33 | font-style: normal; 34 | } 35 | 36 | @font-face { 37 | font-family: 'sans'; 38 | src: url(fonts/IBMPlexSans-Bold.otf); 39 | font-style: normal; 40 | font-weight: bold; 41 | } 42 | 43 | @font-face { 44 | font-family: 'sans-light'; 45 | src: url(fonts/IBMPlexSans-Regular.otf); 46 | font-style: normal; 47 | } 48 | 49 | @font-face { 50 | font-family: 'mono'; 51 | src: url(fonts/IBMPlexMono-Text.otf); 52 | font-style: normal; 53 | font-weight: 100; 54 | } 55 | 56 | /* 57 | BODY 58 | */ 59 | 60 | html { 61 | padding: 0; 62 | margin: 0; 63 | } 64 | body { 65 | margin: 0; 66 | padding: 0; 67 | font-family: 'merriweather', serif; 68 | font-size: 14px; 69 | line-height: 1.5; 70 | color: #21211a; 71 | background-color: #f3f3e7; 72 | } 73 | @media screen and (min-width: 530px) { 74 | body { 75 | font-size: 20px; 76 | } 77 | } 78 | 79 | p { 80 | margin-top: 20px; 81 | } 82 | 83 | p + p { 84 | margin-top: 10px; 85 | /* text-indent: 10em; */ 86 | } 87 | 88 | section { 89 | margin-top: 1em; 90 | } 91 | section p { 92 | margin: 0.15em 0; 93 | } 94 | section p + p { 95 | text-indent: 1.5em; 96 | } 97 | 98 | /* Quotes */ 99 | blockquote { 100 | border-left: 3px solid #0594cb; 101 | text-indent: 0em; 102 | margin-left: 1em; 103 | padding-left: 1em; 104 | } 105 | 106 | /* Heading 1 - Title */ 107 | h1 { 108 | font-family: 'sans', sans-serif; 109 | font-size: 200%; 110 | font-weight: bold; 111 | line-height: 1.1; 112 | text-align: left; 113 | /* Margins & padding */ 114 | margin-top: 1rem; 115 | margin-bottom: 1em; 116 | padding-top: 1.5em; 117 | } 118 | .title { 119 | margin-top: 0rem; 120 | margin-bottom: 10pt; 121 | } 122 | .subtitle { 123 | font-family: 'sans', sans-serif; 124 | font-weight: normal; 125 | font-size: 120%; 126 | margin-bottom: 50px; 127 | } 128 | 129 | /* Heading 2 */ 130 | h2 { 131 | font-family: 'sans', sans-serif; 132 | font-size: 130%; 133 | line-height: 1.1; 134 | hyphens: none; 135 | /* Margins & padding */ 136 | margin-top: 2.5rem; 137 | margin-bottom: 0.5rem; 138 | padding-top: 0.02em; 139 | /* With small border */ 140 | border-bottom: 2px solid #d7d7cd; 141 | margin-bottom: 0.5em; 142 | } 143 | 144 | /* Other headings */ 145 | h3, 146 | h4 { 147 | font-family: 'sans', sans-serif; 148 | } 149 | h3 { 150 | margin-top: 1.5em; 151 | margin-bottom: 0.2rem; 152 | } 153 | 154 | header { 155 | margin: 10px; 156 | } 157 | 158 | /* Captions */ 159 | 160 | figcaption { 161 | font-family: 'sans-light', sans-serif; 162 | font-size: 80%; 163 | text-align: center; 164 | } 165 | 166 | /* Table */ 167 | caption.t-above { 168 | font-family: sans; 169 | } 170 | 171 | .table-number{ 172 | display: none; 173 | } 174 | 175 | /* Links */ 176 | a { 177 | color: #21211a; 178 | } 179 | .org-ul { 180 | list-style-type: '- '; 181 | padding-left: 1.5em; 182 | } 183 | a:hover { 184 | background-color: #d3d3a8; 185 | } 186 | #content { 187 | margin: auto; 188 | margin-bottom: 1.5em; 189 | padding: 0 0.5em; 190 | } 191 | @media screen and (min-width: 460px) { 192 | #content { 193 | padding: 0 2em; 194 | } 195 | } 196 | @media screen and (min-width: 530px) { 197 | #content { 198 | max-width: 750px; 199 | } 200 | } 201 | /* TOC */ 202 | #table-of-contents a { 203 | text-decoration: none; 204 | font-family: 'sans-light', serif; 205 | } 206 | #table-of-contents ul { 207 | font-family: 'sans', serif; 208 | list-style-type: none; 209 | padding-left: 1em; 210 | } 211 | pre, 212 | code { 213 | font-family: 'mono', monospace; 214 | } 215 | pre { 216 | font-size: 60%; 217 | background-color: #fff; 218 | padding: 3pt; 219 | margin: 1.2em 0em; 220 | } 221 | @media screen and (min-width: 460px) { 222 | pre { 223 | margin: 1.2em -2.3em; 224 | } 225 | } 226 | code { 227 | font-size: 100%; 228 | background-color: #e6e6da; 229 | border: 1px solid #ccc; 230 | padding-left: 3px; 231 | padding-right: 3px; 232 | } 233 | table { 234 | margin: auto; 235 | margin-top: 1em; 236 | margin-bottom: 1em; 237 | } 238 | table + table { 239 | margin-top: 2em; 240 | } 241 | th, 242 | td { 243 | padding: 0 0.25em; 244 | } 245 | tr:nth-child(odd) { 246 | background-color: #f2f2f2; 247 | } 248 | 249 | /* Tables */ 250 | 251 | th { 252 | background-color: #dadada; 253 | padding-left: 10px; 254 | padding-right: 10px; 255 | border-bottom: 2px solid; 256 | border-top: 2px solid; 257 | } 258 | 259 | .underline { 260 | font-family: 'sans', sans-serif; 261 | text-transform: lowercase; 262 | letter-spacing: 0.5px; 263 | text-decoration: none; 264 | } 265 | #preamble, 266 | #postamble { 267 | background-color: #e3e3c7; 268 | padding: 3px 0px; 269 | } 270 | #head { 271 | margin: auto; 272 | } 273 | #head a { 274 | text-decoration: none; 275 | padding-right: 10px; 276 | padding-left: 10px; 277 | } 278 | @media screen and (min-width: 530px) { 279 | #head { 280 | max-width: 750px; 281 | } 282 | } 283 | #head >h1 { 284 | font-size: 120%; 285 | font-weight: normal; 286 | display: inline; 287 | } 288 | #head >ul { 289 | display: inline; 290 | list-style-type: none; 291 | margin: 0; 292 | padding: 0; 293 | } 294 | #head >ul li { 295 | display: inline; 296 | } 297 | sup { 298 | vertical-align: baseline; 299 | position: relative; 300 | top: -0.4em; 301 | } 302 | 303 | /* --------- */ 304 | /* Footnotes */ 305 | /* --------- */ 306 | 307 | /* Footnote header */ 308 | .footnotes { 309 | font-family: 'sans', sans-serif; 310 | font-size: 100%; 311 | border-bottom: none; 312 | border-top: 2px solid #d7d7cd; 313 | padding: 0.5rem 0 314 | } 315 | 316 | /* Full footnote */ 317 | .footdef { 318 | margin-left: 1rem; 319 | text-indent: -1rem; 320 | margin-bottom: 1em; 321 | } 322 | 323 | /* Footnote references & numbers */ 324 | .footref::before, 325 | .footnum::before { 326 | content: ""; 327 | } 328 | .footref::after, 329 | .footnum::after { 330 | content: ""; 331 | } 332 | 333 | figure { 334 | margin: 2em 1em; 335 | } 336 | 337 | img { 338 | max-width: 100%; 339 | height:auto; 340 | } 341 | 342 | /* Figure numbers */ 343 | .figure-number { 344 | display: none; 345 | } 346 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Contributing to Zetteldeft 8 | 9 | 10 | 194 | 195 | 196 | 197 |
198 | 208 |
209 |
210 |
211 |

Contributing to Zetteldeft

212 |

213 | Comments and contributions to Zetteldeft are more than welcome. 214 |

215 | 216 |

217 | Reach out via Mastodon, file an issue over on Github, or submit a pull request to get your code included. 218 |

219 | 220 |

221 | If you want to contribute code, please keep in mind that Zetteldeft is maintained in the zetteldeft.org file. 222 | The zetteldeft.el is then created from the zetteldeft.org – or, in Org-mode jargon, "tangled". 223 |

224 | 225 |

226 | Please respect this order of things if you can, by making changes to the .org file (rather than to the .el file directly). 227 | Alter the code in the source blocks, or create new source blocks like so: 228 |

229 | 230 |
231 |
#+begin_src emacs-lisp
232 | (message "Code goes here")
233 | 
234 |
235 |

236 | #+end_src 237 |

238 | 239 |

240 | You can add some documentation if you like, but this is not necessary. 241 |

242 | 243 |

244 | If you run into any issue, don’t hesitate to reach out. 245 |

246 |
247 |
248 | 258 |
259 | 260 | -------------------------------------------------------------------------------- /docs/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Zetteldeft 8 | 9 | 10 | 194 | 195 | 196 | 197 | 198 |
199 | 209 |
210 |
211 |
212 |

Zetteldeft

213 |
214 |
215 | zetteldeft-badge.svg 216 | 217 |
218 | 219 |
220 |

221 | UPDATE 2023: Zetteldeft is no longer in active development, as its main author is moving away from a note-taking system based on Deft towards one based on Denote. 222 | The Emacs package and this repository will remain available, but might no longer receive updates. 223 |

224 |
225 | 226 |

227 | The zetteldeft.org file in this repository contains documented code for a set of functions for emacs, which aims to extend the deft package and turn it into a (very very) basic Zettelkasten note-taking system. 228 |

229 | 230 |

231 | The best way to get to know Zetteldeft, is from within Zetteldeft. 232 | Get started by cloning the zd-tutorial repository, available at https://github.com/EFLS/zd-tutorial. 233 | Bootstrap use-package installation instructions included. 234 |

235 | 236 |

237 | If you’d rather start with an introduction of key concepts and basic functions, check out an introduction here: efls.github.io/zetteldeft. 238 |

239 | 240 |

241 | Documentation, literate code and further technical details can be found in the .org file of this repository, or can be read in a more convenient format over at efls.github.io/zetteldeft/zetteldeft.html. 242 |

243 | 244 |

245 | zetteldeft is available on MELPA. 246 | If you use use-package, installing is as easy as 247 |

248 | 249 |
250 |
(use-package zetteldeft
251 |   :after deft
252 |   :config
253 |     (zetteldeft-set-classic-keybindings))
254 | 
255 |
256 | 257 |

258 | Please note that this was originally written for personal use and that I’m far from an emacs lisp expert. 259 | That said, contributions and feedback are more than welcome. 260 |

261 |
262 |
263 | 273 |
274 | 275 | -------------------------------------------------------------------------------- /introduction.org: -------------------------------------------------------------------------------- 1 | #+title: Introducing Zetteldeft 2 | #+subtitle: A Zettelkasten for Emacs 3 | #+author: EFLS 4 | #+date: 2018-2020 5 | #+OPTIONS: num:nil 6 | #+EXPORT_FILE_NAME: ./docs/index 7 | 8 | *NOTE* (Mar 2023): Zetteldeft is no longer under active development. The the code is functional and the package remains available, but I am currently exploring note-taking based on Denote rather than on Deft. Feel free to reach out (via Mastodon or email) if you want to exchange ideas. 9 | 10 | * Introduction :ignore: 11 | 12 | Zetteldeft is an extension of the Deft package for Emacs. 13 | Building on Deft's search functionality, Zetteldeft provides a way to create and manage links between short notes. 14 | 15 | In the spirit of Zettelkasten, Zetteldeft maintains a flat hierarchy at the level of files, but invites authors to create structure and meaning via links and tags. 16 | The result is a free-form note taking system of linked notes and ideas. 17 | Or, as the original Zettelkasten creator Niklas Luhmann would call it: a partner in communication.[fn:lhcs] 18 | 19 | The text below introduces Zetteldeft, but even easier is the *tutorial*: get to know Zetteldeft from within Zetteldeft with =zd-tutorial= on [[https://github.com/EFLS/zd-tutorial][Github]]. 20 | Installation instructions included. 21 | Clone it and get started! 22 | 23 | Just want *source*? 24 | Take a look at this [[file:zetteldeft.org]["literate code"]], which contains both source and documentation, 25 | or simply refer to the [[https://github.com/efls/zetteldeft][Github repository]]. 26 | 27 | I've been maintaining this code for personal use since 2018,[fn:innp] and have been sharing it on Github for a while now. 28 | It's high time for a proper introduction of this package. 29 | 30 | One last note: I hope to make this a living document, so expect changes and additions. 31 | And feel free to leave comments, for example via [[https://github.com/efls/zetteldeft][Github]], [[https://twitter.com/EFLS0][Twitter]], or (preferably) [[https://mastodon.social/@EFLS][Mastodon]]. 32 | 33 | #+TOC: headlines 2 34 | 35 | 36 | [fn:lhcs] Luhmann was first and foremost a social theorist who developed a unique systems theory. 37 | In one of his writings, he compares his Zettelkasten to a "communicative system". 38 | Available here in English translation: 39 | http://luhmann.surge.sh/communicating-with-slip-boxes. 40 | 41 | [fn:innp] Please beware that I'm no programmer. 42 | Zetteldeft is written in Emacs Lisp, the only language I can claim to have ever programmed in outside of school, but can't claim to know well. 43 | I use Emacs mainly for writing, both personal and academic, and have fallen in love with its extensibility. 44 | My overall experience resonates strongly with this story [[https://www.gnu.org/gnu/rms-lisp.en.html][shared by Richard Stallman]]: 45 | 46 | #+begin_quote 47 | Multics Emacs proved to be a great success -- programming new editing commands was so convenient that even the secretaries in his [Bernie Greenberg] office started learning how to use it. 48 | They used a manual someone had written which showed how to extend Emacs, but didn't say it was a programming. 49 | So the secretaries, who believed they couldn't do programming, weren't scared off. 50 | They read the manual, discovered they could do useful things and they learned to program. 51 | #+end_quote 52 | 53 | * A Zettelkasten in Deft 54 | 55 | As the name suggests, Zetteldeft is inspired by the now famous Zettelkasten system first implemented by the German sociologist Niklas Luhmann long before Personal Computers made their appearance. 56 | 57 | In our digital times, however, a Zettelkasten note-taking system looks a lot like (though is not the same as) a personal wiki for your notes. 58 | This introduction won't attempt to explain what a Zettelkasten is or what you might use it for. 59 | To that end, the internet provides various resources, such as the great [[https://www.zettelkasten.de][zettelkasten.de]] and their community.[fn:insp] 60 | 61 | In this text I want to briefly introduce what Zetteldeft is and mention its core features, so that you can check it out if you'd like. 62 | 63 | 64 | [fn:insp] Zetteldeft is inspired by /The Archive/, created by the guys at [[https://www.zettelkasten.de][zettelkasten.de]]. 65 | 66 | * Key concepts 67 | 68 | Following the Zettelkasten philosophy, each note in Zetteldeft should either: (1) contain a core idea, (2) connect different ideas (and link to notes), (3) or contain a structured set of links to other notes. 69 | 70 | How you do that is completely up to you, but links between notes are key. 71 | And, it should be emphasized, links require /work/. 72 | Work done personally by /you/, the author, so that your notes might breathe life.[fn:lhtw] 73 | 74 | In Zetteldeft, each note has a unique *identifier* or ID, based on the time and date of its creation, included at the beginning of its filename. 75 | This, fore example, is the name of a file in my Zettelkasten: =2018-07-07-2356 The zetteldeft idea.org=. 76 | 77 | Notes contain *links* to other notes. 78 | These links are indicated by prepending the =§= character to an ID: 79 | =§2018-07-07-2356= links to the file mentioned above. 80 | No other formatting is needed: a plain text =§= and the ID suffice to create a link. 81 | And don't worry, you won't have to type the =§= manually (or the ID for that matter). 82 | And yes, you can change this link indicator or even disable it (and include a link suffix, if you so require). 83 | 84 | #+CAPTION: A note with links. The note shown is part of [[https://github.com/efls/zd-tutorial][zd-tutorial]] 85 | [[./img/screenshot.png]] 86 | 87 | The ID combined with Deft's full text search allow to 88 | 1. retrieve a note via its identifier (by searching file titles), 89 | 2. find out which notes link to a given note (via a full text search). 90 | 91 | That's pretty much all there is to it, for the basics at least. 92 | All of this is done in plain text. 93 | Org-mode by default, but it really is formatting agnostic. 94 | 95 | A way to further organize your notes, is to use tags, indicated with a =#= (or another string, it's all customizable). 96 | In my Zettelkasten, for example, I use =#zetteldeft= for all notes related to... 97 | Well, you can guess. 98 | 99 | 100 | [fn:lhtw] Or, in the words of Luhmann himself: 101 | 102 | #+begin_quote 103 | It is impossible to think without writing; at least it is impossible in any sophisticated or networked (/anschlußfähig/) fashion. 104 | #+end_quote 105 | 106 | Quote from [fn:lhcs]. 107 | 108 | * Basic functions 109 | ** A quick introduction 110 | 111 | Let's look at some basic functions you need to get started. 112 | 113 | *Create* a note with =C-c d n= (or =zetteldeft-new-file=). 114 | Enter a title and you're set. 115 | Zetteldeft will generate a note ID and include it in its filename. 116 | 117 | To *insert a link* to a note, you can use 118 | - =C-c d i= (or =zetteldeft-find-file-id-insert=), 119 | - or =C-c d I= if you want to include the title of your destination (which calls =zetteldeft-find-file-full-title-insert=). 120 | 121 | Hit =C-c d f= (or =zetteldeft-follow-link=) to *follow a link* to a note. 122 | All link indicators, those =§= symbols, will be replaced different characters (thanks to the Avy package). 123 | Pick one to follow a link. 124 | If only one link is available, or if point is in a link, it will be selected automatically. 125 | 126 | Use =C-c d F= to open a link in a separate window of choice. 127 | This is especially useful when browsing your own notes, looking for new ideas and connections. 128 | 129 | To quickly *open* one of the notes in your Zettelkasten, use =C-c d o= (or =zetteldeft-find-file=) and search the titles. 130 | Or simply hit =C-c d D= to open Deft and start a full text search. 131 | 132 | To quickly find out which notes *refer* to the current note, use =C-c d c= (which is =zetteldeft-search-current-id=). 133 | 134 | To search a *tag*, hit =C-c d t= and select a highlighted tag, similar to how you follow a link. 135 | Easily insert tags with =C-c d #= and select one from the list (or enter a new one). 136 | To generate a *list* of tags currently in your Zettelkasten, use =C-c d T=. 137 | Quickly launch a search for a tag of choice with =C-c d /=. 138 | 139 | There are many more functions, but these will be enough to get you started. 140 | 141 | ** An overview of keybindings 142 | 143 | As Zetteldeft does not launch a minor mode, no default keys are bound. 144 | You can set keys mentioned in this text by calling =zetteldeft-set-classic-keybindings=. 145 | 146 | For different setups with similar bindings, check the [[file:zetteldeft.org][literate source]]. 147 | Personally, I prefer vim style bindings behind a leader key, set up with general, 148 | [[file:zetteldeft.org::#kb-general][like so]]. 149 | 150 | #+CAPTION: Classic keybindings 151 | | Key | Function | 152 | |---------+----------------------------------------| 153 | | =C-c d d= | =deft= | 154 | | =C-c d D= | =zetteldeft-deft-new-search= | 155 | | =C-c d R= | =deft-refresh= | 156 | | =C-c d s= | =zetteldeft-search-at-point= | 157 | | =C-c d c= | =zetteldeft-search-current-id= | 158 | | =C-c d f= | =zetteldeft-follow-link= | 159 | | =C-c d F= | =zetteldeft-avy-file-search-ace-window= | 160 | | =C-c d l= | =zetteldeft-avy-link-search= | 161 | | =C-c d t= | =zetteldeft-avy-tag-search= | 162 | | =C-c d T= | =zetteldeft-tag-buffer= | 163 | | =C-c d #= | =zetteldeft-tag-insert= | 164 | | =C-c d i= | =zetteldeft-find-file-id-insert= | 165 | | =C-c d I= | =zetteldeft-find-file-full-title-insert= | 166 | | =C-c d o= | =zetteldeft-find-file= | 167 | | =C-c d n= | =zetteldeft-new-file= | 168 | | =C-c d N= | =zetteldeft-new-file-and-link= | 169 | | =C-c d r= | =zetteldeft-file-rename= | 170 | | =C-c d x= | =zetteldeft-count-words= | 171 | 172 | * Sneak peek at more advanced features 173 | 174 | As emphasized above, any Zettelkasten system relies on its author for links between notes. 175 | There are, however, some features in Zetteldeft that help you with this. 176 | For this introduction, I won't go into detail, but more information is found in the full [[file:zetteldeft.org][Zetteldeft.org]]. 177 | 178 | There is =zetteldeft-insert-list-links= to automatically generate a list of links to notes containing a provided search term. 179 | Or use =zetteldeft-insert-list-links-missing= if you only want to include those notes that /don't/ yet appear in the current note. 180 | 181 | Zetteldeft is not limited to Org-mode, but integrates well with source code blocks to, for example, automate generating the lists mentioned above. 182 | 183 | You can export your notes to HTML to read them outside of of Emacs, as explained [[file:zetteldeft.org::#export-setup][in the documentation]]. 184 | 185 | With the help of =graphviz=, we can even draw graphical representations of links between notes. 186 | Check out =zetteldeft-org-graph-search= and =zetteldeft-org-graph-note= [[file:zetteldeft.org::#visuals][in the documentation]]. 187 | It generates something like this: 188 | 189 | #+CAPTION: Example of a graph generated with graphviz. 190 | [[./img/zetteldeft-graph.jpg]] 191 | 192 | This feature is fairly crude but easily hackable. 193 | Ideas on how to extend or replace it are more than welcome. 194 | 195 | * Installing & getting started 196 | ** Installing Zetteldeft 197 | *** Intro :ignore: 198 | 199 | This section will take you through an example Zetteldeft setup and installation. 200 | It assumes basic Emacs knowledge, so I'm going to guess you understand that the code below should go in your =init.el= (or equivalent). 201 | 202 | It also assumes that you have =use-package= installed, that you use [[http://melpa.org/#/][MELPA]] to install Emacs packages, and that you'll write notes in =org-mode=. 203 | 204 | Prefer Markdown? 205 | That's easy enough to change in the example below. 206 | 207 | For different methods of installation, please refer to the [[file:zetteldeft.org::#install][documentation]]. 208 | 209 | *** Deft 210 | 211 | Zetteldeft relies on Deft. 212 | Let's start with a basic setup. 213 | 214 | #+begin_src emacs-lisp 215 | (use-package deft 216 | :ensure t 217 | :custom 218 | (deft-extensions '("org" "md" "txt")) 219 | (deft-directory "~/notes") 220 | (deft-use-filename-as-title t)) 221 | #+end_src 222 | 223 | Note that none of these settings are strictly required, apart from changing the default =deft-directory=. 224 | 225 | The =deft-use-filename-as-title= ensures that we can see the note IDs from the deft buffer, but this can be disabled if you prefer. 226 | 227 | *** Zetteldeft 228 | 229 | Installing Zetteldeft can be done in a similar fashion. 230 | 231 | Let's start bare bones: 232 | 233 | #+BEGIN_SRC emacs-lisp 234 | (use-package zetteldeft 235 | :ensure t 236 | :after deft 237 | :config (zetteldeft-set-classic-keybindings)) 238 | #+END_SRC 239 | 240 | That should be enough to get you started, really. 241 | 242 | *** Installation with Spacemacs 243 | 244 | Installation with Spacemacs is easy. 245 | Locate =dotspacemacs-configuration-layers= in your =.spacemacs= and add the code like so. 246 | 247 | #+BEGIN_SRC emacs-lisp 248 | (setq-default dotspacemacs-configuration-layers 249 | '((deft :variables deft-zetteldeft t))) 250 | #+END_SRC 251 | 252 | This should take care of keybindings as well. 253 | Take a look in [[file:zetteldeft.org][the documentation]] to see how keys are bound. 254 | 255 | ** Customization 256 | 257 | Some pointers for further customization: 258 | - alter =zetteldeft-link-indicator= to change the prefix to links, 259 | or set it to an empty string to remove it altogether, 260 | - change =zetteldeft-title-prefix= and =zetteldeft-title-suffix= to change how titles are appear, 261 | - you can modify =zetteldeft-id-format= to change how IDs are generated, but make sure to change =zetteldeft-id-regex= accordingly so that the new IDs can be detected. 262 | 263 | There's more to Zetteldeft, and to its customization, but that's all for this introduction. 264 | 265 | ** Using Zetteldeft with Markdown notes 266 | :PROPERTIES: 267 | :CUSTOM_ID: markdown 268 | :END: 269 | 270 | While Zetteldeft works nicely with Org-mode, you can call its functions from any mode. 271 | Many people keep Zettelkasten in Markdown, so let's explore how such a setup can be achieved. 272 | 273 | First, make sure =deft-extensions= is set correctly. 274 | If =md= is the first element on the list, new notes will be Markdown notes. 275 | Zetteldeft uses Deft to create new notes, so using =zetteldeft-new-file= should now create Markdown files. 276 | 277 | #+begin_src emacs-lisp 278 | (setq deft-extensions '("md" "org" "txt")) 279 | #+end_src 280 | 281 | In such Zettelkasten links are often wrapped in square brackets. 282 | This can be easily achieved by setting the =zetteldeft-link-indicator= and =zetteldeft-link-suffix=. 283 | 284 | #+begin_src emacs-lisp 285 | (setq zetteldeft-link-indicator "[[" 286 | zetteldeft-link-suffix "]]") 287 | #+end_src 288 | 289 | To make sure that your Markdown notes start with correct title syntax, customize the =zetteldeft-title-prefix=. 290 | 291 | #+begin_src emacs-lisp 292 | (setq zetteldeft-title-prefix "# ") 293 | #+end_src 294 | 295 | When using =zetteldeft-insert-list-links=, you might want to change a list entry to correct Markdown syntax, like so: 296 | 297 | #+begin_src emacs-lisp 298 | (setq zetteldeft-list-prefix "* ") 299 | #+end_src 300 | 301 | To highlight links you need to set up font-lock keywords for =markdown-mode=. 302 | 303 | #+begin_src emacs-lisp 304 | (font-lock-add-keywords 'markdown-mode 305 | `((,zetteldeft-id-regex 306 | . font-lock-warning-face))) 307 | #+end_src 308 | 309 | Alternatively, if you want to highlight the brackets as well, you need to escape them like so: 310 | 311 | #+begin_src emacs-lisp 312 | (font-lock-add-keywords 'markdown-mode 313 | `((,(concat "\\[\\[" 314 | zetteldeft-id-regex 315 | "\\]\\]") 316 | . font-lock-warning-face))) 317 | #+end_src 318 | 319 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Introducing Zetteldeft 8 | 9 | 10 | 194 | 195 | 196 | 197 |
198 | 208 |
209 |
210 |
211 |

Introducing Zetteldeft

212 |

A Zettelkasten for Emacs

213 |

214 | NOTE (Mar 2023): Zetteldeft is no longer under active development. The the code is functional and the package remains available, but I am currently exploring note-taking based on Denote rather than on Deft. Feel free to reach out (via Mastodon or email) if you want to exchange ideas. 215 |

216 | 217 |

218 | Zetteldeft is an extension of the Deft package for Emacs. 219 | Building on Deft’s search functionality, Zetteldeft provides a way to create and manage links between short notes. 220 |

221 | 222 |

223 | In the spirit of Zettelkasten, Zetteldeft maintains a flat hierarchy at the level of files, but invites authors to create structure and meaning via links and tags. 224 | The result is a free-form note taking system of linked notes and ideas. 225 | Or, as the original Zettelkasten creator Niklas Luhmann would call it: a partner in communication.1 226 |

227 | 228 |

229 | The text below introduces Zetteldeft, but even easier is the tutorial: get to know Zetteldeft from within Zetteldeft with zd-tutorial on Github. 230 | Installation instructions included. 231 | Clone it and get started! 232 |

233 | 234 |

235 | Just want source? 236 | Take a look at this "literate code", which contains both source and documentation, 237 | or simply refer to the Github repository. 238 |

239 | 240 |

241 | I’ve been maintaining this code for personal use since 2018,2 and have been sharing it on Github for a while now. 242 | It’s high time for a proper introduction of this package. 243 |

244 | 245 |

246 | One last note: I hope to make this a living document, so expect changes and additions. 247 | And feel free to leave comments, for example via Github, Twitter, or (preferably) Mastodon. 248 |

249 | 250 | 273 | 274 |
275 |

A Zettelkasten in Deft

276 |
277 |

278 | As the name suggests, Zetteldeft is inspired by the now famous Zettelkasten system first implemented by the German sociologist Niklas Luhmann long before Personal Computers made their appearance. 279 |

280 | 281 |

282 | In our digital times, however, a Zettelkasten note-taking system looks a lot like (though is not the same as) a personal wiki for your notes. 283 | This introduction won’t attempt to explain what a Zettelkasten is or what you might use it for. 284 | To that end, the internet provides various resources, such as the great zettelkasten.de and their community.3 285 |

286 | 287 |

288 | In this text I want to briefly introduce what Zetteldeft is and mention its core features, so that you can check it out if you’d like. 289 |

290 |
291 |
292 | 293 |
294 |

Key concepts

295 |
296 |

297 | Following the Zettelkasten philosophy, each note in Zetteldeft should either: (1) contain a core idea, (2) connect different ideas (and link to notes), (3) or contain a structured set of links to other notes. 298 |

299 | 300 |

301 | How you do that is completely up to you, but links between notes are key. 302 | And, it should be emphasized, links require work. 303 | Work done personally by you, the author, so that your notes might breathe life.4 304 |

305 | 306 |

307 | In Zetteldeft, each note has a unique identifier or ID, based on the time and date of its creation, included at the beginning of its filename. 308 | This, fore example, is the name of a file in my Zettelkasten: 2018-07-07-2356 The zetteldeft idea.org. 309 |

310 | 311 |

312 | Notes contain links to other notes. 313 | These links are indicated by prepending the § character to an ID: 314 | §2018-07-07-2356 links to the file mentioned above. 315 | No other formatting is needed: a plain text § and the ID suffice to create a link. 316 | And don’t worry, you won’t have to type the § manually (or the ID for that matter). 317 | And yes, you can change this link indicator or even disable it (and include a link suffix, if you so require). 318 |

319 | 320 | 321 |
322 | screenshot.png 323 | 324 |
Figure 1: A note with links. The note shown is part of zd-tutorial
325 |
326 | 327 |

328 | The ID combined with Deft’s full text search allow to 329 |

330 |
    331 |
  1. retrieve a note via its identifier (by searching file titles),
  2. 332 |
  3. find out which notes link to a given note (via a full text search).
  4. 333 |
334 | 335 |

336 | That’s pretty much all there is to it, for the basics at least. 337 | All of this is done in plain text. 338 | Org-mode by default, but it really is formatting agnostic. 339 |

340 | 341 |

342 | A way to further organize your notes, is to use tags, indicated with a # (or another string, it’s all customizable). 343 | In my Zettelkasten, for example, I use #zetteldeft for all notes related to… 344 | Well, you can guess. 345 |

346 |
347 |
348 | 349 |
350 |

Basic functions

351 |
352 |
353 |
354 |

A quick introduction

355 |
356 |

357 | Let’s look at some basic functions you need to get started. 358 |

359 | 360 |

361 | Create a note with C-c d n (or zetteldeft-new-file). 362 | Enter a title and you’re set. 363 | Zetteldeft will generate a note ID and include it in its filename. 364 |

365 | 366 |

367 | To insert a link to a note, you can use 368 |

369 |
    370 |
  • C-c d i (or zetteldeft-find-file-id-insert),
  • 371 |
  • or C-c d I if you want to include the title of your destination (which calls zetteldeft-find-file-full-title-insert).
  • 372 |
373 | 374 |

375 | Hit C-c d f (or zetteldeft-follow-link) to follow a link to a note. 376 | All link indicators, those § symbols, will be replaced different characters (thanks to the Avy package). 377 | Pick one to follow a link. 378 | If only one link is available, or if point is in a link, it will be selected automatically. 379 |

380 | 381 |

382 | Use C-c d F to open a link in a separate window of choice. 383 | This is especially useful when browsing your own notes, looking for new ideas and connections. 384 |

385 | 386 |

387 | To quickly open one of the notes in your Zettelkasten, use C-c d o (or zetteldeft-find-file) and search the titles. 388 | Or simply hit C-c d D to open Deft and start a full text search. 389 |

390 | 391 |

392 | To quickly find out which notes refer to the current note, use C-c d c (which is zetteldeft-search-current-id). 393 |

394 | 395 |

396 | To search a tag, hit C-c d t and select a highlighted tag, similar to how you follow a link. 397 | Easily insert tags with C-c d # and select one from the list (or enter a new one). 398 | To generate a list of tags currently in your Zettelkasten, use C-c d T. 399 | Quickly launch a search for a tag of choice with C-c d /. 400 |

401 | 402 |

403 | There are many more functions, but these will be enough to get you started. 404 |

405 |
406 |
407 | 408 |
409 |

An overview of keybindings

410 |
411 |

412 | As Zetteldeft does not launch a minor mode, no default keys are bound. 413 | You can set keys mentioned in this text by calling zetteldeft-set-classic-keybindings. 414 |

415 | 416 |

417 | For different setups with similar bindings, check the literate source. 418 | Personally, I prefer vim style bindings behind a leader key, set up with general, 419 | like so. 420 |

421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 |
Table 1: Classic keybindings
KeyFunction
C-c d ddeft
C-c d Dzetteldeft-deft-new-search
C-c d Rdeft-refresh
C-c d szetteldeft-search-at-point
C-c d czetteldeft-search-current-id
C-c d fzetteldeft-follow-link
C-c d Fzetteldeft-avy-file-search-ace-window
C-c d lzetteldeft-avy-link-search
C-c d tzetteldeft-avy-tag-search
C-c d Tzetteldeft-tag-buffer
C-c d #zetteldeft-tag-insert
C-c d izetteldeft-find-file-id-insert
C-c d Izetteldeft-find-file-full-title-insert
C-c d ozetteldeft-find-file
C-c d nzetteldeft-new-file
C-c d Nzetteldeft-new-file-and-link
C-c d rzetteldeft-file-rename
C-c d xzetteldeft-count-words
528 |
529 |
530 |
531 | 532 |
533 |

Sneak peek at more advanced features

534 |
535 |

536 | As emphasized above, any Zettelkasten system relies on its author for links between notes. 537 | There are, however, some features in Zetteldeft that help you with this. 538 | For this introduction, I won’t go into detail, but more information is found in the full Zetteldeft.org. 539 |

540 | 541 |

542 | There is zetteldeft-insert-list-links to automatically generate a list of links to notes containing a provided search term. 543 | Or use zetteldeft-insert-list-links-missing if you only want to include those notes that don’t yet appear in the current note. 544 |

545 | 546 |

547 | Zetteldeft is not limited to Org-mode, but integrates well with source code blocks to, for example, automate generating the lists mentioned above. 548 |

549 | 550 |

551 | You can export your notes to HTML to read them outside of of Emacs, as explained in the documentation. 552 |

553 | 554 |

555 | With the help of graphviz, we can even draw graphical representations of links between notes. 556 | Check out zetteldeft-org-graph-search and zetteldeft-org-graph-note in the documentation. 557 | It generates something like this: 558 |

559 | 560 | 561 |
562 | zetteldeft-graph.jpg 563 | 564 |
Figure 2: Example of a graph generated with graphviz.
565 |
566 | 567 |

568 | This feature is fairly crude but easily hackable. 569 | Ideas on how to extend or replace it are more than welcome. 570 |

571 |
572 |
573 | 574 |
575 |

Installing & getting started

576 |
577 |
578 |
579 |

Installing Zetteldeft

580 |
581 |

582 | This section will take you through an example Zetteldeft setup and installation. 583 | It assumes basic Emacs knowledge, so I’m going to guess you understand that the code below should go in your init.el (or equivalent). 584 |

585 | 586 |

587 | It also assumes that you have use-package installed, that you use MELPA to install Emacs packages, and that you’ll write notes in org-mode. 588 |

589 | 590 |

591 | Prefer Markdown? 592 | That’s easy enough to change in the example below. 593 |

594 | 595 |

596 | For different methods of installation, please refer to the documentation. 597 |

598 |
599 | 600 |
601 |

Deft

602 |
603 |

604 | Zetteldeft relies on Deft. 605 | Let’s start with a basic setup. 606 |

607 | 608 |
609 |
(use-package deft
610 |   :ensure t
611 |   :custom
612 |     (deft-extensions '("org" "md" "txt"))
613 |     (deft-directory "~/notes")
614 |     (deft-use-filename-as-title t))
615 | 
616 |
617 | 618 |

619 | Note that none of these settings are strictly required, apart from changing the default deft-directory. 620 |

621 | 622 |

623 | The deft-use-filename-as-title ensures that we can see the note IDs from the deft buffer, but this can be disabled if you prefer. 624 |

625 |
626 |
627 | 628 |
629 |

Zetteldeft

630 |
631 |

632 | Installing Zetteldeft can be done in a similar fashion. 633 |

634 | 635 |

636 | Let’s start bare bones: 637 |

638 | 639 |
640 |
(use-package zetteldeft
641 |   :ensure t
642 |   :after deft
643 |   :config (zetteldeft-set-classic-keybindings))
644 | 
645 |
646 | 647 |

648 | That should be enough to get you started, really. 649 |

650 |
651 |
652 | 653 |
654 |

Installation with Spacemacs

655 |
656 |

657 | Installation with Spacemacs is easy. 658 | Locate dotspacemacs-configuration-layers in your .spacemacs and add the code like so. 659 |

660 | 661 |
662 |
(setq-default dotspacemacs-configuration-layers
663 |   '((deft :variables deft-zetteldeft t)))
664 | 
665 |
666 | 667 |

668 | This should take care of keybindings as well. 669 | Take a look in the documentation to see how keys are bound. 670 |

671 |
672 |
673 |
674 | 675 |
676 |

Customization

677 |
678 |

679 | Some pointers for further customization: 680 |

681 |
    682 |
  • alter zetteldeft-link-indicator to change the prefix to links, 683 | or set it to an empty string to remove it altogether,
  • 684 |
  • change zetteldeft-title-prefix and zetteldeft-title-suffix to change how titles are appear,
  • 685 |
  • you can modify zetteldeft-id-format to change how IDs are generated, but make sure to change zetteldeft-id-regex accordingly so that the new IDs can be detected.
  • 686 |
687 | 688 |

689 | There’s more to Zetteldeft, and to its customization, but that’s all for this introduction. 690 |

691 |
692 |
693 | 694 |
695 |

Using Zetteldeft with Markdown notes

696 |
697 |

698 | While Zetteldeft works nicely with Org-mode, you can call its functions from any mode. 699 | Many people keep Zettelkasten in Markdown, so let’s explore how such a setup can be achieved. 700 |

701 | 702 |

703 | First, make sure deft-extensions is set correctly. 704 | If md is the first element on the list, new notes will be Markdown notes. 705 | Zetteldeft uses Deft to create new notes, so using zetteldeft-new-file should now create Markdown files. 706 |

707 | 708 |
709 |
(setq deft-extensions '("md" "org" "txt"))
710 | 
711 |
712 | 713 |

714 | In such Zettelkasten links are often wrapped in square brackets. 715 | This can be easily achieved by setting the zetteldeft-link-indicator and zetteldeft-link-suffix. 716 |

717 | 718 |
719 |
(setq zetteldeft-link-indicator "[["
720 |       zetteldeft-link-suffix "]]")
721 | 
722 |
723 | 724 |

725 | To make sure that your Markdown notes start with correct title syntax, customize the zetteldeft-title-prefix. 726 |

727 | 728 |
729 |
(setq zetteldeft-title-prefix "# ")
730 | 
731 |
732 | 733 |

734 | When using zetteldeft-insert-list-links, you might want to change a list entry to correct Markdown syntax, like so: 735 |

736 | 737 |
738 |
(setq zetteldeft-list-prefix "* ")
739 | 
740 |
741 | 742 |

743 | To highlight links you need to set up font-lock keywords for markdown-mode. 744 |

745 | 746 |
747 |
(font-lock-add-keywords 'markdown-mode
748 |    `((,zetteldeft-id-regex
749 |       . font-lock-warning-face)))
750 | 
751 |
752 | 753 |

754 | Alternatively, if you want to highlight the brackets as well, you need to escape them like so: 755 |

756 | 757 |
758 |
(font-lock-add-keywords 'markdown-mode
759 |    `((,(concat "\\[\\["
760 |                zetteldeft-id-regex
761 |                "\\]\\]")
762 |       . font-lock-warning-face)))
763 | 
764 |
765 |
766 |
767 |
768 |
769 |

Footnotes:

770 |
771 | 772 |
1

773 | Luhmann was first and foremost a social theorist who developed a unique systems theory. 774 | In one of his writings, he compares his Zettelkasten to a "communicative system". 775 | Available here in English translation: 776 | http://luhmann.surge.sh/communicating-with-slip-boxes. 777 |

778 | 779 |
2

780 | Please beware that I’m no programmer. 781 | Zetteldeft is written in Emacs Lisp, the only language I can claim to have ever programmed in outside of school, but can’t claim to know well. 782 | I use Emacs mainly for writing, both personal and academic, and have fallen in love with its extensibility. 783 | My overall experience resonates strongly with this story shared by Richard Stallman: 784 |

785 | 786 |
787 |

788 | Multics Emacs proved to be a great success – programming new editing commands was so convenient that even the secretaries in his [Bernie Greenberg] office started learning how to use it. 789 | They used a manual someone had written which showed how to extend Emacs, but didn’t say it was a programming. 790 | So the secretaries, who believed they couldn’t do programming, weren’t scared off. 791 | They read the manual, discovered they could do useful things and they learned to program. 792 |

793 |
794 | 795 |
3

796 | Zetteldeft is inspired by The Archive, created by the guys at zettelkasten.de. 797 |

798 | 799 |
4

800 | Or, in the words of Luhmann himself: 801 |

802 | 803 |
804 |

805 | It is impossible to think without writing; at least it is impossible in any sophisticated or networked (anschlußfähig) fashion. 806 |

807 |
808 | 809 |

810 | Quote from 1. 811 |

812 | 813 | 814 |
815 |
816 |
817 | 827 |
828 | 829 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /zetteldeft.el: -------------------------------------------------------------------------------- 1 | ;;; zetteldeft.el --- Turn deft into a zettelkasten system -*- lexical-binding: t -*- 2 | 3 | ;; Copyright (C) 2018-2021 EFLS 4 | 5 | ;; Author: EFLS 6 | ;; URL: https://efls.github.io/zetteldeft/ 7 | ;; Keywords: deft zettelkasten zetteldeft wp files 8 | ;; Version: 0.3 9 | ;; Package-Requires: ((emacs "25.1") (deft "0.8") (ace-window "0.7.0")) 10 | 11 | ;; This file is not part of Emacs 12 | 13 | ;; This program is free software; you can redistribute it and/or modify 14 | ;; it under the terms of the GNU General Public License as published by 15 | ;; the Free Software Foundation, either version 3 of the License, or 16 | ;; (at your option) any later version. 17 | 18 | ;; This program is distributed in the hope that it will be useful, 19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 | ;; GNU General Public License for more details. 22 | 23 | ;; You should have received a copy of the GNU General Public License 24 | ;; along with this program. If not, see . 25 | 26 | ;;; Commentary: 27 | 28 | ;; Zetteldeft is an extension of the deft package for Emacs. 29 | ;; It generates unique IDs to create stable links between notes, which 30 | ;; allows the user to make an interconnected system of notes. 31 | ;; Zetteldeft uses deft to find and follow links to notes. 32 | ;; For more information, see zetteldeft.org 33 | ;; or https://efls.github.io/zetteldeft 34 | 35 | ;; Note: this file is tangled from zetteldeft.org. 36 | ;; The .org contains documentation and notes on usage of the package. 37 | 38 | ;;; Code: 39 | 40 | (require 'deft) 41 | 42 | (unless (require 'avy nil 'no-error) 43 | (user-error "Avy not installed, required for zetteldeft-avy-* functions")) 44 | 45 | (require 'thingatpt) 46 | 47 | (require 'ace-window) 48 | 49 | (require 'seq) 50 | 51 | (declare-function avy-jump "avy") 52 | (unless (fboundp 'avy-jump) 53 | (display-warning 'zetteldeft 54 | "Function `avy-jump' not available. Please update `avy'")) 55 | 56 | (defgroup zetteldeft nil 57 | "A zettelkasten on top of deft." 58 | :group 'deft 59 | :link '(url-link "https://efls.github.io/zetteldeft")) 60 | 61 | ;;;###autoload 62 | (defun zetteldeft-search-at-point () 63 | "Search via `deft' with `thing-at-point' as filter. 64 | Thing can be a double-bracketed link, a hashtag, or a word." 65 | (interactive) 66 | (let ((string (zetteldeft--get-thing-at-point))) 67 | (if string 68 | (zetteldeft--search-global string t) 69 | (user-error "No search term at point")))) 70 | 71 | ;;;###autoload 72 | (defun zetteldeft-search-current-id () 73 | "Search deft with the id of the current file as filter. 74 | Open if there is only one result." 75 | (interactive) 76 | (zetteldeft--search-global 77 | (zetteldeft--current-id) t)) 78 | 79 | (defun zetteldeft--get-thing-at-point () 80 | "Return the thing at point. 81 | This can be 82 | - a link: a string between [[ brackets ]], 83 | - a tag matching `zetteldeft-tag-regex', 84 | - a link matching `zetteldeft-link-indicator', 85 | `zetteldeft-id-regex' and `zetteldeft-link-suffix', 86 | - or a word." 87 | (let* ((link-brackets-re "\\[\\[\\([^]]+\\)\\]\\]") 88 | (link-id-re (zetteldeft--link-regex)) 89 | (htag-re zetteldeft-tag-regex)) 90 | (cond 91 | ((thing-at-point-looking-at link-brackets-re) 92 | (match-string-no-properties 1)) 93 | ((thing-at-point-looking-at link-id-re) 94 | (match-string-no-properties 0)) 95 | ((thing-at-point-looking-at htag-re) 96 | (match-string-no-properties 0)) 97 | (t (thing-at-point 'word t))))) 98 | 99 | (defun zetteldeft--search-global (str &optional dntOpn) 100 | "Search deft with STR as filter. 101 | If there is only one result, open that file (unless DNTOPN is true)." 102 | ;; Sanitize the filter string 103 | (setq str (replace-regexp-in-string "[[:space:]\n]+" " " str)) 104 | ;; Switch to Deft window if buffer is currently visible 105 | (when (deft-buffer-visible-p) 106 | (select-window (deft-buffer-visible-p))) 107 | ;; Call deft search on the filter string 108 | (let ((deft-incremental-search t)) 109 | (deft) 110 | (deft-filter str t)) 111 | ;; If there is a single match, open the file 112 | (unless dntOpn 113 | (when (eq (length deft-current-files) 1) 114 | (deft-open-file (car deft-current-files))))) 115 | 116 | (defun zetteldeft--search-filename (thisStr &optional otherWindow) 117 | "Search for deft files with string THISSTR in filename. 118 | Open if there is only one result (in another window if OTHERWINDOW is non-nil). 119 | Return a message if no results are found." 120 | ;; Sanitize the filter string 121 | (setq thisStr (replace-regexp-in-string "[[:space:]\n]+" " " thisStr)) 122 | ;; Call deft search on the filter string 123 | (let ((deft-filter-only-filenames t)) 124 | (deft-filter thisStr t)) 125 | ;; If there is a single match, open the file 126 | (cond 127 | ((eq (length deft-current-files) 1) 128 | (deft-open-file (car deft-current-files) otherWindow)) 129 | ((eq (length deft-current-files) 0) 130 | (message "No notes found with %s in name." thisStr)))) 131 | 132 | (defun zetteldeft--get-file-list (srch) 133 | "Return a list of files with the search item SRCH." 134 | (let ((deft-current-sort-method 'title)) 135 | (deft-filter srch t) 136 | deft-current-files)) 137 | 138 | ;;;###autoload 139 | (defun zetteldeft-search-tag () 140 | "Prompt interactively for Zetteldeft tag and launch Deft search" 141 | (interactive) 142 | (let* ((tags (zetteldeft--get-all-sorted-tags)) 143 | (search-term (completing-read "Tag to search for: " tags))) 144 | (zetteldeft--search-global search-term t))) 145 | 146 | (defun zetteldeft--id-font-lock-setup (var val) 147 | "Add font-lock highlighting for zetteldeft links. 148 | Called when `zetteldeft-link-indicator' or 149 | `zetteldeft-id-regex' are customized." 150 | (when (and (boundp 'zetteldeft-link-indicator) 151 | (boundp 'zetteldeft-id-regex) 152 | (boundp 'zetteldeft-link-suffix)) 153 | (font-lock-remove-keywords 'org-mode 154 | `((,(concat zetteldeft-link-indicator 155 | zetteldeft-id-regex 156 | zetteldeft-link-suffix) 157 | . font-lock-warning-face)))) 158 | (set-default var val) 159 | (when (and (boundp 'zetteldeft-id-regex) 160 | (boundp 'zetteldeft-link-indicator) 161 | (boundp 'zetteldeft-link-suffix)) 162 | (font-lock-add-keywords 'org-mode 163 | `((,(concat zetteldeft-link-indicator 164 | zetteldeft-id-regex 165 | zetteldeft-link-suffix) 166 | . font-lock-warning-face))))) 167 | 168 | (defcustom zetteldeft-id-format "%Y-%m-%d-%H%M" 169 | "Format used when generating time-based zetteldeft IDs. 170 | 171 | Be warned: the regexp to find IDs is set separately. 172 | If you change this value, set `zetteldeft-id-regex' so that 173 | the IDs can be found. 174 | 175 | Check the documentation of the `format-time-string' 176 | function to see which placeholders can be used." 177 | :type 'string 178 | :group 'zetteldeft) 179 | 180 | (setq deft-new-file-format zetteldeft-id-format) 181 | 182 | (defun zetteldeft-generate-id (title &optional filename) 183 | "Generate and return a Zetteldeft ID. 184 | The ID is created using `zetteldeft-id-format', unless 185 | `zetteldeft-custom-id-function' is bound to a function, in which case 186 | that function is used and TITLE and FILENAME are passed to it." 187 | (let ((id 188 | (if-let ((f zetteldeft-custom-id-function)) 189 | (funcall f title filename) 190 | (format-time-string zetteldeft-id-format)))) 191 | (if (zetteldeft--id-available-p id) 192 | id 193 | (error "Generated ID %s is not unique." id)))) 194 | 195 | (defun zetteldeft--id-available-p (str) 196 | "Return t only if provided string STR is unique among Zetteldeft filenames." 197 | (let ((deft-filter-only-filenames t)) 198 | (deft-filter str t)) 199 | (eq 0 (length deft-current-files))) 200 | 201 | (defcustom zetteldeft-custom-id-function nil 202 | "User-defined function to generate an ID. 203 | The specified function must accept arguments for note `TITLE' 204 | and &optional `FILENAME'. The returned ID must be a string." 205 | :type 'function 206 | :group 'zetteldeft) 207 | 208 | (defcustom zetteldeft-id-regex "[0-9]\\{4\\}\\(-[0-9]\\{2,\\}\\)\\{3\\}" 209 | "The regular expression used to search for zetteldeft IDs. 210 | Set it so that it matches strings generated with 211 | `zetteldeft-id-format'." 212 | :type 'string 213 | :group 'zetteldeft 214 | :set 'zetteldeft--id-font-lock-setup) 215 | 216 | (defcustom zetteldeft-link-indicator "§" 217 | "String to indicate zetteldeft links. 218 | String prepended to IDs to easily identify them as links to zetteldeft notes. 219 | This variable should be a string containing only one character." 220 | :type 'string 221 | :group 'zetteldeft 222 | :set 'zetteldeft--id-font-lock-setup) 223 | 224 | (defcustom zetteldeft-link-suffix "" 225 | "String to append to zetteldeft links. 226 | To disable, set to empty string rather than to nil." 227 | :type 'string 228 | :group 'zetteldeft 229 | :set 'zetteldeft--id-font-lock-setup) 230 | 231 | (defun zetteldeft--link-regex () 232 | "Return regex for a Zetteldeft link. 233 | Concat link indicator, id-regex, and link suffix." 234 | (concat zetteldeft-link-indicator 235 | zetteldeft-id-regex 236 | zetteldeft-link-suffix)) 237 | 238 | (defun zetteldeft--lift-id (str) 239 | "Extract zetteldeft ID from STR. 240 | This is done with the regular expression stored in 241 | `zetteldeft-id-regex'." 242 | (with-temp-buffer 243 | (insert str) 244 | (when (re-search-forward zetteldeft-id-regex nil t -1) 245 | (match-string 0)))) 246 | 247 | (defun zetteldeft--insert-link (id &optional title) 248 | "Insert a link to Zetteldeft note ID. 249 | If TITLE is included, use it as link text. To customize how inserted 250 | links are formatted, change the `zetteldeft-insert-link-function' 251 | variable." 252 | (interactive) 253 | (funcall zetteldeft-insert-link-function id title)) 254 | 255 | (defcustom zetteldeft-insert-link-function 256 | #'zetteldeft-insert-link-zd-style 257 | "The function to use when inserting note links. 258 | 259 | Use either 260 | - `zetteldeft-insert-link-zd-style' for Zetteldeft type links 261 | - `zetteldeft-insert-link-org-style' for Org-mode zdlink: links 262 | - A custom function that takes two arguments: an ID and an optional title." 263 | :type 'function 264 | :options '(zetteldeft-insert-link-zd-style 265 | zetteldeft-insert-link-org-style) 266 | :group 'zetteldeft) 267 | 268 | (defun zetteldeft-insert-link-zd-style (id &optional title) 269 | "Insert a Zetteldeft link to note with provided ID." 270 | (insert zetteldeft-link-indicator 271 | id 272 | zetteldeft-link-suffix) 273 | (when title (insert " " title))) 274 | 275 | (defun zetteldeft-insert-link-org-style (id &optional title) 276 | "Insert a Zetteldeft link in Org-mode format as zdlink: type." 277 | (if title 278 | (insert "[[zdlink:" id "][" title "]]") 279 | (insert "[[zdlink:" id "]]"))) 280 | 281 | ;;;###autoload 282 | (defun zetteldeft-find-file (file) 283 | "Open deft file FILE. 284 | When no completing match, prompt user to create a new deft file using 285 | input as the title." 286 | (interactive 287 | (list (completing-read "Deft find file: " 288 | (deft-find-all-files-no-prefix)))) 289 | (let* ((dir (expand-file-name deft-directory))) 290 | (unless (string-match (concat "^" dir) file) 291 | (setq file (concat dir "/" file))) 292 | (if (file-exists-p file) 293 | (deft-open-file file) 294 | (when (y-or-n-p (format 295 | "Create new note with title \"%s\"?" 296 | (file-name-base file))) 297 | (zetteldeft-new-file (file-name-base file)))))) 298 | 299 | (defvar zetteldeft-home-id nil 300 | "String with ID of home note, used by `zetteldeft-go-home'.") 301 | 302 | (defun zetteldeft-go-home () 303 | "Move to a designated home note. 304 | Set `zetteldeft-home-id' to an ID string of your home note." 305 | (interactive) 306 | (if (stringp zetteldeft-home-id) 307 | (zetteldeft-find-file 308 | (zetteldeft--id-to-full-path zetteldeft-home-id)) 309 | (message "No home set. Provide a string to zetteldeft-home-id."))) 310 | 311 | ;;;###autoload 312 | (defun zetteldeft-find-file-id-insert (file) 313 | "Find deft file FILE and insert a link." 314 | (interactive (list 315 | (completing-read "File to insert id from: " 316 | (deft-find-all-files-no-prefix)))) 317 | (zetteldeft--insert-link (zetteldeft--lift-id file))) 318 | 319 | (defcustom zetteldeft-backlink-prefix "# Backlink: " 320 | "Prefix string included before a back link. 321 | Formatted as `org-mode' comment by default." 322 | :type 'string 323 | :group 'zetteldeft) 324 | 325 | (defcustom zetteldeft-backlink-location-function 326 | #'zetteldeft-backlink-get-location 327 | "Function to get location for new backlinks. 328 | The function should return a position in the current buffer.") 329 | 330 | (defun zetteldeft-backlink-get-location () 331 | "Default function that returns where a backlink should be added. 332 | 333 | This is the line below whichever is found first: 334 | - existing backlink 335 | - tag line 336 | - title 337 | - at top of file 338 | " 339 | (interactive) 340 | (save-excursion 341 | (goto-char (point-min)) 342 | (cond 343 | ((re-search-forward (regexp-quote zetteldeft-backlink-prefix) nil t) 344 | (forward-line) 345 | (point)) 346 | ((re-search-forward (regexp-quote zetteldeft-tag-line-prefix) nil t) 347 | (forward-line) 348 | (newline) 349 | (point)) 350 | ((re-search-forward (regexp-quote zetteldeft-title-prefix) nil t) 351 | (forward-line) 352 | (newline) 353 | (point)) 354 | (t (point-min))))) 355 | 356 | ;;;###autoload 357 | (defun zetteldeft-backlink-add (file) 358 | "Find deft file FILE and insert a backlink to it. 359 | Finds the title line, and adds `backlink-prefix' with 360 | ID and title on a new line." 361 | (interactive (list 362 | (completing-read "File to add backlink to: " 363 | (deft-find-all-files-no-prefix)))) 364 | (save-excursion 365 | (goto-char (funcall zetteldeft-backlink-location-function)) 366 | (insert zetteldeft-backlink-prefix) 367 | (zetteldeft--insert-link 368 | (zetteldeft--lift-id file) 369 | (zetteldeft--lift-file-title (concat deft-directory file))) 370 | (insert "\n")) 371 | (message "Backlink added.")) 372 | 373 | ;;;###autoload 374 | (defun zetteldeft-find-file-full-title-insert (file) 375 | "Find deft file FILE and insert a link with title." 376 | (interactive (list 377 | (completing-read "File to insert full title from: " 378 | (deft-find-all-files-no-prefix)))) 379 | (let ((id (zetteldeft--lift-id file))) 380 | (zetteldeft--insert-link 381 | id 382 | (zetteldeft--id-to-title id)))) 383 | 384 | (defun zetteldeft--full-search (string) 385 | "Return list of deft files with STRING in full body of file." 386 | (let ((dir (expand-file-name deft-directory)) 387 | (result-files (zetteldeft--get-file-list string)) 388 | (this-file (buffer-file-name))) 389 | (when this-file 390 | (setq result-files (delete this-file result-files))) 391 | (setq result-files 392 | (mapcar 393 | (lambda (f) (replace-regexp-in-string dir "" f)) 394 | result-files)) 395 | (completing-read (format "Search files containing \"%s\": " string) 396 | result-files))) 397 | 398 | ;;;###autoload 399 | (defun zetteldeft-full-search-id-insert (string) 400 | "Insert ID of file from list of files containing STRING." 401 | (interactive (list (read-string "Search string: "))) 402 | (zetteldeft-find-file-id-insert 403 | (zetteldeft--full-search string))) 404 | 405 | ;;;###autoload 406 | (defun zetteldeft-full-search-full-title-insert (string) 407 | "Insert title and ID of file from list of files containing 408 | STRING." 409 | (interactive (list (read-string "Search string: "))) 410 | (zetteldeft-find-file-full-title-insert 411 | (zetteldeft--full-search string))) 412 | 413 | ;;;###autoload 414 | (defun zetteldeft-full-search-find-file (string) 415 | "Open file containing STRING." 416 | (interactive (list (read-string "Search string: "))) 417 | (zetteldeft-find-file (zetteldeft--full-search string))) 418 | 419 | (defcustom zetteldeft-id-filename-separator " " 420 | "String to separate zetteldeft ID from filename." 421 | :type 'string 422 | :group 'zetteldeft) 423 | 424 | (declare-function evil-insert-state "evil") 425 | 426 | (defcustom zetteldeft-new-filename-to-kill-ring nil 427 | "Add new filename to kill ring?" 428 | :type 'boolean 429 | :group 'zetteldeft) 430 | 431 | ;;;###autoload 432 | (defun zetteldeft-new-file (str &optional id) 433 | "Create a new deft file. 434 | 435 | The filename is a Zetteldeft ID, appended by STR. The ID will be 436 | generated, unless ID is provided. A file title will be inserted in the 437 | newly created file wrapped in `zetteldeft-title-prefix' and 438 | `zetteldeft-title-suffix'. When `zetteldeft-new-filename-to-kill-ring' 439 | is non-nil, the filename (without extension) is added to the kill 440 | ring. When `evil' is loaded, change to insert state." 441 | (interactive (list (read-string "Note title: "))) 442 | (let* ((deft-use-filename-as-title t) 443 | (zdId (or id 444 | (zetteldeft-generate-id str))) 445 | (zdName (concat zdId zetteldeft-id-filename-separator str))) 446 | (deft-new-file-named zdName) 447 | (when zetteldeft-new-filename-to-kill-ring 448 | (kill-new zdName)) 449 | (zetteldeft--insert-title str) 450 | (save-buffer) 451 | (when (featurep 'evil) (evil-insert-state)))) 452 | 453 | ;;;###autoload 454 | (defun zetteldeft-new-file-and-link (str) 455 | "Create a new note and insert a link to it. 456 | Similar to `zetteldeft-new-file', but insert a link to the new file." 457 | (interactive (list (read-string "Note title: "))) 458 | (let ((zdId (zetteldeft-generate-id str))) 459 | (zetteldeft--insert-link zdId str) 460 | (zetteldeft-new-file str zdId))) 461 | 462 | ;;;###autoload 463 | (defun zetteldeft-new-file-and-backlink (str) 464 | "Create a new note and insert link and backlink." 465 | (interactive (list (read-string "Note title: "))) 466 | (let ((ogId (zetteldeft--current-id)) 467 | (zdId (zetteldeft-generate-id str))) 468 | (zetteldeft--insert-link zdId str) 469 | (zetteldeft-new-file str zdId) 470 | (newline) 471 | (zetteldeft--insert-link ogId (zetteldeft--id-to-title ogId)))) 472 | 473 | (defun zetteldeft-extract-region-to-note (title) 474 | "Extract the marked region to a new note with TITLE." 475 | (interactive (list (if (not (use-region-p)) 476 | (user-error "No region active.") 477 | (read-string "Note title: ")))) 478 | (let* ((id (zetteldeft-generate-id title)) 479 | (text (kill-region (region-beginning) (region-end)))) 480 | (save-excursion 481 | (zetteldeft-new-file title id) 482 | (yank) 483 | (save-buffer)) 484 | (zetteldeft--insert-link id title))) 485 | 486 | ;;;###autoload 487 | (defun zetteldeft-follow-link () 488 | "Use Avy to follow a link. 489 | 490 | Follows zetteldeft link to a file if point is on a link. 491 | Uses Avy to prompt for a link to follow with `zetteldeft-avy-file-search' 492 | if it isn't. Variable `zetteldeft-follow-at-point' controls this last 493 | option: when nil, always use Avy." 494 | (interactive) 495 | (if (and zetteldeft-follow-at-point 496 | (thing-at-point-looking-at (zetteldeft--link-regex))) 497 | (zetteldeft--search-filename 498 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point))) 499 | (zetteldeft-avy-file-search))) 500 | 501 | (defcustom zetteldeft-follow-at-point t 502 | "Should `zetteldeft-follow-link' open link at point? 503 | When t, open note at point if point is on a link. 504 | When nil, always use Avy." 505 | :type 'boolean 506 | :group 'zetteldeft) 507 | 508 | ;;;###autoload 509 | (defun zetteldeft-browse () 510 | "Browse your notes with avy. 511 | Keep calling `zetteldeft-avy-file-search' in a loop." 512 | (interactive) 513 | (let ((avy-single-candidate-jump nil) 514 | (avy-handler-function 'zetteldeft--browse-avy-handler)) 515 | (while (zetteldeft-avy-file-search) 516 | (message "Browsing in Zetteldeft! [.] home [<] prev [>] next")))) 517 | 518 | (defun zetteldeft--browse-avy-handler (char) 519 | "The default handler for a bad CHAR." 520 | (let (dispatch) 521 | (cond ((setq dispatch (assoc char avy-dispatch-alist)) 522 | (unless (eq avy-style 'words) 523 | (setq avy-action (cdr dispatch))) 524 | (throw 'done 'restart)) 525 | ((memq char avy-escape-chars) 526 | ;; exit silently 527 | (throw 'done 'abort)) 528 | ;; Go to home note 529 | ((eq char ?.) 530 | (zetteldeft-go-home) 531 | (message "Brwosing to home note.") 532 | (zetteldeft-browse) 533 | (throw 'done 'abort)) 534 | ;; Previous buffer 535 | ((eq char ?<) 536 | (previous-buffer) 537 | (message "Browsing to previous buffer.") 538 | (zetteldeft-browse) 539 | (throw 'done 'abort)) 540 | ((eq char ?>) 541 | (next-buffer) 542 | (message "Browsing to next buffer.") 543 | (zetteldeft-browse) 544 | (throw 'done 'abort)) 545 | ((eq char ??) 546 | (avy-show-dispatch-help) 547 | (throw 'done 'restart)) 548 | ((mouse-event-p char) 549 | (signal 'user-error (list "Mouse event not handled" char))) 550 | (t 551 | (message "No such candidate: %s, hit `C-g' to quit." 552 | (if (characterp char) (string char) char)))))) 553 | 554 | ;;;###autoload 555 | (defun zetteldeft-avy-tag-search () 556 | "Call on avy to jump to a tag. 557 | Tags are filtered with `zetteldeft-tag-regex'." 558 | (interactive) 559 | (save-excursion 560 | (let ((avy-all-windows nil)) 561 | (when (consp (avy-jump zetteldeft-tag-regex)) 562 | (zetteldeft-search-at-point))))) 563 | 564 | ;;;###autoload 565 | (defun zetteldeft-avy-file-search (&optional otherWindow) 566 | "Use `avy' to follow a zetteldeft link. 567 | Links are found via `zetteldeft-link-indicator' and `zetteldeft-id-regex'. 568 | Open that file (in another window if OTHERWINDOW)." 569 | (interactive) 570 | (save-excursion 571 | (when (consp (avy-jump (zetteldeft--link-regex))) 572 | (zetteldeft--search-filename 573 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point)) otherWindow)))) 574 | 575 | (declare-function aw-select "ace-window") 576 | 577 | ;;;###autoload 578 | (defun zetteldeft-avy-file-search-ace-window () 579 | "Use `avy' to follow a zetteldeft link in another window. 580 | Similar to `zetteldeft-avy-file-search', but with window selection. 581 | When only one window is active, split it first. 582 | When more windows are active, select one via `ace-window'." 583 | (interactive) 584 | (save-excursion 585 | (when (consp (avy-jump (zetteldeft--link-regex))) 586 | (let ((ID (zetteldeft--lift-id (zetteldeft--get-thing-at-point)))) 587 | (when (eq 1 (length (window-list))) (split-window)) 588 | (select-window (aw-select "Select window...")) 589 | (zetteldeft--search-filename ID))))) 590 | 591 | ;;;###autoload 592 | (defun zetteldeft-avy-link-search () 593 | "Use `avy' to perform a deft search on a zetteldeft link. 594 | Similar to `zetteldeft-avy-file-search' but performs a full 595 | text search for the link ID instead of filenames only. 596 | Opens immediately if there is only one result." 597 | (interactive) 598 | (save-excursion 599 | (when (consp (avy-jump (zetteldeft--link-regex))) 600 | (zetteldeft--search-global 601 | (zetteldeft--lift-id (zetteldeft--get-thing-at-point)))))) 602 | 603 | (defun zetteldeft--list-dead-links () 604 | "Return a list with IDs in Zetteldeft notes that have no corresponding note." 605 | (let ((dead-links '()) 606 | (deft-filter-only-filenames t)) 607 | (dolist (link (zetteldeft--list-all-links)) 608 | (deft-filter link t) 609 | (when (eq 0 (length deft-current-files)) 610 | (unless (member link dead-links) 611 | (push link dead-links)))) 612 | dead-links)) 613 | 614 | (defconst zetteldeft--dead-links-buffer-name "*zetteldeft-dead-links*") 615 | 616 | (defun zetteldeft-dead-links-buffer () 617 | "Show a buffer with all dead links in Zetteldeft." 618 | (interactive) 619 | (switch-to-buffer zetteldeft--dead-links-buffer-name) 620 | (erase-buffer) 621 | (message "Finding all dead Zetteldeft links...") 622 | (let ((dead-links (zetteldeft--list-dead-links))) 623 | (insert (format "# Found %d dead links\n" (length dead-links))) 624 | (dolist (link dead-links) 625 | (insert (format " - %s in: " link)) 626 | (deft-filter link t) 627 | (dolist (source (deft-current-files)) 628 | (zetteldeft--insert-link (zetteldeft--lift-id source))) 629 | (insert "\n"))) 630 | (unless (eq major-mode 'org-mode) (org-mode))) 631 | 632 | ;;;###autoload 633 | (defun zetteldeft-deft-new-search () 634 | "Launch deft, clear filter and enter insert state." 635 | (interactive) 636 | (deft) 637 | (deft-filter-clear) 638 | (when (featurep 'evil) (evil-insert-state))) 639 | 640 | (defun zetteldeft--check () 641 | "Check if the currently visited file is in `zetteldeft' territory: 642 | whether it has `deft-directory' somewhere in its path." 643 | (unless (buffer-file-name) 644 | (user-error "Buffer not visiting a file")) 645 | (unless (string-match-p 646 | (regexp-quote (file-truename deft-directory)) 647 | (file-truename (buffer-file-name))) 648 | (user-error "Not in zetteldeft territory"))) 649 | 650 | (defun zetteldeft--current-id () 651 | "Retrieve ID from current file." 652 | (zetteldeft--check) 653 | (zetteldeft--lift-id 654 | (file-name-base (buffer-file-name)))) 655 | 656 | (defcustom zetteldeft-title-prefix "#+TITLE: " 657 | "Prefix string included when `zetteldeft--insert-title' is called. 658 | Formatted for `org-mode' by default. 659 | Don't forget to include a space." 660 | :type 'string 661 | :group 'zetteldeft) 662 | 663 | (defcustom zetteldeft-title-suffix "" 664 | "String inserted below title when `zetteldeft--insert-title' is called. 665 | Empty by default. 666 | Don't forget to add `\\n' at the beginning to start a new line." 667 | :type 'string 668 | :group 'zetteldeft) 669 | 670 | (defun zetteldeft--insert-title (title) 671 | "Insert TITLE as title in file. 672 | Prepended by `zetteldeft-title-prefix' and appended by `zetteldeft-title-suffix'." 673 | (zetteldeft--check) 674 | (insert 675 | zetteldeft-title-prefix 676 | title 677 | zetteldeft-title-suffix)) 678 | 679 | (defcustom zetteldeft-title-parsing-function #'deft-parse-title 680 | "Function used to extract a title from a note; defaults to `deft-parse-title'. 681 | The function you use here must be compatible with `deft-parse-title': the 682 | first argument is the file's fully qualified path; the second is the contents 683 | of said file." 684 | :type 'function 685 | :group 'zetteldeft) 686 | 687 | (defun zetteldeft--lift-file-title (zdFile) 688 | "Return the title of a zetteldeft note. 689 | ZDFILE should be a full path to a note." 690 | (let ((deft-use-filename-as-title nil)) 691 | (funcall zetteldeft-title-parsing-function 692 | zdFile 693 | (with-temp-buffer 694 | (insert-file-contents zdFile) 695 | (buffer-string))))) 696 | 697 | ;;;###autoload 698 | (defun zetteldeft-file-rename () 699 | "Change current file's title, and use the new title to rename the file. 700 | Use this on files in the `deft-directory'. 701 | When the file has no Zetteldeft ID, one is generated and included in the new name." 702 | (interactive) 703 | (zetteldeft--check) 704 | (let ((old-filename (buffer-file-name))) 705 | (when old-filename 706 | (let* ((old-title (zetteldeft--lift-file-title old-filename)) 707 | (prompt-text (concat "Change " old-title " to: ")) 708 | (new-title (read-string prompt-text old-title)) 709 | (id (or (zetteldeft--lift-id (file-name-base old-filename)) 710 | (zetteldeft-generate-id new-title old-filename))) 711 | (new-filename 712 | (deft-absolute-filename 713 | (concat id zetteldeft-id-filename-separator new-title)))) 714 | (rename-file old-filename new-filename) 715 | (deft-update-visiting-buffers old-filename new-filename) 716 | (zetteldeft-update-title-in-file new-title) 717 | (deft-refresh))))) 718 | 719 | (defcustom zetteldeft-always-insert-title t 720 | "When renaming a note, insert title if not already present." 721 | :type 'boolean 722 | :group 'zetteldeft) 723 | 724 | (defun zetteldeft-update-title-in-file (title) 725 | "Update the title in the current note buffer to TITLE. 726 | This searches the buffer for `zetteldeft-title-prefix' and updates the current 727 | title, if present. If not present and `zetteldeft-always-insert-title' is set, 728 | this inserts a title line at the beginning of the buffer. Otherwise, no change 729 | is made." 730 | (save-excursion 731 | (let ((zetteldeft-title-suffix "")) 732 | (goto-char (point-min)) 733 | (if (re-search-forward (regexp-quote zetteldeft-title-prefix) nil t) 734 | (progn (delete-region (line-beginning-position) (line-end-position)) 735 | (zetteldeft--insert-title title)) 736 | (when zetteldeft-always-insert-title 737 | (zetteldeft--insert-title title) 738 | (newline)))))) 739 | 740 | ;;;###autoload 741 | (defun zetteldeft-count-words () 742 | "Prints total number of words and notes in the minibuffer." 743 | (interactive) 744 | (let ((numWords 0)) 745 | (dolist (deftFile deft-all-files) 746 | (with-temp-buffer 747 | (insert-file-contents deftFile) 748 | (setq numWords (+ numWords (count-words (point-min) (point-max)))))) 749 | (message 750 | "Your zettelkasten contains %s notes with %s words in total." 751 | (length deft-all-files) numWords))) 752 | 753 | ;;;###autoload 754 | (defun zetteldeft-copy-id-current-file () 755 | "Copy current ID. 756 | Add the id from the filename the buffer is currently visiting to the 757 | kill ring." 758 | (interactive) 759 | (zetteldeft--check) 760 | (let ((ID (concat zetteldeft-link-indicator 761 | (zetteldeft--lift-id (file-name-base (buffer-file-name))) 762 | zetteldeft-link-suffix))) 763 | (kill-new ID) 764 | (message "%s" ID))) 765 | 766 | (defun zetteldeft--extract-links (deftFile) 767 | "Find all links in DEFTFILE and return a list." 768 | (let ((zdLinks (list))) 769 | (with-temp-buffer 770 | (insert-file-contents deftFile) 771 | (while (re-search-forward zetteldeft-id-regex nil t) 772 | (let ((foundTag (replace-regexp-in-string " " "" (match-string 0)))) 773 | ;; Add found tag to zdLinks if it isn't there already 774 | (unless (member foundTag zdLinks) 775 | (push foundTag zdLinks))) 776 | ;; Remove found tag from buffer 777 | (delete-region (point) (re-search-backward zetteldeft-id-regex)))) 778 | zdLinks)) 779 | 780 | (defun zetteldeft--list-all-links () 781 | "Return a list with all IDs that appear in notes." 782 | (let ((all-links '())) 783 | (dolist (file deft-all-files) 784 | (dolist (link (zetteldeft--extract-links file)) 785 | (unless (member link all-links) 786 | (push link all-links)))) 787 | all-links)) 788 | 789 | (defun zetteldeft--id-to-full-path (zdID) 790 | "Return full path from given zetteldeft ID ZDID. 791 | Returns nil when no files are found. 792 | Throws an error when multiple files are found." 793 | (let ((deft-filter-only-filenames t)) 794 | (deft-filter zdID t)) 795 | (when (> (length deft-current-files) 1) 796 | (user-error "ID Error. Multiple zetteldeft files found with ID %s" zdID)) 797 | (car deft-current-files)) 798 | 799 | (defun zetteldeft--id-to-title (zdId) 800 | "Turn a Zetteldeft ID into the title." 801 | (zetteldeft--lift-file-title 802 | (zetteldeft--id-to-full-path zdId))) 803 | 804 | (defcustom zetteldeft-tag-regex "[#@][[:alnum:]_-]+" 805 | "Regular expression for finding Zetteldeft tags." 806 | :type 'string 807 | :group 'zetteldeft) 808 | 809 | (defcustom zetteldeft-tag-prefix "#" 810 | "String prefix used when inserting new Zetteldeft tags." 811 | :type 'string 812 | :group 'zetteldeft) 813 | 814 | (defcustom zetteldeft-tag-line-prefix "# Tags" 815 | "String used to find the line where tags in Zetteldeft files should go." 816 | :type 'string 817 | :group 'zetteldeft) 818 | 819 | ;;;###autoload 820 | (defun zetteldeft-tag-insert-at-point (tag) 821 | "Insert TAG at point. Interactively, select an existing tag or provide new one." 822 | (interactive (list (completing-read 823 | "Tag to insert: " 824 | (zetteldeft--get-all-sorted-tags)))) 825 | (unless (string-prefix-p zetteldeft-tag-prefix tag) 826 | (insert zetteldeft-tag-prefix)) 827 | (insert tag)) 828 | 829 | ;;;###autoload 830 | (defun zetteldeft-tag-insert () 831 | "Select existing tag or enter new one to insert in current Zetteldeft note. 832 | 833 | The tag is appended to the first line starting with `zetteldeft-tag-line-prefix'. 834 | If this variable is nil, or tag line is not found, insert tag at point." 835 | (interactive) 836 | (zetteldeft--check) 837 | (let ((dest (when zetteldeft-tag-line-prefix 838 | (save-excursion 839 | (goto-char (point-min)) 840 | (re-search-forward zetteldeft-tag-line-prefix nil t))))) 841 | (if dest 842 | (save-excursion 843 | (goto-char dest) 844 | (end-of-line) 845 | (insert " ") 846 | (call-interactively 'zetteldeft-tag-insert-at-point)) 847 | (call-interactively 'zetteldeft-tag-insert-at-point)))) 848 | 849 | (defun zetteldeft-tag-remove () 850 | "Prompt for a tag to remove from the current Zetteldeft note. 851 | Only the first instance of the selected tag is removed." 852 | (interactive) 853 | (zetteldeft--check) 854 | ; Extract tags of current file into `zetteldeft--tag-list' 855 | (setq zetteldeft--tag-list (list)) 856 | (save-buffer) 857 | (zetteldeft--extract-tags (buffer-file-name)) 858 | ; Select a tag from that list 859 | (let* ((tag (completing-read 860 | "Tag to remove: " 861 | (seq-filter 'stringp zetteldeft--tag-list)))) 862 | ; Find and remove first instance of that tag 863 | (save-excursion 864 | (goto-char (point-min)) 865 | (re-search-forward tag nil t) 866 | (delete-region (point) (re-search-backward tag nil t)) 867 | ; remove potential empty space before tag 868 | (backward-char) 869 | (when (looking-at " ") (delete-char 1))))) 870 | 871 | (defconst zetteldeft--tag-buffer-name "*zetteldeft-tag-buffer*") 872 | 873 | ;;;###autoload 874 | (defun zetteldeft-tag-buffer () 875 | "Switch to the `zetteldeft-tag-buffer' and list tags." 876 | (interactive) 877 | (switch-to-buffer zetteldeft--tag-buffer-name) 878 | (erase-buffer) 879 | (let ((tagList (zetteldeft--get-all-tags))) 880 | (dolist (zdTag tagList) 881 | (when (stringp zdTag) 882 | (insert (format "%s (%d) \n" 883 | zdTag 884 | (lax-plist-get tagList zdTag))))) 885 | (unless (eq major-mode 'org-mode) (org-mode)) 886 | (sort-lines nil (point-min) (point-max)) 887 | (goto-char (point-min)))) 888 | 889 | (defvar zetteldeft--tag-list nil 890 | "A temporary property list to store all tags.") 891 | 892 | (defun zetteldeft--get-all-tags () 893 | "Return a plist of all the tags found in zetteldeft files." 894 | (setq zetteldeft--tag-list (list)) 895 | (dolist (deftFile deft-all-files) 896 | (zetteldeft--extract-tags deftFile)) 897 | zetteldeft--tag-list) 898 | 899 | (defun zetteldeft--get-all-sorted-tags () 900 | "Return a sorted plist of all the tags found in zetteldeft files." 901 | (seq-sort 'string-lessp 902 | (seq-filter 'stringp 903 | (zetteldeft--get-all-tags)))) 904 | 905 | (defun zetteldeft--tag-format () 906 | "Adjust `zetteldeft-tag-regex' for more accurate results." 907 | (concat "\\(^\\|\s\\)" zetteldeft-tag-regex)) 908 | 909 | (defun zetteldeft--extract-tags (deftFile) 910 | "Find all tags in DEFTFILE and add them to `zetteldeft--tag-list'. 911 | Increase counters as we go." 912 | (with-temp-buffer 913 | (insert-file-contents deftFile) 914 | (while (re-search-forward (zetteldeft--tag-format) nil t) 915 | (let ((foundTag (replace-regexp-in-string " " "" (match-string 0)))) 916 | ;; Add found tag to zetteldeft--tag-list if it isn't there already 917 | (zetteldeft--tag-count foundTag)) 918 | ;; Remove found tag from buffer 919 | (delete-region (point) (re-search-backward (zetteldeft--tag-format)))))) 920 | 921 | (defun zetteldeft--tag-count (zdTag) 922 | (let ((tagCount (lax-plist-get zetteldeft--tag-list zdTag))) 923 | (if tagCount 924 | ; if the tag was there already, inc by 1 925 | (setq zetteldeft--tag-list 926 | (lax-plist-put zetteldeft--tag-list zdTag (1+ tagCount))) 927 | ; if tag was not there yet, add & set to 1 928 | (setq zetteldeft--tag-list 929 | (lax-plist-put zetteldeft--tag-list zdTag 1))))) 930 | 931 | ;;;###autoload 932 | (defun zetteldeft-insert-list-links (string) 933 | "Search for SEARCH-STRING and insert list of links to results." 934 | (interactive (list (read-string "search string: "))) 935 | (let ((result-files (zetteldeft--get-file-list string)) 936 | (this-file (buffer-file-name))) 937 | (when this-file 938 | (setq result-files (delete this-file result-files))) 939 | (dolist (file result-files) 940 | (zetteldeft--list-entry-file-link file)))) 941 | 942 | (defcustom zetteldeft-list-links-missing-message 943 | " No missing links with search term =%s= found\n" 944 | "Message to insert when no missing links are found. 945 | This is used by `zetteldeft-insert-list-links-missing'. 946 | %s will be replaced by the search term provided to 947 | this function." 948 | :type 'string 949 | :group 'zetteldeft) 950 | 951 | ;;;###autoload 952 | (defun zetteldeft-insert-list-links-missing (string) 953 | "Insert a list of links to all deft files with a search string ZDSRCH. 954 | In contrast to `zetteldeft-insert-list-links' only include links not 955 | yet present in the current file. Can only be called from a file in the 956 | zetteldeft directory." 957 | (interactive (list (read-string "Search string: "))) 958 | (zetteldeft--check) 959 | (let (this-id ; ID of current file 960 | current-ids ; IDs present in current buffer 961 | found-ids ; IDs of notes with search result 962 | final-ids) ; Final list of IDs for the list 963 | ;; Gather list of IDs in current buffer 964 | (setq current-ids (zetteldeft--extract-links (buffer-file-name))) 965 | ;; Execute search and push found IDs to temporary list 966 | (dolist (file (zetteldeft--get-file-list string)) 967 | (push (zetteldeft--lift-id file) found-ids)) 968 | ;; Keep only unique IDs on the final list 969 | (dolist (id found-ids) 970 | (unless (member id current-ids) 971 | (push id final-ids))) 972 | ;; Remove the ID of the current buffer from the final list 973 | (setq this-id (zetteldeft--lift-id (file-name-base (buffer-file-name)))) 974 | (setq final-ids (delete this-id final-ids)) 975 | ;; Finally insert ID and title for each element on the list 976 | (if final-ids 977 | (dolist (id final-ids) 978 | (zetteldeft--list-entry-file-link (zetteldeft--id-to-full-path id))) 979 | ;; Unless the list is empty, then insert a message 980 | (insert (format zetteldeft-list-links-missing-message string))))) 981 | 982 | (defcustom zetteldeft-list-prefix " - " 983 | "Prefix for lists created with `zetteldeft-insert-list-links' 984 | and `zetteldeft-insert-list-links-missing'." 985 | :type 'string 986 | :group 'zetteldeft) 987 | 988 | (defun zetteldeft--list-entry-file-link (file) 989 | "Insert ZDFILE as list entry." 990 | (let ((id (zetteldeft--lift-id (file-name-base file)))) 991 | (insert zetteldeft-list-prefix) 992 | (when id 993 | (insert zetteldeft-link-indicator 994 | id 995 | zetteldeft-link-suffix 996 | " ")) 997 | (insert (zetteldeft--lift-file-title file) 998 | "\n"))) 999 | 1000 | (defun zetteldeft-list-links (str &optional missing sort) 1001 | "Crude attempt to automate `zetteldeft-insert-list-links-missing'. 1002 | Meant to be called from an Org source block. 1003 | Replaces the list below the source block. 1004 | When MISSING is t, use `zetteldeft-insert-list-links-missing'. 1005 | When SORT is t, sort the list with most recent at top." 1006 | (save-excursion 1007 | (org-forward-element) 1008 | ; Delete any existing list 1009 | (when (org-in-item-p) 1010 | (delete-region (point) (org-end-of-item-list))) 1011 | ; New line & insert links 1012 | (if missing 1013 | (progn (save-buffer) 1014 | (deft-refresh) 1015 | (zetteldeft-insert-list-links-missing str)) 1016 | (zetteldeft-insert-list-links str)) 1017 | (when sort 1018 | (org-backward-element) 1019 | (org-sort-list t ?A)))) 1020 | 1021 | (defun zetteldeft-insert-list-links-block (str) 1022 | "Prompt for a STR to search, and insert an org-mode 1023 | source block that calls `zetteldeft-list-links'. 1024 | Also include the list of links below the block. 1025 | When called with a prefix, make use of the missing links 1026 | functions." 1027 | (interactive 1028 | (list (read-string "Search: "))) 1029 | (newline) 1030 | (insert "#+BEGIN_SRC emacs-lisp :results silent\n" 1031 | "(zetteldeft-list-links \"" str"\"") 1032 | (when current-prefix-arg (insert " t")) 1033 | (insert ")\n" 1034 | "#+END_SRC\n\n") 1035 | (if current-prefix-arg 1036 | (progn (save-buffer) 1037 | (deft-refresh) 1038 | (zetteldeft-insert-list-links-missing str)) 1039 | (zetteldeft-insert-list-links str))) 1040 | 1041 | (defun zetteldeft-org-dblock-insert-links (string) 1042 | "Insert an Org Dynamic Block of the `zetteldeft-links' type. 1043 | 1044 | The Dynamic Block takes the following parameters: 1045 | - :search 'string', the search string 1046 | - :sort t, to sort the results 1047 | - :missing-only t, to only include missing links. 1048 | " 1049 | (interactive 1050 | (list (read-string "Search: "))) 1051 | (org-create-dblock (list :name "zetteldeft-links" :search string)) 1052 | (org-update-dblock)) 1053 | 1054 | (with-eval-after-load 'org-mode 1055 | (org-dynamic-block-define "zetteldeft-links" 1056 | 'zetteldeft-org-dblock-insert-links)) 1057 | 1058 | (defun org-dblock-write:zetteldeft-links (params) 1059 | "Fill the zetteldeft search Org Dynamic Block with contents." 1060 | (let ((string (plist-get params :search)) 1061 | (missing-only (plist-get params :missing-only)) 1062 | (sort (plist-get params :sort))) 1063 | (if missing-only 1064 | (zetteldeft-insert-list-links-missing string) 1065 | (zetteldeft-insert-list-links string)) 1066 | (when sort 1067 | (org-backward-element) 1068 | (org-sort-list t ?A)))) 1069 | 1070 | (defcustom zetteldeft-export-tmp-dir 1071 | (expand-file-name "zetteldeft/tmp/" user-emacs-directory) 1072 | "Temporary directory for Zetteldeft export") 1073 | 1074 | (defun zetteldeft--export-prepare-tmp-notes (&optional ignored) 1075 | "Copy Zetteldeft files and prepare for export." 1076 | (delete-directory zetteldeft-export-tmp-dir t t) 1077 | (make-directory zetteldeft-export-tmp-dir t) 1078 | (deft-refresh) 1079 | (message 1080 | "Zetteldeft preparing notes for export at %s" 1081 | zetteldeft-export-tmp-dir) 1082 | (dolist (file (deft-find-all-files)) 1083 | (zetteldeft--export-prepare-file file)) 1084 | (message "Zetteldeft notes copy finished.")) 1085 | 1086 | (defun zetteldeft--export-prepare-file (zdFile) 1087 | "Prepare ZDFILE for export. 1088 | Copy its contents to `zetteldeftd-export-tmp-dir' and replace links with Org 1089 | file links. ZDFILE should be the path to the file." 1090 | (with-temp-file (expand-file-name 1091 | (file-name-nondirectory zdFile) 1092 | zetteldeft-export-tmp-dir) 1093 | (insert-file-contents zdFile) 1094 | (while (re-search-forward (zetteldeft--link-regex) nil t) 1095 | (let ((zdLink (match-string 0))) 1096 | (delete-region (point) 1097 | (re-search-backward (zetteldeft--link-regex))) 1098 | (let ((filePath (or (zetteldeft--id-to-full-path 1099 | (zetteldeft--lift-id zdLink)) 1100 | ; When ID doesn't return a file (a dead link) 1101 | ; use empty string 1102 | ""))) 1103 | (insert 1104 | (org-make-link-string 1105 | (format "./%s" (file-name-nondirectory filePath)) 1106 | zdLink))))))) 1107 | 1108 | ;;;###autoload 1109 | (defun zetteldeft-org-search-include (zdSrch) 1110 | "Insert `org-mode' syntax to include all files containing ZDSRCH. 1111 | Prompt for search string when called interactively." 1112 | (interactive (list (read-string "tag (include the #): "))) 1113 | (dolist (zdFile (zetteldeft--get-file-list zdSrch)) 1114 | (zetteldeft--org-include-file zdFile))) 1115 | 1116 | ;;;###autoload 1117 | (defun zetteldeft-org-search-insert (zdSrch) 1118 | "Insert the contents of all files containing ZDSRCH. 1119 | Files are separated by `org-mode' headers with corresponding titles. 1120 | Prompt for search string when called interactively." 1121 | (interactive (list (read-string "Search term: "))) 1122 | (dolist (zdFile (zetteldeft--get-file-list zdSrch)) 1123 | (zetteldeft--org-insert-file zdFile))) 1124 | 1125 | (defun zetteldeft--file-contents (zdFile &optional removeLines) 1126 | "Insert file contents of a zetteldeft note. 1127 | ZDFILE should be a full path to a note. 1128 | 1129 | Optional: leave out first REMOVELINES lines." 1130 | (with-temp-buffer 1131 | (insert-file-contents zdFile) 1132 | (when removeLines 1133 | (kill-whole-line removeLines)) 1134 | (buffer-string))) 1135 | 1136 | (defun zetteldeft--org-include-file (zdFile) 1137 | "Insert code to include org file ZDFILE." 1138 | (insert 1139 | ;; Insert org-mode title 1140 | "* " (zetteldeft--lift-file-title zdFile) "\n" 1141 | ;; Insert #+INCLUDE: "file.org" :lines 2- 1142 | "#+INCLUDE: \"" zdFile "\" :lines \"2-\"\n\n")) 1143 | 1144 | (defun zetteldeft--org-insert-file (zdFile) 1145 | "Insert title and contents of ZDFILE." 1146 | (insert 1147 | ;; Insert org-mode title 1148 | "\n* " (zetteldeft--lift-file-title zdFile) "\n\n" 1149 | ;; Insert file contents (without the first 3 lines) 1150 | (zetteldeft--file-contents zdFile 3))) 1151 | 1152 | (defcustom zetteldeft-graph-syntax-begin 1153 | "#+BEGIN_SRC dot :file ./graph.pdf :cmdline -Kfdp -Tpdf 1154 | \n graph {\n" 1155 | "Syntax to be included at the start of the zetteldeft graph." 1156 | :type 'string 1157 | :group 'zetteldeft) 1158 | 1159 | (defcustom zetteldeft-graph-syntax-end 1160 | "} \n#+END_SRC\n" 1161 | "Syntax to be included at the end of the zetteldeft graph." 1162 | :type 'string 1163 | :group 'zetteldeft) 1164 | 1165 | (defvar zetteldeft--graph-links) 1166 | 1167 | ;;;###autoload 1168 | (defun zetteldeft-org-graph-search (str) 1169 | "Insert org source block for graph with zd search results. 1170 | STR should be the search the resulting notes of which should be included in the graph." 1171 | (interactive (list (read-string "search string: "))) 1172 | (setq zetteldeft--graph-links (list)) 1173 | (let ((zdList (zetteldeft--get-file-list str))) 1174 | (insert zetteldeft-graph-syntax-begin) 1175 | (insert "\n // links\n") 1176 | (dolist (oneFile zdList) 1177 | (insert "\n") 1178 | (zetteldeft--graph-insert-links oneFile)) 1179 | (zetteldeft--graph-insert-all-titles)) 1180 | (insert zetteldeft-graph-syntax-end)) 1181 | 1182 | ;;;###autoload 1183 | (defun zetteldeft-org-graph-note (deftFile) 1184 | "Create a graph starting from note DEFTFILE." 1185 | (interactive (list 1186 | (completing-read "Note to start graph from: " 1187 | (deft-find-all-files)))) 1188 | (setq zetteldeft--graph-links (list)) 1189 | (insert zetteldeft-graph-syntax-begin) 1190 | (insert "\n // base note and links \n") 1191 | (zetteldeft--graph-insert-links deftFile) 1192 | (zetteldeft--graph-insert-additional-links) 1193 | (zetteldeft--graph-insert-all-titles) 1194 | (insert zetteldeft-graph-syntax-end)) 1195 | 1196 | (defun zetteldeft--graph-insert-links (deftFile) 1197 | "Insert links in DEFTFILE in dot graph syntax on a single line. 1198 | Any inserted ID is also stored in `zetteldeft--graph-links'." 1199 | (let ((zdId (zetteldeft--lift-id deftFile))) 1200 | (when zdId 1201 | (insert " \"" zdId "\" -- {") 1202 | (dolist (oneLink (zetteldeft--extract-links deftFile)) 1203 | (zetteldeft--graph-store-link oneLink t) 1204 | (insert "\"" oneLink "\" ")) 1205 | (insert "}\n") 1206 | (zetteldeft--graph-store-link deftFile)))) 1207 | 1208 | (defun zetteldeft--graph-insert-title (deftFile) 1209 | "Insert the DEFTFILE title definition in a one line dot graph format." 1210 | (let ((zdTitle 1211 | (replace-regexp-in-string "\"" "" 1212 | (zetteldeft--lift-file-title deftFile))) 1213 | (zdId (zetteldeft--lift-id deftFile))) 1214 | (when zdId 1215 | (insert " \"" zdId "\"" 1216 | " [label = \"" zdTitle " (" 1217 | zetteldeft-link-indicator zdId zetteldeft-link-suffix ")\"") 1218 | (insert "]" "\n")) 1219 | (zetteldeft--graph-store-link deftFile))) 1220 | 1221 | (defun zetteldeft--graph-store-link (deftFile &optional idToFile) 1222 | "Push DEFTFILE to zetteldeft--graph-links unless it's already there. 1223 | When IDTOFILE is non-nil, DEFTFILE is considered an id 1224 | and the the function first looks for the corresponding file." 1225 | (when idToFile 1226 | (let ((deft-filter-only-filenames t)) 1227 | (progn 1228 | (deft-filter deftFile t) 1229 | (setq deftFile (car deft-current-files))))) 1230 | (unless (member deftFile zetteldeft--graph-links) 1231 | (push deftFile zetteldeft--graph-links))) 1232 | 1233 | (defun zetteldeft--graph-insert-additional-links () 1234 | "Insert rest of `zetteldeft--graph-links'." 1235 | (setq zetteldeft--graph-links (cdr zetteldeft--graph-links)) 1236 | (dolist (oneFile zetteldeft--graph-links) 1237 | (zetteldeft--graph-insert-links oneFile))) 1238 | 1239 | (defun zetteldeft--graph-insert-all-titles () 1240 | "Insert graphviz title lines. 1241 | Does this for all links stored in `zetteldeft--graph-links'." 1242 | (insert "\n // titles \n") 1243 | (dolist (oneLink zetteldeft--graph-links) 1244 | ;; Sometimes, a 'nil' list item is present. Ignore those. 1245 | (when oneLink 1246 | (zetteldeft--graph-insert-title oneLink)))) 1247 | 1248 | ;;;###autoload 1249 | (defun zetteldeft-set-classic-keybindings () 1250 | "Sets global keybindings for `zetteldeft'." 1251 | (interactive) 1252 | (define-prefix-command 'zetteldeft-prefix) 1253 | (global-set-key (kbd "C-c d") 'zetteldeft-prefix) 1254 | (global-set-key (kbd "C-c d d") 'deft) 1255 | (global-set-key (kbd "C-c d D") 'zetteldeft-deft-new-search) 1256 | (global-set-key (kbd "C-c d R") 'deft-refresh) 1257 | (global-set-key (kbd "C-c d s") 'zetteldeft-search-at-point) 1258 | (global-set-key (kbd "C-c d c") 'zetteldeft-search-current-id) 1259 | (global-set-key (kbd "C-c d f") 'zetteldeft-follow-link) 1260 | (global-set-key (kbd "C-c d F") 'zetteldeft-avy-file-search-ace-window) 1261 | (global-set-key (kbd "C-c d .") 'zetteldeft-browse) 1262 | (global-set-key (kbd "C-c d h") 'zetteldeft-go-home) 1263 | (global-set-key (kbd "C-c d l") 'zetteldeft-avy-link-search) 1264 | (global-set-key (kbd "C-c d t") 'zetteldeft-avy-tag-search) 1265 | (global-set-key (kbd "C-c d T") 'zetteldeft-tag-buffer) 1266 | (global-set-key (kbd "C-c d /") 'zetteldeft-search-tag) 1267 | (global-set-key (kbd "C-c d i") 'zetteldeft-find-file-id-insert) 1268 | (global-set-key (kbd "C-c d C-i") 'zetteldeft-full-search-id-insert) 1269 | (global-set-key (kbd "C-c d I") 'zetteldeft-find-file-full-title-insert) 1270 | (global-set-key (kbd "C-c d C-I") 'zetteldeft-full-search-full-title-insert) 1271 | (global-set-key (kbd "C-c d o") 'zetteldeft-find-file) 1272 | (global-set-key (kbd "C-c d C-o") 'zetteldeft-full-search-find-file) 1273 | (global-set-key (kbd "C-c d n") 'zetteldeft-new-file) 1274 | (global-set-key (kbd "C-c d N") 'zetteldeft-new-file-and-link) 1275 | (global-set-key (kbd "C-c d B") 'zetteldeft-new-file-and-backlink) 1276 | (global-set-key (kbd "C-c d b") 'zetteldeft-backlink-add) 1277 | (global-set-key (kbd "C-c d r") 'zetteldeft-file-rename) 1278 | (global-set-key (kbd "C-c d x") 'zetteldeft-count-words) 1279 | (global-set-key (kbd "C-c d #") 'zetteldeft-tag-insert) 1280 | (global-set-key (kbd "C-c d $") 'zetteldeft-tag-remove)) 1281 | 1282 | (provide 'zetteldeft) 1283 | ;;; zetteldeft.el ends here 1284 | --------------------------------------------------------------------------------