mirror of
https://github.com/org-roam/org-roam
synced 2025-08-03 12:27:23 -05:00
Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc01cf346e | |||
eaf99cba03 | |||
65a2cb6efd | |||
bc12d1cf04 | |||
a86d82b20e | |||
d4c875b53b | |||
2159b6a846 | |||
1db4c22950 | |||
9c0f030ffd | |||
983d7a8798 | |||
910b37268e | |||
d39556a78b | |||
167553b8ee | |||
76affe177a | |||
e96685b1a9 | |||
aef71f1623 | |||
d913447939 | |||
47e83f7d3f | |||
023bcce867 | |||
4f6eb285bf | |||
8c81104816 | |||
7602b8c48d | |||
c6797cbd75 | |||
440461a90b | |||
4d423a916e | |||
b184cdaef0 | |||
b2cc997976 | |||
bc5c41d212 | |||
7c83a84db3 | |||
56c47fbff8 | |||
0d235686f4 | |||
ac2044b84b | |||
cffa0bd201 | |||
bd8b5587f5 | |||
b937bc9655 | |||
a7cf48ea89 | |||
46327991ef | |||
a4da8f32bf | |||
5d483f2d4d | |||
09fd41ce24 | |||
a0c4abf579 | |||
cbf1b585ac | |||
66cd5b6226 | |||
5348654a7e | |||
87d7c07e87 | |||
02fda3adb1 | |||
82bd6c6cda | |||
e8d3516fa8 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,4 +12,4 @@
|
||||
/doc/mimetype
|
||||
/doc/stats/
|
||||
/config.mk
|
||||
/doc/manual/
|
||||
/doc/manual.html
|
||||
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,5 +1,33 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.3 (13-11-2020)
|
||||
|
||||
Primarily a stabilization and bug-fix release.
|
||||
|
||||
Org-roam-dailies has also been revamped to include new features, see [this video](https://www.youtube.com/watch?v=1q9x2aZCJJ4) for a quick overview.
|
||||
|
||||
### Added
|
||||
- [#978](https://github.com/org-roam/org-roam/pull/978) Revamp org-roam-dailies
|
||||
- [#1183](https://github.com/org-roam/org-roam/pull/1183) Interactive functions for managing aliases and tags in Org-roam file, namely `org-roam-alias-add`, `org-roam-alias-delete`, `org-roam-tag-add`, and `org-roam-tag-delete`.
|
||||
- [#1215](https://github.com/org-roam/org-roam/pull/1215) Multiple `ROAM_KEY` keywords can now be specified in one file. This allows bibliographical entries to share the same note file.
|
||||
- [#1238](https://github.com/org-roam/org-roam/pull/1238) Add `org-roam-prefer-id-links` variable to select linking method
|
||||
- [#1239](https://github.com/org-roam/org-roam/pull/1239) Allow `org-roam-protocol` to capture the webpage's selection, and add a toggle for storing the links to the pages
|
||||
- [#1264](https://github.com/org-roam/org-roam/pull/1264) add `org-roam-db-update-method` to control when the cache is rebuilt.
|
||||
|
||||
### Changed
|
||||
- [#1264](https://github.com/org-roam/org-roam/pull/1264) renamed `org-roam-update-db-idle-seconds` to `org-roam-db-idle-idle-seconds`
|
||||
|
||||
### Fixed
|
||||
- [#1074](https://github.com/org-roam/org-roam/issues/1074) fix `org-roam--extract-links` to handle content boundaries.
|
||||
- [#1193](https://github.com/org-roam/org-roam/issues/1193) fix `org-roam-db-build-cache` by not killing temporary buffer in `org-roam--extract-links`.
|
||||
- [#1195](https://github.com/org-roam/org-roam/issues/1195) fix ID face showing as invalid if within Org ID files, but not Org-roam's.
|
||||
- [#1199](https://github.com/org-roam/org-roam/issues/1199) make Org-roam link insertions respect `org-roam-link-title-format` everywhere.
|
||||
- [#1201](https://github.com/org-roam/org-roam/issues/1201) fix `org-roam-db-build-cache` failing in scenarios involving duplicate IDs and deleted files.
|
||||
- [#1226](https://github.com/org-roam/org-roam/issues/1226) only update relative path of file links
|
||||
- [#1232](https://github.com/org-roam/org-roam/issues/1232) fix incorrect title extractions from narrowed buffers
|
||||
- [#1233](https://github.com/org-roam/org-roam/issues/1233) fixes bug where descriptive file links become plain links during update for relative paths
|
||||
- [#1252](https://github.com/org-roam/org-roam/issues/1252) respect original link type during automatic replacement
|
||||
|
||||
## 1.2.2 (06-10-2020)
|
||||
|
||||
In this release we support fuzzy links of the form `[[roam:Title]]`, `[[roam:*Headline]]` and `[[roam:Title*Headline]]`. Completion for these fuzzy links is supported via `completion-at-point`.
|
||||
|
@ -121,7 +121,7 @@ General Public License, Version 3
|
||||
[roamresearch]: https://www.roamresearch.com/
|
||||
[org]: https://orgmode.org/
|
||||
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
|
||||
[docs]: https://www.orgroam.com/manual/
|
||||
[docs]: https://www.orgroam.com/manual.html
|
||||
[discourse]: https://org-roam.discourse.group/
|
||||
[slack]: https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg
|
||||
[issues]: https://github.com/org-roam/org-roam/issues
|
||||
|
@ -28,11 +28,8 @@ dir: org-roam.info
|
||||
@$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) $<
|
||||
|
||||
html-dir:
|
||||
@printf "Generating org-roam/*.html\n"
|
||||
@$(MAKEINFO) --html $(MANUAL_HTML_ARGS) org-roam.texi
|
||||
mv org-roam manual
|
||||
cp -r assets manual
|
||||
cp -r images manual
|
||||
@$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) org-roam.texi
|
||||
mv org-roam.html manual.html
|
||||
|
||||
%.pdf: %.texi
|
||||
@printf "Generating $@\n"
|
||||
|
@ -1,442 +1,59 @@
|
||||
/* Import Inter font */
|
||||
/* More info at https://github.com/xz/fonts */
|
||||
@import url("https://fonts.xz.style/serve/inter.css");
|
||||
|
||||
:root {
|
||||
--nc-font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif,
|
||||
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
--nc-font-mono: "Courier New", Courier, "Ubuntu Mono", "Liberation Mono",
|
||||
monospace;
|
||||
--nc-tx-1: #000000;
|
||||
--nc-tx-2: #1a1a1a;
|
||||
--nc-bg-1: #ffffff;
|
||||
--nc-bg-2: #f6f8fa;
|
||||
--nc-bg-3: #e5e7eb;
|
||||
--nc-lk-1: #0070f3;
|
||||
--nc-lk-2: #0366d6;
|
||||
--nc-lk-tx: #ffffff;
|
||||
--nc-ac-1: #79ffe1;
|
||||
--nc-ac-tx: #0c4047;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--nc-tx-1: #ffffff;
|
||||
--nc-tx-2: #eeeeee;
|
||||
--nc-bg-1: #000000;
|
||||
--nc-bg-2: #111111;
|
||||
--nc-bg-3: #222222;
|
||||
--nc-lk-1: #3291ff;
|
||||
--nc-lk-2: #0070f3;
|
||||
--nc-lk-tx: #ffffff;
|
||||
--nc-ac-1: #7928ca;
|
||||
--nc-ac-tx: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
/* Reset margins and padding */
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
address,
|
||||
area,
|
||||
article,
|
||||
aside,
|
||||
audio,
|
||||
blockquote,
|
||||
datalist,
|
||||
details,
|
||||
dl,
|
||||
fieldset,
|
||||
figure,
|
||||
form,
|
||||
input,
|
||||
iframe,
|
||||
img,
|
||||
meter,
|
||||
nav,
|
||||
ol,
|
||||
optgroup,
|
||||
option,
|
||||
output,
|
||||
p,
|
||||
pre,
|
||||
progress,
|
||||
ruby,
|
||||
section,
|
||||
table,
|
||||
textarea,
|
||||
ul,
|
||||
video {
|
||||
/* Margins for most elements */
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
html,
|
||||
input,
|
||||
select,
|
||||
button {
|
||||
/* Set body font family and some finicky elements */
|
||||
font-family: var(--nc-font-sans);
|
||||
--border: #526980;
|
||||
--code: #007;
|
||||
}
|
||||
|
||||
body {
|
||||
/* Center body in page */
|
||||
margin: 0 auto;
|
||||
max-width: 750px;
|
||||
padding: 2rem;
|
||||
border-radius: 6px;
|
||||
overflow-x: hidden;
|
||||
background: var(--nc-bg-1);
|
||||
|
||||
/* Main body text */
|
||||
color: var(--nc-tx-2);
|
||||
font-size: 1.03rem;
|
||||
line-height: 1.5;
|
||||
margin: 5ex 10ex;
|
||||
max-width: 80ex;
|
||||
line-height: 1.5;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
::selection {
|
||||
/* Set background color for selected text */
|
||||
background: var(--nc-ac-1);
|
||||
color: var(--nc-ac-tx);
|
||||
h1, h2, h3 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1;
|
||||
color: var(--nc-tx-1);
|
||||
padding-top: 0.875rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
color: var(--nc-tx-1);
|
||||
padding-bottom: 2px;
|
||||
margin-bottom: 8px;
|
||||
border-bottom: 1px solid var(--nc-bg-2);
|
||||
}
|
||||
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.25rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.85rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.55rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--nc-lk-1);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--nc-lk-2);
|
||||
}
|
||||
|
||||
abbr:hover {
|
||||
/* Set the '?' cursor while hovering an abbreviation */
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 1.5rem;
|
||||
background: var(--nc-bg-2);
|
||||
border-left: 5px solid var(--nc-bg-3);
|
||||
}
|
||||
|
||||
abbr {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
blockquote *:last-child {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
background: var(--nc-bg-2);
|
||||
border-bottom: 1px solid var(--nc-bg-3);
|
||||
padding: 2rem 1.5rem;
|
||||
|
||||
/* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */
|
||||
|
||||
margin: -2rem calc(0px - (50vw - 50%)) 2rem;
|
||||
|
||||
/* Shorthand for:
|
||||
|
||||
margin-top: -2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
margin-left: calc(0px - (50vw - 50%));
|
||||
margin-right: calc(0px - (50vw - 50%)); */
|
||||
|
||||
padding-left: calc(50vw - 50%);
|
||||
padding-right: calc(50vw - 50%);
|
||||
}
|
||||
|
||||
header h1,
|
||||
header h2,
|
||||
header h3 {
|
||||
padding-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
header > *:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
header > *:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button,
|
||||
button,
|
||||
input[type="submit"],
|
||||
input[type="reset"],
|
||||
input[type="button"] {
|
||||
font-size: 1rem;
|
||||
display: inline-block;
|
||||
padding: 6px 12px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
background: var(--nc-lk-1);
|
||||
color: var(--nc-lk-tx);
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
color: var(--nc-lk-tx);
|
||||
}
|
||||
|
||||
.button[disabled],
|
||||
button[disabled],
|
||||
input[type="submit"][disabled],
|
||||
input[type="reset"][disabled],
|
||||
input[type="button"][disabled] {
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
|
||||
/* Set the [X] cursor while hovering a disabled link */
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.button:focus,
|
||||
.button:hover,
|
||||
button:focus,
|
||||
button:hover,
|
||||
input[type="submit"]:focus,
|
||||
input[type="submit"]:hover,
|
||||
input[type="reset"]:focus,
|
||||
input[type="reset"]:hover,
|
||||
input[type="button"]:focus,
|
||||
input[type="button"]:hover {
|
||||
background: var(--nc-lk-2);
|
||||
}
|
||||
|
||||
code,
|
||||
pre,
|
||||
kbd,
|
||||
samp {
|
||||
/* Set the font family for monospaced elements */
|
||||
font-family: var(--nc-font-mono);
|
||||
}
|
||||
|
||||
code,
|
||||
samp,
|
||||
kbd,
|
||||
pre {
|
||||
/* The main preformatted style. This is changed slightly across different cases. */
|
||||
background: var(--nc-bg-2);
|
||||
border: 1px solid var(--nc-bg-3);
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
kbd {
|
||||
/* Makes the kbd element look like a keyboard key */
|
||||
border-bottom: 3px solid var(--nc-bg-3);
|
||||
pre, code {
|
||||
font-family: x, monospace;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1rem 1.4rem;
|
||||
max-width: 100%;
|
||||
overflow: auto;
|
||||
padding: 1ex;
|
||||
background: #eee;
|
||||
border: solid 1px #ddd;
|
||||
min-width: 0;
|
||||
font-size: 80%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
pre code {
|
||||
/* When <code> is in a <pre>, reset it's formatting to blend in */
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
code pre {
|
||||
/* When <pre> is in a <code>, reset it's formatting to blend in */
|
||||
display: inline;
|
||||
background: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
details {
|
||||
/* Make the <details> look more "clickable" */
|
||||
padding: 0.6rem 1rem;
|
||||
background: var(--nc-bg-2);
|
||||
border: 1px solid var(--nc-bg-3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
summary {
|
||||
/* Makes the <summary> look more like a "clickable" link with the pointer cursor */
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
details[open] {
|
||||
/* Adjust the <details> padding while open */
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
details[open] summary {
|
||||
/* Adjust the <details> padding while open */
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
details[open] > *:last-child {
|
||||
/* Resets the bottom margin of the last element in the <details> while <details> is opened. This prevents double margins/paddings. */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd::before {
|
||||
/* Add an arrow to data table definitions */
|
||||
content: "→ ";
|
||||
}
|
||||
|
||||
hr {
|
||||
/* Reset the border of the <hr> separator, then set a better line */
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--nc-bg-3);
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
margin-top: 1rem;
|
||||
padding: 2rem;
|
||||
border: 1px solid var(--nc-bg-3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: auto 0.5rem;
|
||||
}
|
||||
|
||||
textarea {
|
||||
/* Don't let the <textarea> extend off the screen naturally or when dragged by the user */
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul {
|
||||
/* Replace the browser default padding */
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 0.4rem;
|
||||
}
|
||||
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol,
|
||||
ol ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
mark {
|
||||
padding: 3px 6px;
|
||||
background: var(--nc-ac-1);
|
||||
color: var(--nc-ac-tx);
|
||||
}
|
||||
|
||||
textarea,
|
||||
select,
|
||||
input {
|
||||
padding: 6px 12px;
|
||||
margin-bottom: 0.5rem;
|
||||
background: var(--nc-bg-2);
|
||||
color: var(--nc-tx-2);
|
||||
|
||||
/* Set a border of the same color as the main background. It isn't visible on idle, but prevents the cell from growing in size when a darker border is set on focus. */
|
||||
border: 1px solid var(--nc-bg-2);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea:focus,
|
||||
select:focus,
|
||||
input[type]:focus {
|
||||
border: 1px solid var(--nc-bg-3);
|
||||
|
||||
/* Reset any browser default outlines */
|
||||
outline: 0;
|
||||
code {
|
||||
color: var(--code);
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Customizations */
|
||||
.menu-comment {
|
||||
font-weight: bold;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 1.2rem;
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
thead {
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
tfoot {
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-left: 1rem;
|
||||
font-style: italic;
|
||||
font-family: serif;
|
||||
border-left: 3px solid;
|
||||
border-left-color: currentcolor;
|
||||
border-color: var(--text-color);
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
@ -93,7 +93,7 @@
|
||||
You can find us on Discourse and Slack.
|
||||
|
||||
<ul>
|
||||
<li>Read our documentation within Emacs, or on the <a href="https://www.orgroam.com/manual/">Online Manual</a></li>
|
||||
<li>Read our documentation within Emacs, or on the <a href="https://www.orgroam.com/manual.html">Online Manual</a></li>
|
||||
<li>Participate in our forum discussions on <a href="https://org-roam.discourse.group">Discourse</a></li>
|
||||
<li>Chat with us on <a href="https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg">Slack</a></li>
|
||||
</ul>
|
||||
@ -120,13 +120,6 @@
|
||||
>org-roam-server</a
|
||||
>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a
|
||||
class="content footer-links"
|
||||
href="https://github.com/org-roam/company-org-roam"
|
||||
>company-org-roam</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
|
606
doc/org-roam.org
606
doc/org-roam.org
@ -1,20 +1,20 @@
|
||||
#+title: Org-roam User Manual
|
||||
:PREAMBLE:
|
||||
#+author: Jethro Kuan
|
||||
#+email: jethrokuan95@gmail.com
|
||||
#+date: 2020-2020
|
||||
#+language: en
|
||||
|
||||
#+texinfo_deffn: t
|
||||
#+texinfo_dir_category: Emacs
|
||||
#+texinfo_dir_title: Org-roam: (org-roam).
|
||||
#+texinfo_dir_desc: Rudimentary Roam Replica for Emacs.
|
||||
#+subtitle: for version 1.2.2
|
||||
#+subtitle: for version 1.2.3
|
||||
|
||||
#+options: H:4 num:3 toc:2 creator:t
|
||||
#+options: H:4 num:3 toc:nil creator:t ':t
|
||||
#+property: header-args :eval never
|
||||
#+texinfo: @noindent
|
||||
|
||||
This manual is for Org-roam version 1.2.2.
|
||||
This manual is for Org-roam version 1.2.3.
|
||||
|
||||
#+BEGIN_QUOTE
|
||||
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
@ -29,20 +29,19 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
#+END_QUOTE
|
||||
|
||||
:END:
|
||||
|
||||
* Introduction
|
||||
|
||||
Org-roam is a [[https://roamresearch.com/][Roam Research]] replica built around the
|
||||
all-powerful [[https://orgmode.org/][Org-mode]].
|
||||
Org-roam is a tool for network thought. It reproduces some of [[https://roamresearch.com/][Roam
|
||||
Research's]] [fn:roam] features within the all-powerful [[https://orgmode.org/][Org-mode]].
|
||||
|
||||
Org-roam is a solution for effortless non-hierarchical note-taking
|
||||
with Org-mode. With Org-roam, notes flow naturally, making note-taking
|
||||
fun and easy. Org-roam should also work as a plug-and-play solution
|
||||
for anyone already using Org-mode for their personal wiki.
|
||||
Org-roam is a solution for effortless non-hierarchical note-taking with
|
||||
Org-mode. With Org-roam, notes flow naturally, making note-taking fun and easy.
|
||||
Org-roam keeps closely to Org syntax, and will work for anyone already using
|
||||
Org-mode for their personal wiki.
|
||||
|
||||
To understand more about Roam, a collection of links are available in
|
||||
[[*Note-taking Workflows][Note-taking Workflows]].
|
||||
Org-roam gains its superpowers by leveraging the mature ecosystem around
|
||||
Org-mode. For example, it has first-class support for [[https://github.com/jkitchin/org-ref][org-ref]] for citation
|
||||
management.
|
||||
|
||||
Org-roam aims to implement the core features of Roam, leveraging the
|
||||
mature ecosystem around Org-mode where possible. Eventually, we hope
|
||||
@ -50,11 +49,23 @@ to further introduce features enabled by the Emacs ecosystem.
|
||||
|
||||
Org-roam provides several benefits over other tooling:
|
||||
|
||||
- Privacy and Security :: Edit your personal wiki completely offline, entirely in your control. Encrypt your notes with GPG.
|
||||
- Longevity of Plain Text :: Unlike web solutions like Roam research, the notes are first and foremost plain Org-mode files -- Org-roam simply builds up an auxilliary database to give the personal wiki superpowers. Having your notes in plain-text is crucial for the longevity of your wiki. Never have to worry about proprietary web solutions being taken down. Edit your plain-text notes in notepad if all other editors cease to exist
|
||||
- Free and Open Source :: Org-roam is free and open-source, which means that if you feel unhappy with any part of Org-roam, you may choose to extend Org-roam, or open a PR.
|
||||
- Leverages the Org-mode ecosystem :: Over the years, Emacs and Org-mode has developed into a mature system for plain-text organization. Building upon Org-mode already puts Org-roam light-years ahead of many other solutions.
|
||||
- Built on Emacs :: Emacs is also a fantastic interface for editing text, and we can inherit many of the powerful text-navigation and editing packages available to Emacs.
|
||||
- *Privacy and Security:* Keep your personal wiki entirely offline and in your
|
||||
control. Encrypt your notes with GPG.
|
||||
- *Longevity of Plain Text:* Unlike web solutions like Roam Research, the notes
|
||||
are first and foremost plain Org-mode files -- Org-roam simply builds an
|
||||
auxiliary database to give the personal wiki superpowers. Having your notes
|
||||
in plain-text is crucial for the longevity of your wiki. Never have to worry
|
||||
about proprietary web solutions being taken down. The notes are still
|
||||
functional even if Org-roam ceases to exist.
|
||||
- *Free and Open Source:* Org-roam is free and open-source, which means that if
|
||||
you feel unhappy with any part of Org-roam, you may choose to extend Org-roam,
|
||||
or open a pull request.
|
||||
- *Leverage the Org-mode ecosystem:* Over the years, Emacs and Org-mode has
|
||||
developed into a mature system for plain-text organization. Building upon
|
||||
Org-mode already puts Org-roam light-years ahead of many other solutions.
|
||||
- *Built on Emacs:* Emacs is also a fantastic interface for editing text, and we
|
||||
can inherit many of the powerful text-navigation and editing packages
|
||||
available to Emacs.
|
||||
|
||||
* Target Audience
|
||||
|
||||
@ -94,38 +105,44 @@ Org-roam provides utilities for maintaining a digital slip-box. This section
|
||||
aims to provide a brief introduction to the "slip-box", or "Zettelkasten"
|
||||
method. By providing some background on the method, we hope that the design
|
||||
decisions of Org-roam will become clear, and that will aid in using Org-roam
|
||||
appropriately. In this section we will also introduce terms commonly used within
|
||||
the Zettelkasten community, which will also commonly appear in the Org-roam
|
||||
forums and channels of discussion.
|
||||
appropriately. In this section we will introduce terms commonly used within the
|
||||
Zettelkasten community and the Org-roam forums.
|
||||
|
||||
The Zettelkasten method of note-taking is designed to increase research
|
||||
productivity: in particular, it acts as a research partner, where conversations
|
||||
with it may produce new and surprising lines of thought. This method is
|
||||
attributed to German sociologist Niklas Luhmann, who using the method had
|
||||
produced volumes of written works.
|
||||
The Zettelkasten is a personal tool for thinking and writing. It places heavy
|
||||
emphasis on connecting ideas, building up a web of thought. Hence, it is well
|
||||
suited for knowledge workers and intellectual tasks, such as conducting
|
||||
research. The Zettelkasten can act as a research partner, where conversations
|
||||
with it may produce new and surprising lines of thought.
|
||||
|
||||
In its paper form, the slip-box is simply a box of cards. These cards are small
|
||||
-- often only large enough to fit a single concept. The size limitation
|
||||
encourages ideas to be broken down into individual concepts. These ideas are
|
||||
explicitly linked together. The breakdown of ideas encourages tangential
|
||||
exploration of ideas, increasing the surface for thought. Making linking
|
||||
explicit between notes also encourages one to think about the connections
|
||||
between concepts.
|
||||
This method is attributed to German sociologist Niklas Luhmann, who using the
|
||||
method had produced volumes of written works. Luhmann's slip-box was simply a
|
||||
box of cards. These cards are small -- often only large enough to fit a single
|
||||
concept. The size limitation encourages ideas to be broken down into individual
|
||||
concepts. These ideas are explicitly linked together. The breakdown of ideas
|
||||
encourages tangential exploration of ideas, increasing the surface for thought.
|
||||
Making linking explicit between notes also encourages one to think about the
|
||||
connections between concepts.
|
||||
|
||||
At the corner of each note, Luhmann ascribed each note with an ordered ID,
|
||||
allowing him to link and jump between notes. In Org-roam, we simply use
|
||||
hyperlinks.
|
||||
|
||||
Org-roam is the slip-box, digitalized in Org-mode. Every zettel (card) is a
|
||||
plain-text, Org-mode file. These files are often placed in the same directory.
|
||||
In the same way one would maintain a paper slip-box, Org-roam makes it easy to
|
||||
create new zettels, pre-filling boilerplate content using a powerful templating
|
||||
system. Org-roam also facilitates the linking of zettels using Org-mode ~file:~
|
||||
links.
|
||||
plain-text, Org-mode file. In the same way one would maintain a paper slip-box,
|
||||
Org-roam makes it easy to create new zettels, pre-filling boilerplate content
|
||||
using a powerful templating system.
|
||||
|
||||
A slip-box requires a method of quickly capturing ideas. These are called
|
||||
** Fleeting notes
|
||||
|
||||
A slip-box requires a method for quickly capturing ideas. These are called
|
||||
*fleeting notes*: they are simple reminders of information or ideas that will
|
||||
need to be processed later on, or trashed. This is typically accomplished using
|
||||
~org-capture~ (see info:org#capture), or using Org-roam's daily notes
|
||||
functionality (see [[*Daily Notes][Daily Notes]]). This provides a central inbox for collecting
|
||||
functionality (see [[*Daily-notes][Daily-notes]]). This provides a central inbox for collecting
|
||||
thoughts, to be processed later into permanent notes.
|
||||
|
||||
** Permanent notes
|
||||
|
||||
Permanent notes are further split into two categories: *literature notes* and
|
||||
*concept notes*. Literature notes can be brief annotations on a particular
|
||||
source (e.g. book, website or paper), that you'd like to access later on.
|
||||
@ -197,9 +214,16 @@ Org-roam will then be autoloaded into Emacs.
|
||||
|
||||
** Installing from the Git Repository
|
||||
|
||||
You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like. This will give you access to the latest version hours or days before it appears on MELPA, and months (or more) before it is added to the Debian or Ubuntu repositories. This will also give you access to various developmental branches that may be available.
|
||||
You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like.
|
||||
This will give you access to the latest version hours or days before it appears
|
||||
on MELPA, and months (or more) before it is added to the Debian or Ubuntu
|
||||
repositories. This will also give you access to various developmental branches
|
||||
that may be available.
|
||||
|
||||
Note, however, that development version, and especially any feature branches, may not always be in working order. You'll need to be prepared to do some debugging, or to manually roll-back to working versions, if you install from GitHub.
|
||||
Note, however, that development version, and especially any feature branches,
|
||||
may not always be in working order. You'll need to be prepared to do some
|
||||
debugging, or to manually roll-back to working versions, if you install from
|
||||
GitHub.
|
||||
|
||||
Installing from GitHub requires that you clone the repository:
|
||||
|
||||
@ -209,14 +233,16 @@ git clone https://github.com/org-roam/org-roam.git /path/to/org/roam
|
||||
|
||||
where ~./path/to/org/roam~ is the location you will store your copy of the code.
|
||||
|
||||
Next, you need to add this location to your load path, and ~require~ the Org-roam library. Add the following code to your ~.emacs~:
|
||||
Next, you need to add this location to your load path, and ~require~ the
|
||||
Org-roam library. Add the following code to your ~.emacs~:
|
||||
|
||||
#+begin_src elisp
|
||||
(add-to-list 'load-path "/path/to/org/roam")
|
||||
(require 'org-roam)
|
||||
#+end_src
|
||||
|
||||
You now have Org-roam installed. However, you don't necessarily have the dependencies that it requires. These include:
|
||||
You now have Org-roam installed. However, you don't necessarily have the
|
||||
dependencies that it requires. These include:
|
||||
|
||||
- dash
|
||||
- f
|
||||
@ -225,22 +251,29 @@ You now have Org-roam installed. However, you don't necessarily have the depende
|
||||
- emacsql
|
||||
- emacsql-sqlite3
|
||||
|
||||
You can install this manually as well, or get the latest version from MELPA. You may wish to use [[https://github.com/jwiegley/use-package][use-package]], [[https://github.com/raxod502/straight.el][straight.el]], or some other tool or tools to help manage this.
|
||||
You can install this manually as well, or get the latest version from MELPA. You
|
||||
may wish to use [[https://github.com/jwiegley/use-package][use-package]], [[https://github.com/raxod502/straight.el][straight.el]] to help manage this.
|
||||
|
||||
If you would like to install the manual for access from Emacs' built-in Info system, you'll need to compile the .texi source file, and install it in an appropriate location.
|
||||
If you would like to install the manual for access from Emacs' built-in Info
|
||||
system, you'll need to compile the .texi source file, and install it in an
|
||||
appropriate location.
|
||||
|
||||
To compile the .texi source file, from a terminal navigate to the ~/doc~ subdirectory of the Org-roam repository, and run the following:
|
||||
To compile the .texi source file, from a terminal navigate to the ~/doc~
|
||||
subdirectory of the Org-roam repository, and run the following:
|
||||
|
||||
#+begin_src bash
|
||||
make infodir=/path/to/my/info/files install-info
|
||||
#+end_src
|
||||
|
||||
Where ~/path/to/my/info/files~ is the location where you keep info files. This target directory needs to be stored in the variable `Info-default-directory-list`. If you aren't using one of the default info locations, you can configure this with the following in your ~.emacs~ file:
|
||||
Where ~/path/to/my/info/files~ is the location where you keep info files. This
|
||||
target directory needs to be stored in the variable
|
||||
`Info-default-directory-list`. If you aren't using one of the default info
|
||||
locations, you can configure this with the following in your ~.emacs~ file:
|
||||
|
||||
#+begin_src elisp
|
||||
(require 'info)
|
||||
(add-to-list 'Info-default-directory-list
|
||||
"/path/to/my/info/files")
|
||||
(require 'info)
|
||||
(add-to-list 'Info-default-directory-list
|
||||
"/path/to/my/info/files")
|
||||
#+end_src
|
||||
|
||||
You can also use one of the default locations, such as:
|
||||
@ -249,9 +282,11 @@ You can also use one of the default locations, such as:
|
||||
- /usr/share/info/
|
||||
- /usr/local/share/info/
|
||||
|
||||
If you do this, you'll need to make sure you have write-access to that location, or run the above ~make~ command as root.
|
||||
If you do this, you'll need to make sure you have write-access to that location,
|
||||
or run the above ~make~ command as root.
|
||||
|
||||
Now that the info file is ready, you need to add it to the corresponding ~dir~ file:
|
||||
Now that the info file is ready, you need to add it to the corresponding ~dir~
|
||||
file:
|
||||
|
||||
#+begin_src bash
|
||||
install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir
|
||||
@ -289,10 +324,10 @@ requires a radical change in your current note-taking workflow. To understand
|
||||
more about the methods and madness, see [[*Note-taking Workflows][Note-taking Workflows]].
|
||||
|
||||
To first start using Org-roam, one needs to pick a location to store the
|
||||
Org-roam files. The directory that will contain your notes, and database index
|
||||
is specified by the variable ~org-roam-directory~. This variable needs to be set
|
||||
before any calls to Org-roam functions, including enabling ~org-roam-mode~. For
|
||||
this tutorial, create an empty directory, and set ~org-roam-directory~:
|
||||
Org-roam files. The directory that will contain your notes is specified by the
|
||||
variable ~org-roam-directory~. This variable needs to be set before any calls to
|
||||
Org-roam functions, including enabling ~org-roam-mode~. For this tutorial,
|
||||
create an empty directory, and set ~org-roam-directory~:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(make-directory "~/org-roam")
|
||||
@ -301,39 +336,38 @@ this tutorial, create an empty directory, and set ~org-roam-directory~:
|
||||
|
||||
We encourage using a flat hierarchy for storing notes, but some prefer using
|
||||
folders for storing specific kinds of notes (e.g. websites, papers). This is
|
||||
fine; Org-roam searches recursively within ~org-roam-directory~ for any notes.
|
||||
Instead of relying on the file hierarchy for any form of categorization, we
|
||||
solely rely on links between files to establish connections between notes.
|
||||
fine; Org-roam searches recursively within ~org-roam-directory~ for notes.
|
||||
Instead of relying on the file hierarchy for any form of categorization, one
|
||||
should use links between files to establish connections between notes.
|
||||
|
||||
Next, we need to enable the global minor mode ~org-roam-mode~. This sets up Emacs
|
||||
with several hooks, builds a cache and keeps it consistent. We recommend
|
||||
starting ~org-roam-mode~ on startup:
|
||||
Next, we need to enable the global minor mode ~org-roam-mode~. This sets up
|
||||
Emacs with several hooks, building a cache that is kept consistent as your
|
||||
slip-box grows. We recommend starting ~org-roam-mode~ on startup:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(add-hook 'after-init-hook 'org-roam-mode)
|
||||
#+END_SRC
|
||||
|
||||
To build the cache manually, one can run ~M-x org-roam-db-build-cache~. The
|
||||
cache is a sqlite database named ~org-roam.db~, which defaults to residing in
|
||||
the root ~org-roam-directory~. Cache builds may take a while the first time, but
|
||||
is often instantaneous in subsequent runs.
|
||||
To build the cache manually, one can run ~M-x org-roam-db-build-cache~. Cache
|
||||
builds may take a while the first time, but is often instantaneous in subsequent
|
||||
runs because it only reprocesses modified files.
|
||||
|
||||
Let us now create our first note. Call ~M-x org-roam-find-file~. This shows a list
|
||||
of titles for notes that reside in ~org-roam-directory~. It should show nothing
|
||||
right now, since there are no notes in the directory. Entering the title of the
|
||||
note you wish to create, and pressing ~RET~ should begin the note creation
|
||||
process. This process uses ~org-capture~'s templating system, and can be freely
|
||||
customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing ~C-c
|
||||
C-c~ finishes the note capture. Running ~M-x org-roam-find-file~ again should show
|
||||
the note you have created, and selecting that entry will bring you to that note.
|
||||
Let us now create our first note. Call ~M-x org-roam-find-file~. This shows a
|
||||
list of titles for notes that reside in ~org-roam-directory~. It should show
|
||||
nothing right now, since there are no notes in the directory. Entering the title
|
||||
of the note you wish to create, and pressing ~RET~ should begin the note
|
||||
creation process. This process uses ~org-capture~'s templating system, and can
|
||||
be customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing
|
||||
~C-c C-c~ finishes the note capture. Running ~M-x org-roam-find-file~ again
|
||||
should show the note you have created, and selecting that entry will bring you
|
||||
to that note.
|
||||
|
||||
The crux of Org-roam is making it easy to create notes, and link them together.
|
||||
To link notes together, we call ~M-x org-roam-insert~. This brings up a prompt
|
||||
with a list of title for existing notes. Selecting an existing entry will create
|
||||
and insert a link to the current file. Entering a non-existent title will create
|
||||
a new note with that title. Good usage of Org-roam requires liberally linking
|
||||
files: this facilitates building up a dense knowledge graph of inter-connected
|
||||
notes.
|
||||
Org-roam makes it easy to create notes, and link them together. To link notes
|
||||
together, we call ~M-x org-roam-insert~. This brings up a prompt with a list of
|
||||
title for existing notes. Selecting an existing entry will create and insert a
|
||||
link to the current file. Entering a non-existent title will create a new note
|
||||
with that title. Good usage of Org-roam requires liberally linking files: this
|
||||
facilitates building up a dense graph of inter-connected notes.
|
||||
|
||||
Org-roam provides an interface to view backlinks. It shows backlinks for the
|
||||
currently active Org-roam note, along with some surrounding context. To toggle
|
||||
@ -342,15 +376,14 @@ the visibility of this buffer, call ~M-x org-roam~.
|
||||
For a visual representation of the notes and their connections, Org-roam also
|
||||
provides graphing capabilities, using Graphviz. It generates graphs with notes
|
||||
as nodes, and links between them as edges. The generated graph can be used to
|
||||
navigate to the files, but this requires some additional setup (see [[*Roam Protocol][Roam
|
||||
Protocol]]).
|
||||
navigate to the files, but this requires some additional setup (see [[*Roam
|
||||
Protocol][Roam Protocol]]).
|
||||
|
||||
* Anatomy of an Org-roam File
|
||||
|
||||
The bulk of Org-roam's functionality is built on top of vanilla
|
||||
Org-mode. However, to support additional functionality, Org-roam adds
|
||||
several Org-roam-specific keywords. These functionality are not crucial
|
||||
to effective use of Org-roam.
|
||||
The bulk of Org-roam's functionality is built on top of vanilla Org-mode.
|
||||
However, to support additional functionality, Org-roam adds several
|
||||
Org-roam-specific keywords.
|
||||
|
||||
** Titles
|
||||
|
||||
@ -367,7 +400,7 @@ title extraction methods supported are:
|
||||
1. ~'title~: This extracts the title using the file ~#+title~ property
|
||||
2. ~'headline~: This extracts the title from the first headline in the Org file
|
||||
3. ~'alias~: This extracts a list of titles using the ~#+roam_alias~ property.
|
||||
The aliases are space-delimited, and can be multi-worded using quotes
|
||||
The aliases are space-delimited, and can be multi-worded using quotes.
|
||||
|
||||
Take for example the following org file:
|
||||
|
||||
@ -398,13 +431,15 @@ If you wish to add your own title extraction method, you may push a symbol
|
||||
|
||||
Tags are used as meta-data for files: they facilitate interactions with notes
|
||||
where titles are insufficient. For example, tags allow for categorization of
|
||||
notes: differentiating between bibliographical and structure notes during interactive commands.
|
||||
notes: differentiating between bibliographical and structure notes during
|
||||
interactive commands.
|
||||
|
||||
Org-roam calls ~org-roam--extract-tags~ to extract tags from files. It uses the
|
||||
variable ~org-roam-tag-sources~, to control how tags are extracted. The tag
|
||||
extraction methods supported are:
|
||||
|
||||
1. ~'prop~: This extracts tags from the ~#+roam_tags~ property. Tags are space delimited, and can be multi-word using double quotes.
|
||||
1. ~'prop~: This extracts tags from the ~#+roam_tags~ property. Tags are space
|
||||
delimited, and can be multi-word using double quotes.
|
||||
2. ~'all-directories~: All sub-directories relative to ~org-roam-directory~ are
|
||||
extracted as tags. That is, if a file is located at relative path
|
||||
~foo/bar/file.org~, the file will have tags ~foo~ and ~bar~.
|
||||
@ -429,29 +464,34 @@ accepts the absolute file path as its argument. See
|
||||
|
||||
** File Refs
|
||||
|
||||
Refs are unique identifiers for files. Each note can only have 1 ref.
|
||||
For example, a note for a website may contain a ref:
|
||||
Refs are unique identifiers for files. For example, a note for a website may
|
||||
contain a ref:
|
||||
|
||||
#+BEGIN_SRC org
|
||||
#+title: Google
|
||||
#+roam_key: https://www.google.com/
|
||||
#+END_SRC
|
||||
|
||||
These keys come in useful for when taking website notes, using the
|
||||
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
|
||||
These keys allow references to the key to show up in the backlinks buffer. For
|
||||
instance, with the example above, if another file then links to
|
||||
https://www.google.com, that will show up as a “Ref Backlink”.
|
||||
|
||||
Alternatively, add a ref for notes for a specific paper, using its
|
||||
[[https://github.com/jkitchin/org-ref][org-ref]] citation key:
|
||||
These keys also come in useful for when taking website notes, using the
|
||||
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
|
||||
|
||||
[[https://github.com/jkitchin/org-ref][org-ref]] citation keys can also be used as refs:
|
||||
|
||||
#+BEGIN_SRC org
|
||||
#+title: Neural Ordinary Differential Equations
|
||||
#+roam_key: cite:chen18_neural_ordin_differ_equat
|
||||
#+END_SRC
|
||||
|
||||
The backlinks buffer will show any cites of this key: e.g.
|
||||
|
||||
#+CAPTION: org-ref-citelink
|
||||
[[file:images/org-ref-citelink.png]]
|
||||
|
||||
You may assign multiple refs to a single file, for example when you want
|
||||
multiple papers in a series to share the same note, or an article has a citation
|
||||
key and a URL at the same time.
|
||||
* The Templating System
|
||||
|
||||
Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
|
||||
@ -489,15 +529,14 @@ the default template, reproduced below.
|
||||
:unnarrowed t)
|
||||
#+END_SRC
|
||||
|
||||
1. The template has short key ~"d"~. If you have only one template,
|
||||
org-roam automatically chooses this template for you.
|
||||
1. The template has short key ~"d"~. If you have only one template, org-roam
|
||||
automatically chooses this template for you.
|
||||
2. The template is given a description of ~"default"~.
|
||||
3. ~plain~ text is inserted. Other options include Org headings via
|
||||
~entry~.
|
||||
4. ~(function org-roam--capture-get-point)~ should not be changed.
|
||||
5. ~"%?"~ is the template inserted on each call to ~org-roam-capture--capture~.
|
||||
This template means don't insert any content, but place the cursor
|
||||
here.
|
||||
This template means don't insert any content, but place the cursor here.
|
||||
6. ~:file-name~ is the file-name template for a new note, if it doesn't yet
|
||||
exist. This creates a file at path that looks like
|
||||
~/path/to/org-roam-directory/20200213032037-foo.org~. This template also
|
||||
@ -526,9 +565,9 @@ provided title. ~${title}~ is then expanded into the provided title during the
|
||||
org-capture process. Any variables that do not contain strings, are prompted for
|
||||
values using ~completing-read~.
|
||||
|
||||
After doing this expansion, the org-capture's template expansion system
|
||||
is used to fill up the rest of the template. You may read up more on
|
||||
this on [[https://orgmode.org/manual/Template-expansion.html#Template-expansion][org-capture's documentation page]].
|
||||
After doing this expansion, the org-capture's template expansion system is used
|
||||
to fill up the rest of the template. You may read up more on this on
|
||||
[[https://orgmode.org/manual/Template-expansion.html#Template-expansion][org-capture's documentation page]].
|
||||
|
||||
To illustrate this dual expansion process, take for example the template string:
|
||||
~"%<%Y%m%d%H%M%S>-${title}"~, with the title ~"Foo"~. The template is first
|
||||
@ -549,12 +588,12 @@ directly to provide its third argument to specify UTC.
|
||||
#+END_SRC
|
||||
|
||||
* Concepts and Configuration
|
||||
The number of configuration options is deliberately kept small, to keep
|
||||
the Org-roam codebase manageable. However, we attempt to accommodate as
|
||||
many usage styles as possible.
|
||||
The number of configuration options is deliberately kept small, to keep the
|
||||
Org-roam codebase manageable. However, we attempt to accommodate as many usage
|
||||
styles as possible.
|
||||
|
||||
All of Org-roam's customization options can be viewed via
|
||||
~M-x customize-group org-roam~.
|
||||
All of Org-roam's customization options can be viewed via ~M-x customize-group
|
||||
org-roam~.
|
||||
|
||||
** Directories and Files
|
||||
|
||||
@ -600,15 +639,56 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
|
||||
Height of ~org-roam-buffer~. Has an effect only if ~org-roam-buffer-position~ is
|
||||
~'top~ or ~'bottom~.
|
||||
|
||||
- User Option: org-roam-buffer-no-delete-other-windows
|
||||
- User Option: org-roam-buffer-window-parameters
|
||||
|
||||
The ~no-delete-window~ parameter for the org-roam buffer. Setting it to ~'t~ prevents the window from being deleted when calling ~delete-other-windows~.
|
||||
Additional window parameters for the org-roam-buffer side window.
|
||||
|
||||
For example one can prevent the window from being deleted when calling
|
||||
~delete-other-windows~, by setting it with the following:
|
||||
|
||||
~(setq org-roam-buffer-window-parameters '((no-delete-other-windows . t)))~
|
||||
|
||||
** Org-roam Files
|
||||
|
||||
Org-roam files are created and prefilled using Org-roam's templating
|
||||
system. The templating system is customizable (see [[*The Templating System][The Templating System]]).
|
||||
|
||||
** Org-roam Faces
|
||||
|
||||
Org-roam introduces several faces to distinguish links within the same buffer.
|
||||
These faces are enabled by default in Org-roam notes.
|
||||
|
||||
- User Option: org-roam-link-use-custom-faces
|
||||
|
||||
When ~t~, use custom faces only inside Org-roam notes.
|
||||
When ~everywhere~, the custom face is applied additionally to non Org-roam notes.
|
||||
When ~nil~, do not use Org-roam's custom faces.
|
||||
|
||||
The ~org-roam-link~ face is the face applied to links to other Org-roam files.
|
||||
This distinguishes internal links from external links (e.g. external web links).
|
||||
|
||||
The ~org-roam-link-current~ face corresponds to links to the same file it is in.
|
||||
|
||||
The ~org-roam-link-invalid~ face is applied to links that are broken. These are
|
||||
links to files or IDs that cannot be found.
|
||||
** TODO The Database
|
||||
|
||||
Org-roam is backed by a Sqlite database.
|
||||
|
||||
- User Option: org-roam-db-update-method
|
||||
|
||||
Method to update the Org-roam database.
|
||||
|
||||
~'immediate~: Update the database immediately upon file changes.
|
||||
|
||||
~'idle-timer~: Updates the database if dirty, if Emacs idles for
|
||||
~org-roam-db-update-idle-seconds~.
|
||||
|
||||
- User Option: org-roam-db-update-idle-seconds
|
||||
|
||||
Number of idle seconds before triggering an Org-roam database update. This is
|
||||
only valid if ~org-roam-db-update-method~ is ~'idle-timer~.
|
||||
|
||||
* Inserting Links
|
||||
|
||||
The preferred mode of linking is via ~file~ links to files, and ~id~ links for
|
||||
@ -626,14 +706,17 @@ An alternative mode of insertion is using Org-roam's ~roam~ links. Org-roam
|
||||
registers this link type, and interprets the path as follows:
|
||||
|
||||
- ~[[roam:title]]~ :: links to an Org-roam file with title or alias "title"
|
||||
- ~[[roam:*headline]]~ :: links to the headline "headline" in the current Org-roam file
|
||||
- ~[[roam:title*headline]]~ :: links to the headline "headline" in the Org-roam file with title or alias "title"
|
||||
- ~[[roam:*headline]]~ :: links to the headline "headline" in the current
|
||||
Org-roam file
|
||||
- ~[[roam:title*headline]]~ :: links to the headline "headline" in the Org-roam
|
||||
file with title or alias "title"
|
||||
|
||||
~roam~ links support auto-completion via ~completion-at-point~: simply call
|
||||
~completion-at-point~ within a roam link. Users of ~company-mode~ may want to
|
||||
prepend ~company-capf~ to the beginning of variable ~company-backends~.
|
||||
|
||||
To easily insert ~roam~ links, one may wish to use a package like [[https://github.com/emacsorphanage/key-chord/][key-chord]]. In the following example, typing "[[" will insert a stub ~roam~ link:
|
||||
To easily insert ~roam~ links, one may wish to use a package like [[https://github.com/emacsorphanage/key-chord/][key-chord]]. In
|
||||
the following example, typing "[[" will insert a stub ~roam~ link:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(key-chord-define org-mode-map "[[" #'my/insert-roam-link)
|
||||
@ -667,7 +750,6 @@ To easily insert ~roam~ links, one may wish to use a package like [[https://gith
|
||||
harder to edit. Defaults to ~t~.
|
||||
|
||||
* Navigating Around
|
||||
|
||||
** Index File
|
||||
|
||||
As your collection grows, you might want to create an index where you keep links
|
||||
@ -697,13 +779,14 @@ GPG), which can be enabled for all new files by setting ~org-roam-encrypt-files~
|
||||
to ~t~. When enabled, new files are created with the ~.org.gpg~ extension and
|
||||
decryption are handled automatically by EasyPG.
|
||||
|
||||
Note that Emacs will prompt for a password for encrypted files during
|
||||
cache updates if it requires reading the encrypted file. To reduce the
|
||||
number of password prompts, you may wish to cache the password.
|
||||
Note that Emacs will prompt for a password for encrypted files during cache
|
||||
updates if it requires reading the encrypted file. To reduce the number of
|
||||
password prompts, you may wish to cache the password.
|
||||
|
||||
- Variable: org-roam-encrypt-files
|
||||
- User Option: org-roam-encrypt-files
|
||||
|
||||
Whether to encrypt new files. If true, create files with .org.gpg extension.
|
||||
|
||||
Whether to encrypt new files. If true, create files with .org.gpg extension.
|
||||
* Graphing
|
||||
|
||||
Org-roam provides graphing capabilities to explore interconnections between
|
||||
@ -727,21 +810,24 @@ The entry point to graph creation is ~org-roam-graph~.
|
||||
|
||||
- User Option: org-roam-graph-executable
|
||||
|
||||
Path to the graphing executable (in this case, Graphviz). Set this if Org-roam is unable to find the Graphviz executable on your system.
|
||||
Path to the graphing executable (in this case, Graphviz). Set this if Org-roam
|
||||
is unable to find the Graphviz executable on your system.
|
||||
|
||||
You may also choose to use ~neato~ in place of ~dot~, which generates a more
|
||||
compact graph layout.
|
||||
|
||||
- User Option: org-roam-graph-viewer
|
||||
|
||||
Org-roam defaults to using Firefox (located on PATH) to view the SVG, but you may choose to set it to:
|
||||
Org-roam defaults to using Firefox (located on PATH) to view the SVG, but you
|
||||
may choose to set it to:
|
||||
|
||||
1. A string, which is a path to the program used
|
||||
2. a function accepting a single argument: the graph file path.
|
||||
|
||||
~nil~ uses ~view-file~ to view the graph.
|
||||
|
||||
If you are using WSL2 and would like to open the graph in Windows, you can use the second option to set the browser and network file path:
|
||||
If you are using WSL2 and would like to open the graph in Windows, you can use
|
||||
the second option to set the browser and network file path:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(setq org-roam-graph-viewer
|
||||
@ -752,7 +838,9 @@ The entry point to graph creation is ~org-roam-graph~.
|
||||
|
||||
** Graph Options
|
||||
|
||||
Graphviz provides many options for customizing the graph output, and Org-roam supports some of them. See https://graphviz.gitlab.io/_pages/doc/info/attrs.html for customizable options.
|
||||
Graphviz provides many options for customizing the graph output, and Org-roam
|
||||
supports some of them. See https://graphviz.gitlab.io/_pages/doc/info/attrs.html
|
||||
for customizable options.
|
||||
|
||||
- User Option: org-roam-graph-extra-config
|
||||
|
||||
@ -817,13 +905,14 @@ commands, set:
|
||||
Other options include ~'ido~, and ~'ivy~.
|
||||
|
||||
* Roam Protocol
|
||||
** _ :ignore:
|
||||
Org-roam extending ~org-protocol~ with 2 protocols: the ~roam-file~
|
||||
and ~roam-ref~ protocol.
|
||||
|
||||
Org-roam extends ~org-protocol~ with 2 protocols: the ~roam-file~ and ~roam-ref~
|
||||
protocols.
|
||||
|
||||
** Installation
|
||||
|
||||
To enable Org-roam's protocol extensions, you have to add the following to your init file:
|
||||
To enable Org-roam's protocol extensions, you have to add the following to your
|
||||
init file:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(require 'org-roam-protocol)
|
||||
@ -834,7 +923,8 @@ The instructions for setting up ~org-protocol~ are reproduced below.
|
||||
We will also need to create a desktop application for ~emacsclient~. The
|
||||
instructions for various platforms are shown below.
|
||||
|
||||
For Linux users, create a desktop application in ~~/.local/share/applications/org-protocol.desktop~:
|
||||
For Linux users, create a desktop application in
|
||||
~~/.local/share/applications/org-protocol.desktop~:
|
||||
|
||||
#+begin_example
|
||||
[Desktop Entry]
|
||||
@ -853,9 +943,9 @@ running in your shell:
|
||||
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
|
||||
#+END_SRC
|
||||
|
||||
To disable the "confirm" prompt in Chrome, you can also make Chrome
|
||||
show a checkbox to tick, so that the ~Org-Protocol Client~ app will be used
|
||||
without confirmation. To do this, run in a shell:
|
||||
To disable the "confirm" prompt in Chrome, you can also make Chrome show a
|
||||
checkbox to tick, so that the ~Org-Protocol Client~ app will be used without
|
||||
confirmation. To do this, run in a shell:
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
sudo mkdir -p /etc/opt/chrome/policies/managed/
|
||||
@ -891,7 +981,6 @@ brew cask install platypus
|
||||
|
||||
3. Create a Platypus app with the following settings:
|
||||
|
||||
#+begin_example
|
||||
| Setting | Value |
|
||||
|--------------------------------+---------------------------|
|
||||
| App Name | "OrgProtocol" |
|
||||
@ -900,18 +989,15 @@ brew cask install platypus
|
||||
| Interface | None |
|
||||
| Accept dropped items | true |
|
||||
| Remain running after execution | false |
|
||||
#+end_example
|
||||
|
||||
|
||||
Inside ~Settings~:
|
||||
|
||||
#+begin_example
|
||||
| Setting | Value |
|
||||
|--------------------------------+----------------|
|
||||
| Accept dropped files | true |
|
||||
| Register as URI scheme handler | true |
|
||||
| Protocol | "org-protocol" |
|
||||
#+end_example
|
||||
|
||||
To disable the "confirm" prompt in Chrome, you can also make Chrome
|
||||
show a checkbox to tick, so that the ~OrgProtocol~ app will be used
|
||||
@ -947,13 +1033,15 @@ REGEDIT4
|
||||
@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
|
||||
#+END_SRC
|
||||
|
||||
The above will forward the protocol to WSL. If you run Emacs natively on Windows, replace the last line with:
|
||||
The above will forward the protocol to WSL. If you run Emacs natively on
|
||||
Windows, replace the last line with:
|
||||
|
||||
#+BEGIN_SRC text
|
||||
@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
|
||||
#+END_SRC
|
||||
|
||||
After executing the .reg file, the protocol is registered and you can delete the file.
|
||||
After executing the .reg file, the protocol is registered and you can delete the
|
||||
file.
|
||||
|
||||
** The roam-file protocol
|
||||
|
||||
@ -970,11 +1058,13 @@ This protocol finds or creates a new note with a given ~roam_key~ (see [[*Anatom
|
||||
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser:
|
||||
|
||||
#+BEGIN_SRC javascript
|
||||
javascript:location.href =
|
||||
'org-protocol://roam-ref?template=r&ref='
|
||||
+ encodeURIComponent(location.href)
|
||||
+ '&title='
|
||||
+ encodeURIComponent(document.title)
|
||||
javascript:location.href =
|
||||
'org-protocol://roam-ref?template=r&ref='
|
||||
+ encodeURIComponent(location.href)
|
||||
+ '&title='
|
||||
+ encodeURIComponent(document.title)
|
||||
+ '&body='
|
||||
+ encodeURIComponent(window.getSelection())
|
||||
#+END_SRC
|
||||
|
||||
or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
|
||||
@ -988,7 +1078,122 @@ where ~template~ is the template key for a template in
|
||||
~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]). These templates
|
||||
should contain a ~#+roam_key: ${ref}~ in it.
|
||||
|
||||
* TODO Daily Notes
|
||||
* Daily-notes
|
||||
|
||||
Org-roam provides journaling capabilities akin to
|
||||
[[#org-journal][Org-journal]] with ~org-roam-dailies~.
|
||||
|
||||
** Configuration
|
||||
|
||||
For ~org-roam-dailies~ to work, you need to define two variables:
|
||||
|
||||
- Variable: ~org-roam-dailies-directory~
|
||||
|
||||
Path to daily-notes.
|
||||
|
||||
- Variable: ~org-roam-dailies-capture-templates~
|
||||
|
||||
Capture templates for daily-notes in Org-roam.
|
||||
|
||||
Here is a sane default configuration:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-roam-dailies-directory "daily/")
|
||||
|
||||
(setq org-roam-dailies-capture-templates
|
||||
'(("d" "default" entry
|
||||
#'org-roam-capture--get-point
|
||||
"* %?"
|
||||
:file-name "daily/%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>\n\n")))
|
||||
#+end_src
|
||||
|
||||
Make sure that ~org-roam-dailies-directory~ appears in ~:file-name~ for your
|
||||
notes to be recognized as daily-notes. You can have different templates placing
|
||||
their notes in different directories, but the one in
|
||||
~org-roam-dailies-directory~ will be considered as the main one in commands.
|
||||
|
||||
See [[*The Templating System][The Templating System]] for creating new
|
||||
templates. ~org-roam-dailies~ provides an extra ~:olp~ option which allows
|
||||
specifying the outline-path to a heading:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-roam-dailies-capture-templates
|
||||
'(("l" "lab" entry
|
||||
#'org-roam-capture--get-point
|
||||
"* %?"
|
||||
:file-name "daily/%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
|
||||
:olp ("Journal"))
|
||||
|
||||
("j" "journal" entry
|
||||
#'org-roam-capture--get-point
|
||||
"* %?"
|
||||
:file-name "daily/%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
|
||||
:olp ("Lab notes"))))
|
||||
#+end_src
|
||||
|
||||
The template ~l~ will put its notes under the heading ‘Lab notes’, and the
|
||||
template ~j~ will put its notes under the heading ‘Journal’. When you use
|
||||
~:olp~, make sure that the headings are present in ~:head~.
|
||||
|
||||
** Capturing and finding daily-notes
|
||||
|
||||
- Function: ~org-roam-dailies-capture-today~ &optional goto
|
||||
|
||||
Create an entry in the daily note for today.
|
||||
|
||||
When ~goto~ is non-nil, go the note without creating an entry.
|
||||
|
||||
- Function: ~org-roam-dailies-find-today~
|
||||
|
||||
Find the daily note for today, creating it if necessary.
|
||||
|
||||
There are variants of those commands for ~-yesterday~ and ~-tomorrow~:
|
||||
|
||||
- Function: ~org-roam-dailies-capture-yesterday~ n &optional goto
|
||||
|
||||
Create an entry in the daily note for yesteday.
|
||||
|
||||
With numeric argument ~n~, use the daily note ~n~ days in the past.
|
||||
|
||||
- Function: ~org-roam-dailies-find-yesterday~
|
||||
|
||||
With numeric argument N, use the daily-note N days in the future.
|
||||
|
||||
There are also commands which allow you to use Emacs’s ~calendar~ to find the date
|
||||
|
||||
- Function: ~org-roam-dailies-capture-date~
|
||||
|
||||
Create an entry in the daily note for a date using the calendar.
|
||||
|
||||
Prefer past dates, unless ~prefer-future~ is non-nil.
|
||||
|
||||
With a 'C-u' prefix or when ~goto~ is non-nil, go the note without
|
||||
creating an entry.
|
||||
|
||||
- Function: ~org-roam-dailies-find-date~
|
||||
|
||||
Find the daily note for a date using the calendar, creating it if necessary.
|
||||
|
||||
Prefer past dates, unless ~prefer-future~ is non-nil.
|
||||
|
||||
** Navigation
|
||||
|
||||
You can navigate between daily-notes:
|
||||
|
||||
- Function: ~org-roam-dailies-find-directory~
|
||||
|
||||
Find and open ~org-roam-dailies-directory~.
|
||||
|
||||
- Function: ~org-roam-dailies-find-previous-note~
|
||||
|
||||
When in an daily-note, find the previous one.
|
||||
|
||||
- Function: ~org-roam-dailies-find-next-note~
|
||||
|
||||
When in an daily-note, find the next one.
|
||||
|
||||
* Diagnosing and Repairing Files
|
||||
|
||||
@ -1002,9 +1207,11 @@ org-roam-doctor~, but note that this may take some time.
|
||||
Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run
|
||||
the check only for the current buffer.
|
||||
|
||||
The checks run are defined in ~org-roam-doctor--checkers~. Each checker is an
|
||||
instance of ~org-roam-doctor-checker~. To define a checker, use
|
||||
~make-org-roam-doctor-checker~. Here is a sample definition:
|
||||
The checks run are defined in ~org-roam-doctor--checkers~. By default, there are
|
||||
checkers for broken links and invalid =#+roam_*= properties.
|
||||
|
||||
Each checker is an instance of ~org-roam-doctor-checker~. To define a checker,
|
||||
use ~make-org-roam-doctor-checker~. Here is a sample definition:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(make-org-roam-doctor-checker
|
||||
@ -1016,10 +1223,10 @@ instance of ~org-roam-doctor-checker~. To define a checker, use
|
||||
#+END_SRC
|
||||
|
||||
The ~:name~ property is the name of the function run. The function takes in the
|
||||
Org parse tree, and returns a list of ~(point error-message)~. ~:description~ is a
|
||||
short description of what the checker does. ~:actions~ is an alist containing
|
||||
elements of the form ~(char . (prompt . function))~. These actions are defined per
|
||||
checker, to perform autofixes for the errors. For each error detected,
|
||||
Org parse tree, and returns a list of ~(point error-message)~. ~:description~ is
|
||||
a short description of what the checker does. ~:actions~ is an alist containing
|
||||
elements of the form ~(char . (prompt . function))~. These actions are defined
|
||||
per checker, to perform autofixes for the errors. For each error detected,
|
||||
~org-roam-doctor~ will move the point to the current error, and pop-up a help
|
||||
window displaying the error message, as well as the list of actions that can be
|
||||
taken provided in ~:actions~.
|
||||
@ -1032,17 +1239,24 @@ facilities for discovering these unlinked references, so one may decide whether
|
||||
to convert them into links.
|
||||
|
||||
To use this feature, simply call ~M-x org-roam-unlinked-references~ from within
|
||||
an Org-roam note. Org-roam uses [[https://github.com/BurntSushi/ripgrep][ripgrep]], specifically a clever PCRE regex to
|
||||
an Org-roam note. Internally, Org-roam uses [[https://github.com/BurntSushi/ripgrep][ripgrep]] and a clever PCRE regex to
|
||||
find occurrences of the title or aliases of the currently open note in all
|
||||
Org-roam files. This thus requires a version of ripgrep that is compiled with
|
||||
Org-roam files. Hence, this requires a version of ripgrep that is compiled with
|
||||
PCRE support.
|
||||
|
||||
#+begin_quote
|
||||
NOTE: Since ripgrep cannot read encrypted files, this function cannot find
|
||||
unlinked references within encrypted files.
|
||||
#+end_quote
|
||||
|
||||
* Performance Optimization
|
||||
** TODO Profiling Key Operations
|
||||
** Garbage Collection
|
||||
|
||||
During the cache-build process, Org-roam generates a lot of in-memory
|
||||
data-structures (such as the Org file's AST), which are discarded after use. These structures are garbage collected at regular intervals (see [[info:elisp#Garbage Collection][info:elisp#Garbage Collection]]).
|
||||
data-structures (such as the Org file's AST), which are discarded after use.
|
||||
These structures are garbage collected at regular intervals (see [[info:elisp#Garbage
|
||||
Collection][info:elisp#Garbage Collection]]).
|
||||
|
||||
Org-roam provides the option ~org-roam-db-gc-threshold~ to temporarily change
|
||||
the threshold value for GC to be triggered during these memory-intensive
|
||||
@ -1090,9 +1304,14 @@ General Public License for more details.
|
||||
|
||||
*** Browsing History with winner-mode
|
||||
|
||||
~winner-mode~ is a global minor mode that allows one to undo and redo changes in the window configuration. It is included with GNU Emacs since version 20.
|
||||
~winner-mode~ is a global minor mode that allows one to undo and redo changes in
|
||||
the window configuration. It is included with GNU Emacs since version 20.
|
||||
|
||||
~winner-mode~ can be used as a simple version of browser history for Org-roam. Each click through org-roam links (from both Org files and the backlinks buffer) causes changes in window configuration, which can be undone and redone using ~winner-mode~. To use ~winner-mode~, simply enable it, and bind the appropriate interactive functions:
|
||||
~winner-mode~ can be used as a simple version of browser history for Org-roam.
|
||||
Each click through org-roam links (from both Org files and the backlinks buffer)
|
||||
causes changes in window configuration, which can be undone and redone using
|
||||
~winner-mode~. To use ~winner-mode~, simply enable it, and bind the appropriate
|
||||
interactive functions:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(winner-mode +1)
|
||||
@ -1130,10 +1349,9 @@ versions of a tracked Org-roam note.
|
||||
(deft-directory "/path/to/org-roam-files/"))
|
||||
#+END_SRC
|
||||
|
||||
If the title of the Org file is not the first line, you might not get
|
||||
nice titles. You may choose to patch this to use ~org-roam~'s
|
||||
functionality. Here I'm using
|
||||
[[https://github.com/raxod502/el-patch][el-patch]]:
|
||||
If the title of the Org file is not the first line, you might not get nice
|
||||
titles. You may choose to patch this to use ~org-roam~'s functionality. Here I'm
|
||||
using [[https://github.com/raxod502/el-patch][el-patch]]:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package el-patch
|
||||
@ -1161,19 +1379,18 @@ functionality. Here I'm using
|
||||
(org-roam--get-title-or-slug file))))
|
||||
#+END_SRC
|
||||
|
||||
The Deft interface can slow down quickly when the number of files get
|
||||
huge. [[https://github.com/hasu/notdeft][Notdeft]] is a fork of Deft
|
||||
that uses an external search engine and indexer.
|
||||
The Deft interface can slow down quickly when the number of files get huge.
|
||||
[[https://github.com/hasu/notdeft][Notdeft]] is a fork of Deft that uses an external search engine and indexer.
|
||||
|
||||
*** Org-journal
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: org-journal
|
||||
:END:
|
||||
|
||||
[[https://github.com/bastibe/org-journal][Org-journal]] is a more
|
||||
powerful alternative to the simple function ~org-roam-dailies-today~. It
|
||||
provides better journaling capabilities, and a nice calendar interface
|
||||
to see all dated entries.
|
||||
[[https://github.com/bastibe/org-journal][Org-journal]] provides journaling capabilities to Org-mode. A lot of its
|
||||
functionalities have been incorporated into Org-roam under the name
|
||||
[[*Daily-notes][~org-roam-dailies~]]. It remains a good tool if you want to isolate your verbose
|
||||
journal entries from the ideas you would write on a scratchpad.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package org-journal
|
||||
@ -1182,7 +1399,7 @@ to see all dated entries.
|
||||
:custom
|
||||
(org-journal-date-prefix "#+title: ")
|
||||
(org-journal-file-format "%Y-%m-%d.org")
|
||||
(org-journal-dir "/path/to/org-roam-files/")
|
||||
(org-journal-dir "/path/to/journal/files/")
|
||||
(org-journal-date-format "%A, %d %B %Y"))
|
||||
#+END_SRC
|
||||
|
||||
@ -1253,6 +1470,10 @@ tight integration between
|
||||
~org-roam~. This helps you manage your bibliographic notes under
|
||||
~org-roam~.
|
||||
|
||||
For example, though helm-bibtex provides the ability to visit notes for
|
||||
bibliographic entries, org-roam-bibtex extends it with the ability to visit the
|
||||
file with the right =#+roam_key=.
|
||||
|
||||
**** Spaced Repetition
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: spaced-repetition
|
||||
@ -1277,13 +1498,15 @@ contain:
|
||||
(org-roam-db-location . "./org-roam.db"))))
|
||||
#+END_SRC
|
||||
|
||||
All files within that directory will be treated as their own separate
|
||||
set of Org-roam files. Remember to run ~org-roam-db-build-cache~ from a
|
||||
file within that directory, at least once.
|
||||
All files within that directory will be treated as their own separate set of
|
||||
Org-roam files. Remember to run ~org-roam-db-build-cache~ from a file within
|
||||
that directory, at least once.
|
||||
|
||||
** How do I migrate from Roam Research?
|
||||
|
||||
Fabio has produced a command-line tool that converts markdown files exported from Roam Research into Org-roam compatible markdown. More instructions are provided [[https://github.com/fabioberger/roam-migration][in the repository]].
|
||||
Fabio has produced a command-line tool that converts markdown files exported
|
||||
from Roam Research into Org-roam compatible markdown. More instructions are
|
||||
provided [[https://github.com/fabioberger/roam-migration][in the repository]].
|
||||
|
||||
** How do I create a note whose title already matches one of the candidates?
|
||||
|
||||
@ -1293,11 +1516,40 @@ This situation arises when, for example, one would like to create a note titled
|
||||
The solution is dependent on the mini-buffer completion framework in use. Here
|
||||
are the solutions:
|
||||
|
||||
- Ivy :: call ~ivy-immediate-done~, typically bound to ~C-M-j~. Alternatively, set ~ivy-use-selectable-prompt~ to ~t~, so that "bar" is now selectable.
|
||||
- Helm :: Org-roam should provide a selectable "[?] bar" candidate at the top of the candidate list.
|
||||
- Ivy :: call ~ivy-immediate-done~, typically bound to ~C-M-j~. Alternatively,
|
||||
set ~ivy-use-selectable-prompt~ to ~t~, so that "bar" is now selectable.
|
||||
- Helm :: Org-roam should provide a selectable "[?] bar" candidate at the top of
|
||||
the candidate list.
|
||||
|
||||
* Keystroke Index
|
||||
:PROPERTIES:
|
||||
:APPENDIX: t
|
||||
:INDEX: ky
|
||||
:COOKIE_DATA: recursive
|
||||
:END:
|
||||
* Command Index
|
||||
:PROPERTIES:
|
||||
:APPENDIX: t
|
||||
:INDEX: cp
|
||||
:END:
|
||||
* Function Index
|
||||
:PROPERTIES:
|
||||
:APPENDIX: t
|
||||
:INDEX: fn
|
||||
:END:
|
||||
* Variable Index
|
||||
:PROPERTIES:
|
||||
:APPENDIX: t
|
||||
:INDEX: vr
|
||||
:END:
|
||||
|
||||
* Footnotes
|
||||
[fn:roam] To understand more about Roam, a collection of links are available in [[*Note-taking Workflows][Note-taking Workflows]].
|
||||
|
||||
# Local Variables:
|
||||
# eval: (require 'ol-info)
|
||||
# eval: (require 'ox-texinfo+ nil t)
|
||||
# eval: (auto-fill-mode +1)
|
||||
# before-save-hook: org-make-toc
|
||||
# after-save-hook: (lambda nil (progn (require 'ox-texinfo nil t) (org-texinfo-export-to-info)))
|
||||
# indent-tabs-mode: nil
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -47,7 +47,7 @@
|
||||
(defvar org-roam--org-link-bracket-typed-re)
|
||||
|
||||
(declare-function org-roam-db--ensure-built "org-roam-db")
|
||||
(declare-function org-roam--extract-ref "org-roam")
|
||||
(declare-function org-roam--extract-refs "org-roam")
|
||||
(declare-function org-roam--extract-titles "org-roam")
|
||||
(declare-function org-roam--get-title-or-slug "org-roam")
|
||||
(declare-function org-roam--get-backlinks "org-roam")
|
||||
@ -107,11 +107,12 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
|
||||
(defun org-roam-buffer--find-file (file)
|
||||
"Open FILE in the window `org-roam' was called from."
|
||||
(setq file (expand-file-name file))
|
||||
(if (and org-roam-last-window (window-valid-p org-roam-last-window))
|
||||
(progn (with-selected-window org-roam-last-window
|
||||
(org-roam--find-file file))
|
||||
(select-window org-roam-last-window))
|
||||
(org-roam--find-file file)))
|
||||
(let ((last-window org-roam-last-window))
|
||||
(if (window-valid-p last-window)
|
||||
(progn (with-selected-window last-window
|
||||
(org-roam--find-file file))
|
||||
(select-window last-window))
|
||||
(org-roam--find-file file))))
|
||||
|
||||
(defun org-roam-buffer--insert-title ()
|
||||
"Insert the org-roam-buffer title."
|
||||
@ -148,10 +149,11 @@ ORIG-PATH is the path where the CONTENT originated."
|
||||
|
||||
(defun org-roam-buffer--insert-ref-links ()
|
||||
"Insert ref backlinks for the current buffer."
|
||||
(when-let ((path (cdr (with-temp-buffer
|
||||
(insert-buffer-substring org-roam-buffer--current)
|
||||
(org-roam--extract-ref)))))
|
||||
(if-let* ((key-backlinks (org-roam--get-backlinks path))
|
||||
(when-let* ((refs (with-temp-buffer
|
||||
(insert-buffer-substring org-roam-buffer--current)
|
||||
(org-roam--extract-refs)))
|
||||
(paths (mapcar #'cdr refs)))
|
||||
(if-let* ((key-backlinks (mapcan #'org-roam--get-backlinks paths))
|
||||
(grouped-backlinks (--group-by (nth 0 it) key-backlinks)))
|
||||
(progn
|
||||
(insert (let ((l (length key-backlinks)))
|
||||
@ -162,57 +164,56 @@ ORIG-PATH is the path where the CONTENT originated."
|
||||
(bls (cdr group)))
|
||||
(insert (format "** %s\n"
|
||||
(org-roam-format-link file-from
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
(dolist (backlink bls)
|
||||
(pcase-let ((`(,file-from _ ,props) backlink))
|
||||
(insert (propertize (org-roam-buffer-expand-links (plist-get props :content) file-from)
|
||||
'help-echo "mouse-1: visit backlinked note"
|
||||
'file-from file-from
|
||||
'file-from-point (plist-get props :point)))
|
||||
(insert "\n\n"))))))
|
||||
(insert (if-let ((content (plist-get props :content)))
|
||||
(propertize (org-roam-buffer-expand-links content file-from)
|
||||
'help-echo "mouse-1: visit backlinked note"
|
||||
'file-from file-from
|
||||
'file-from-point (plist-get props :point))
|
||||
"")
|
||||
"\n\n"))))))
|
||||
(insert "\n\n* No ref backlinks!"))))
|
||||
|
||||
(defun org-roam-buffer--insert-backlinks ()
|
||||
"Insert the org-roam-buffer backlinks string for the current buffer."
|
||||
(if-let* ((file-path (buffer-file-name org-roam-buffer--current))
|
||||
(titles (with-current-buffer org-roam-buffer--current
|
||||
(org-roam--extract-titles)))
|
||||
(backlinks (org-roam--get-backlinks (push file-path titles)))
|
||||
(grouped-backlinks (--group-by (nth 0 it) backlinks)))
|
||||
(progn
|
||||
(insert (let ((l (length backlinks)))
|
||||
(format "\n\n* %d %s\n"
|
||||
l (org-roam-buffer--pluralize "Backlink" l))))
|
||||
(dolist (group grouped-backlinks)
|
||||
(let ((file-from (car group))
|
||||
(bls (mapcar (lambda (row)
|
||||
(nth 2 row)) (cdr group))))
|
||||
(let (props file-from)
|
||||
(if-let* ((file-path (buffer-file-name org-roam-buffer--current))
|
||||
(titles (with-current-buffer org-roam-buffer--current
|
||||
(org-roam--extract-titles)))
|
||||
(backlinks (org-roam--get-backlinks (push file-path titles)))
|
||||
(grouped-backlinks (--group-by (nth 0 it) backlinks)))
|
||||
(progn
|
||||
(insert (let ((l (length backlinks)))
|
||||
(format "\n\n* %d %s\n"
|
||||
l (org-roam-buffer--pluralize "Backlink" l))))
|
||||
(dolist (group grouped-backlinks)
|
||||
(setq file-from (car group))
|
||||
(setq props (mapcar (lambda (row) (nth 2 row)) (cdr group)))
|
||||
(setq props (seq-sort-by (lambda (p) (plist-get p :point)) #'< props))
|
||||
(insert (format "** %s\n"
|
||||
(org-roam-format-link file-from
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
;; Sort backlinks according to time of occurrence in buffer
|
||||
(setq bls (seq-sort-by (lambda (bl)
|
||||
(plist-get bl :point))
|
||||
#'<
|
||||
bls))
|
||||
(dolist (props bls)
|
||||
(org-roam--get-title-or-slug file-from)
|
||||
"file")))
|
||||
(dolist (prop props)
|
||||
(insert "*** "
|
||||
(if-let ((outline (plist-get props :outline)))
|
||||
(if-let ((outline (plist-get prop :outline)))
|
||||
(-> outline
|
||||
(string-join " > ")
|
||||
(org-roam-buffer-expand-links file-from))
|
||||
"Top")
|
||||
"\n"
|
||||
(propertize
|
||||
(s-trim (s-replace "\n" " "
|
||||
(org-roam-buffer-expand-links (plist-get props :content) file-from)))
|
||||
'help-echo "mouse-1: visit backlinked note"
|
||||
'file-from file-from
|
||||
'file-from-point (plist-get props :point))
|
||||
"\n\n")))))
|
||||
(insert "\n\n* No backlinks!")))
|
||||
(if-let ((content (plist-get prop :content)))
|
||||
(propertize
|
||||
(s-trim (s-replace "\n" " " (org-roam-buffer-expand-links content file-from)))
|
||||
'help-echo "mouse-1: visit backlinked note"
|
||||
'file-from file-from
|
||||
'file-from-point (plist-get prop :point))
|
||||
"")
|
||||
"\n\n"))))
|
||||
(insert "\n\n* No backlinks!"))))
|
||||
|
||||
(defun org-roam-buffer-update ()
|
||||
"Update the `org-roam-buffer'."
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -41,6 +41,7 @@
|
||||
(defvar org-roam-directory)
|
||||
(defvar org-roam-mode)
|
||||
(defvar org-roam-title-to-slug-function)
|
||||
|
||||
(declare-function org-roam--get-title-path-completions "org-roam")
|
||||
(declare-function org-roam--get-ref-path-completions "org-roam")
|
||||
(declare-function org-roam--file-path-from-id "org-roam")
|
||||
@ -74,11 +75,11 @@ note with the given `ref'.")
|
||||
(defvar org-roam-capture-additional-template-props nil
|
||||
"Additional props to be added to the Org-roam template.")
|
||||
|
||||
(defconst org-roam-capture--template-keywords '(:file-name :head)
|
||||
(defconst org-roam-capture--template-keywords '(:file-name :head :olp)
|
||||
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
|
||||
|
||||
(defcustom org-roam-capture-templates
|
||||
'(("d" "default" plain (function org-roam-capture--get-point)
|
||||
`(("d" "default" plain (function org-roam-capture--get-point)
|
||||
"%?"
|
||||
:file-name "%<%Y%m%d%H%M%S>-${slug}"
|
||||
:head "#+title: ${title}\n"
|
||||
@ -218,10 +219,10 @@ Template string :\n%v")
|
||||
((const :format "%v " :kill-buffer) (const t))))))
|
||||
|
||||
(defcustom org-roam-capture-ref-templates
|
||||
'(("r" "ref" plain (function org-roam-capture--get-point)
|
||||
'(("r" "ref" plain #'org-roam-capture--get-point
|
||||
"%?"
|
||||
:file-name "${slug}"
|
||||
:head "#+title: ${title}\n#+roam_key: ${ref}\n"
|
||||
:head "#+title: ${title}\n#+roam_key: ${ref}"
|
||||
:unnarrowed t))
|
||||
"The Org-roam templates used during a capture from the roam-ref protocol.
|
||||
Details on how to specify for the template is given in `org-roam-capture-templates'."
|
||||
@ -408,26 +409,37 @@ aborted, we do the following:
|
||||
3. Add a function on `org-capture-before-finalize-hook' that saves
|
||||
the file if the original value of :no-save is not t and
|
||||
`org-note-abort' is not t."
|
||||
(let* ((name-templ (org-roam-capture--get :file-name))
|
||||
(let* ((name-templ (or (org-roam-capture--get :file-name)
|
||||
(user-error "Template needs to specify `:file-name'")))
|
||||
(new-id (s-trim (org-roam-capture--fill-template
|
||||
name-templ)))
|
||||
(file-path (org-roam--file-path-from-id new-id))
|
||||
(roam-head (org-roam-capture--get :head))
|
||||
(roam-head (or (org-roam-capture--get :head)
|
||||
""))
|
||||
(org-template (org-capture-get :template))
|
||||
(roam-template (concat roam-head org-template)))
|
||||
(unless (file-exists-p file-path)
|
||||
(unless (or (file-exists-p file-path)
|
||||
(cl-some (lambda (buffer)
|
||||
(string= (buffer-file-name buffer)
|
||||
file-path))
|
||||
(buffer-list)))
|
||||
(make-directory (file-name-directory file-path) t)
|
||||
(org-roam-capture--put :orig-no-save (org-capture-get :no-save)
|
||||
:new-file t)
|
||||
(org-capture-put :template
|
||||
;; Fixes org-capture-place-plain-text throwing 'invalid search bound'
|
||||
;; when both :unnarowed t and "%?" is missing from the template string;
|
||||
;; may become unnecessary when the upstream bug is fixed
|
||||
(if (s-contains-p "%?" roam-template)
|
||||
roam-template
|
||||
(concat roam-template "%?"))
|
||||
:type 'plain
|
||||
:no-save t))
|
||||
(pcase org-roam-capture--context
|
||||
('dailies
|
||||
;; Populate the header of the daily file before capture to prevent it
|
||||
;; from appearing in the buffer-restriction
|
||||
(save-window-excursion
|
||||
(find-file file-path)
|
||||
(insert (substring (org-capture-fill-template (concat roam-head "*"))
|
||||
0 -2))
|
||||
(set-buffer-modified-p nil))
|
||||
(org-capture-put :template org-template))
|
||||
(_
|
||||
(org-capture-put :template roam-template
|
||||
:type 'plain)))
|
||||
(org-capture-put :no-save t))
|
||||
file-path))
|
||||
|
||||
(defun org-roam-capture--get-point ()
|
||||
@ -446,32 +458,53 @@ If there is no file with that ref, a file with that ref is created.
|
||||
|
||||
This function is used solely in Org-roam's capture templates: see
|
||||
`org-roam-capture-templates'."
|
||||
(let ((file-path (pcase org-roam-capture--context
|
||||
('capture
|
||||
(or (cdr (assoc 'file org-roam-capture--info))
|
||||
(org-roam-capture--new-file)))
|
||||
('title
|
||||
(org-roam-capture--new-file))
|
||||
('dailies
|
||||
(org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info)))
|
||||
(org-roam-capture--new-file))
|
||||
('ref
|
||||
(let ((completions (org-roam--get-ref-path-completions))
|
||||
(ref (cdr (assoc 'ref org-roam-capture--info))))
|
||||
(if-let ((pl (cdr (assoc ref completions))))
|
||||
(plist-get pl :path)
|
||||
(org-roam-capture--new-file))))
|
||||
(_ (error "Invalid org-roam-capture-context")))))
|
||||
(let* ((file-path (pcase org-roam-capture--context
|
||||
('capture
|
||||
(or (cdr (assoc 'file org-roam-capture--info))
|
||||
(org-roam-capture--new-file)))
|
||||
('title
|
||||
(org-roam-capture--new-file))
|
||||
('dailies
|
||||
(org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info)))
|
||||
(org-roam-capture--new-file))
|
||||
('ref
|
||||
(let ((completions (org-roam--get-ref-path-completions))
|
||||
(ref (cdr (assoc 'ref org-roam-capture--info))))
|
||||
(if-let ((pl (cdr (assoc ref completions))))
|
||||
(plist-get pl :path)
|
||||
(org-roam-capture--new-file))))
|
||||
(_ (error "Invalid org-roam-capture-context")))))
|
||||
(org-capture-put :template
|
||||
(org-roam-capture--fill-template (org-capture-get :template)))
|
||||
(org-roam-capture--put :file-path file-path)
|
||||
(org-roam-capture--fill-template (org-capture-get :template)))
|
||||
(org-roam-capture--put :file-path file-path
|
||||
:finalize (or (org-capture-get :finalize)
|
||||
(org-roam-capture--get :finalize)))
|
||||
(while org-roam-capture-additional-template-props
|
||||
(let ((prop (pop org-roam-capture-additional-template-props))
|
||||
(val (pop org-roam-capture-additional-template-props)))
|
||||
(org-roam-capture--put prop val)))
|
||||
(set-buffer (org-capture-target-buffer file-path))
|
||||
(widen)
|
||||
(goto-char (point-max))))
|
||||
(if-let* ((olp (when (eq org-roam-capture--context 'dailies)
|
||||
(--> (org-roam-capture--get :olp)
|
||||
(pcase it
|
||||
((pred stringp)
|
||||
(list it))
|
||||
((pred listp)
|
||||
it)
|
||||
(wrong-type
|
||||
(signal 'wrong-type-argument
|
||||
`((stringp listp)
|
||||
,wrong-type))))))))
|
||||
(condition-case err
|
||||
(when-let ((marker (org-find-olp `(,file-path ,@olp))))
|
||||
(goto-char marker)
|
||||
(set-marker marker nil))
|
||||
(error
|
||||
(when (org-roam-capture--get :new-file)
|
||||
(kill-buffer))
|
||||
(signal (car err) (cdr err))))
|
||||
(goto-char (point-min)))))
|
||||
|
||||
(defun org-roam-capture--convert-template (template)
|
||||
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax."
|
||||
@ -512,7 +545,8 @@ GOTO and KEYS argument have the same functionality as
|
||||
`org-capture'."
|
||||
(let* ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates))
|
||||
(one-template-p (= (length org-capture-templates) 1))
|
||||
org-capture-templates-contexts)
|
||||
org-capture-templates-contexts
|
||||
(org-capture-link-is-already-stored t))
|
||||
(when one-template-p
|
||||
(setq keys (caar org-capture-templates)))
|
||||
(if (or one-template-p
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -79,6 +79,14 @@
|
||||
"org-roam 1.1.0")
|
||||
(define-obsolete-function-alias 'org-roam-db--clear 'org-roam-db-clear
|
||||
"org-roam 1.2.0")
|
||||
(define-obsolete-function-alias 'org-roam-dailies-today 'org-roam-dailies-find-today
|
||||
"org-roam 1.2.2")
|
||||
(define-obsolete-function-alias 'org-roam-dailies-yesterday 'org-roam-dailies-find-yesterday
|
||||
"org-roam 1.2.2")
|
||||
(define-obsolete-function-alias 'org-roam-dailies-tomorrow 'org-roam-dailies-find-tomorrow
|
||||
"org-roam 1.2.2")
|
||||
(define-obsolete-function-alias 'org-roam-dailies-date 'org-roam-dailies-find-date
|
||||
"org-roam 1.2.2")
|
||||
|
||||
;;;; Variables
|
||||
(define-obsolete-variable-alias 'org-roam-graphviz-extra-options
|
||||
@ -95,6 +103,9 @@
|
||||
'org-roam-dailies-capture-templates "org-roam 1.0.0")
|
||||
(define-obsolete-variable-alias 'org-roam-date-filename-format
|
||||
'org-roam-dailies-capture-templates "org-roam 1.0.0")
|
||||
(define-obsolete-variable-alias 'org-roam-update-db-idle-seconds
|
||||
'org-roam-db-update-idle-seconds "org-roam 1.2.2")
|
||||
|
||||
(make-obsolete-variable 'org-roam-buffer-no-delete-other-windows
|
||||
'org-roam-buffer-window-parameters "org-roam 1.1.1")
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -79,6 +79,7 @@ Return user choice."
|
||||
(if (fboundp 'ivy-read)
|
||||
(ivy-read prompt choices
|
||||
:initial-input initial-input
|
||||
:preselect initial-input
|
||||
:require-match require-match
|
||||
:action (prog1 action
|
||||
(setq action nil))
|
||||
|
@ -1,11 +1,13 @@
|
||||
;;; org-roam-dailies.el --- Daily notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
|
||||
;;; org-roam-dailies.el --- Daily-notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
|
||||
;;;
|
||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; Copyright © 2020 Leo Vivier <leo.vivier+dev@gmail.com>
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; Leo Vivier <leo.vivier+dev@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -27,7 +29,7 @@
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This library provides functionality for creating daily notes. This is a
|
||||
;; This library provides functionality for creating daily-notes. This is a
|
||||
;; concept borrowed from Roam Research.
|
||||
;;
|
||||
;;; Code:
|
||||
@ -35,102 +37,330 @@
|
||||
(require 'org-capture)
|
||||
(require 'org-roam-capture)
|
||||
(require 'org-roam-macs)
|
||||
(require 'f)
|
||||
|
||||
;;;; Declarations
|
||||
(defvar org-roam-mode)
|
||||
(defvar org-roam-directory)
|
||||
(declare-function org-roam--org-file-p "org-roam")
|
||||
(declare-function org-roam--file-path-from-id "org-roam")
|
||||
(declare-function org-roam--find-file "org-roam")
|
||||
(declare-function org-roam-mode "org-roam")
|
||||
|
||||
;;;; Customizable variables
|
||||
(defcustom org-roam-dailies-directory "daily/"
|
||||
"Path to daily-notes."
|
||||
:group 'org-roam
|
||||
:type 'string)
|
||||
|
||||
(defcustom org-roam-dailies-find-file-hook nil
|
||||
"Hook that is run right after navigating to a daily-note."
|
||||
:group 'org-roam
|
||||
:type 'hook)
|
||||
|
||||
(defcustom org-roam-dailies-capture-templates
|
||||
'(("d" "daily" plain (function org-roam-capture--get-point)
|
||||
""
|
||||
:immediate-finish t
|
||||
:file-name "%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>"))
|
||||
"Capture templates for daily notes in Org-roam."
|
||||
'(("d" "default" entry (function org-roam-capture--get-point)
|
||||
"* %?"
|
||||
:file-name "daily/%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>\n"))
|
||||
"Capture templates for daily-notes in Org-roam."
|
||||
:group 'org-roam
|
||||
;; Adapted from `org-capture-templates'
|
||||
:type
|
||||
'(repeat
|
||||
(choice :value ("d" "daily" plain (function org-roam-capture--get-point)
|
||||
""
|
||||
:immediate-finish t
|
||||
:file-name "%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>")
|
||||
(list :tag "Multikey description"
|
||||
(string :tag "Keys ")
|
||||
(string :tag "Description"))
|
||||
(list :tag "Template entry"
|
||||
(string :tag "Keys ")
|
||||
(string :tag "Description ")
|
||||
(const :format "" plain)
|
||||
(const :format "" (function org-roam-capture--get-point))
|
||||
(choice :tag "Template "
|
||||
(string :tag "String"
|
||||
:format "String:\n \
|
||||
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
|
||||
"%?"
|
||||
:file-name "daily/%<%Y-%m-%d>"
|
||||
:head "#+title: %<%Y-%m-%d>\n"
|
||||
:unnarrowed t)
|
||||
(list :tag "Multikey description"
|
||||
(string :tag "Keys ")
|
||||
(string :tag "Description"))
|
||||
(list :tag "Template entry"
|
||||
(string :tag "Keys ")
|
||||
(string :tag "Description ")
|
||||
(choice :tag "Type "
|
||||
(const :tag "Plain" plain)
|
||||
(const :tag "Entry (for creating headlines)" entry))
|
||||
(const :format "" #'org-roam-capture--get-point)
|
||||
(choice :tag "Template "
|
||||
(string :tag "String"
|
||||
:format "String:\n \
|
||||
Template string :\n%v")
|
||||
(list :tag "File"
|
||||
(const :format "" file)
|
||||
(file :tag "Template file "))
|
||||
(list :tag "Function"
|
||||
(const :format "" function)
|
||||
(function :tag "Template function ")))
|
||||
(const :format "" :immediate-finish) (const :format "" t)
|
||||
(const :format "File name format :" :file-name)
|
||||
(string :format " %v" :value "#+title: ${title}\n")
|
||||
(const :format "Header format :" :head)
|
||||
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
|
||||
(plist :inline t
|
||||
:tag "Options"
|
||||
;; Give the most common options as checkboxes
|
||||
:options
|
||||
(((const :format "%v " :prepend) (const t))
|
||||
((const :format "%v " :jump-to-captured) (const t))
|
||||
((const :format "%v " :empty-lines) (const 1))
|
||||
((const :format "%v " :empty-lines-before) (const 1))
|
||||
((const :format "%v " :empty-lines-after) (const 1))
|
||||
((const :format "%v " :clock-in) (const t))
|
||||
((const :format "%v " :clock-keep) (const t))
|
||||
((const :format "%v " :clock-resume) (const t))
|
||||
((const :format "%v " :time-prompt) (const t))
|
||||
((const :format "%v " :tree-type) (const week))
|
||||
((const :format "%v " :table-line-pos) (string))
|
||||
((const :format "%v " :kill-buffer) (const t))
|
||||
((const :format "%v " :unnarrowed) (const t))))))))
|
||||
(list :tag "File"
|
||||
(const :format "" file)
|
||||
(file :tag "Template file "))
|
||||
(list :tag "Function"
|
||||
(const :format "" function)
|
||||
(function :tag "Template function ")))
|
||||
(const :format "File name format :" :file-name)
|
||||
(string :format " %v" :value "daily/%<%Y-%m-%d>")
|
||||
(const :format "Header format :" :head)
|
||||
(string :format " %v" :value "#+title: ${title}\n")
|
||||
(plist :inline t
|
||||
:tag "Options"
|
||||
;; Give the most common options as checkboxes
|
||||
:options
|
||||
(((const :tag "Outline path" :olp)
|
||||
(repeat :tag "Headings"
|
||||
(string :tag "Heading")))
|
||||
((const :format "%v " :unnarrowed) (const t))
|
||||
((const :format "%v " :prepend) (const t))
|
||||
((const :format "%v " :immediate-finish) (const t))
|
||||
((const :format "%v " :jump-to-captured) (const t))
|
||||
((const :format "%v " :empty-lines) (const 1))
|
||||
((const :format "%v " :empty-lines-before) (const 1))
|
||||
((const :format "%v " :empty-lines-after) (const 1))
|
||||
((const :format "%v " :clock-in) (const t))
|
||||
((const :format "%v " :clock-keep) (const t))
|
||||
((const :format "%v " :clock-resume) (const t))
|
||||
((const :format "%v " :time-prompt) (const t))
|
||||
((const :format "%v " :tree-type) (const week))
|
||||
((const :format "%v " :table-line-pos) (string))
|
||||
((const :format "%v " :kill-buffer) (const t))))))))
|
||||
|
||||
;; Declarations
|
||||
(defvar org-roam-mode)
|
||||
(declare-function org-roam--file-path-from-id "org-roam")
|
||||
(declare-function org-roam-mode "org-roam")
|
||||
;;;; Utilities
|
||||
(defun org-roam-dailies-directory--get-absolute-path ()
|
||||
"Get absolute path to `org-roam-dailies-directory'."
|
||||
(-> (concat
|
||||
(file-name-as-directory org-roam-directory)
|
||||
org-roam-dailies-directory)
|
||||
(file-truename)))
|
||||
|
||||
(defun org-roam-dailies--file-for-time (time)
|
||||
"Create and find file for TIME."
|
||||
(let ((org-roam-capture-templates org-roam-dailies-capture-templates)
|
||||
(defun org-roam-dailies-find-directory ()
|
||||
"Find and open `org-roam-dailies-directory'."
|
||||
(interactive)
|
||||
(org-roam--find-file (org-roam-dailies-directory--get-absolute-path)))
|
||||
|
||||
(defun org-roam-dailies--daily-note-p (&optional file)
|
||||
"Return t if FILE is an Org-roam daily-note, nil otherwise.
|
||||
|
||||
If FILE is not specified, use the current buffer's file-path."
|
||||
(when-let ((path (or file
|
||||
(-> (buffer-base-buffer)
|
||||
(buffer-file-name))))
|
||||
(directory (org-roam-dailies-directory--get-absolute-path)))
|
||||
(setq path (file-truename path))
|
||||
(save-match-data
|
||||
(and
|
||||
(org-roam--org-file-p path)
|
||||
(f-descendant-of-p path directory)))))
|
||||
|
||||
(defun org-roam-dailies--capture (time &optional goto)
|
||||
"Capture an entry in a daily-note for TIME, creating it if necessary.
|
||||
|
||||
When GOTO is non-nil, go the note without creating an entry."
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(let ((org-roam-capture-templates (--> org-roam-dailies-capture-templates
|
||||
(if goto (list (car it)) it)))
|
||||
(org-roam-capture--info (list (cons 'time time)))
|
||||
(org-roam-capture--context 'dailies))
|
||||
(setq org-roam-capture-additional-template-props (list :finalize 'find-file))
|
||||
(org-roam-capture--capture)))
|
||||
(org-roam-capture--capture (when goto '(4)))))
|
||||
|
||||
(defun org-roam-dailies-today ()
|
||||
"Create and find the daily note for today."
|
||||
;;;; Commands
|
||||
;;; Today
|
||||
(defun org-roam-dailies-capture-today (&optional goto)
|
||||
"Create an entry in the daily-note for today.
|
||||
|
||||
When GOTO is non-nil, go the note without creating an entry."
|
||||
(interactive "P")
|
||||
(org-roam-dailies--capture (current-time) goto)
|
||||
(when goto
|
||||
(run-hooks 'org-roam-dailies-find-file-hook)
|
||||
(message "Showing daily-note for today")))
|
||||
|
||||
(defun org-roam-dailies-find-today ()
|
||||
"Find the daily-note for today, creating it if necessary."
|
||||
(interactive)
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(org-roam-dailies--file-for-time (current-time)))
|
||||
(org-roam-dailies-capture-today t))
|
||||
|
||||
(defun org-roam-dailies-tomorrow (n)
|
||||
"Create and find the daily note for tomorrow.
|
||||
With numeric argument N, use N days in the future."
|
||||
;;; Tomorrow
|
||||
(defun org-roam-dailies-capture-tomorrow (n &optional goto)
|
||||
"Create an entry in the daily-note for tomorrow.
|
||||
|
||||
With numeric argument N, use the daily-note N days in the future.
|
||||
|
||||
With a `C-u' prefix or when GOTO is non-nil, go the note without
|
||||
creating an entry."
|
||||
(interactive "p")
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(org-roam-dailies--file-for-time (time-add (* n 86400) (current-time))))
|
||||
(org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto))
|
||||
|
||||
(defun org-roam-dailies-yesterday (n)
|
||||
"Create and find the file for yesterday.
|
||||
With numeric argument N, use N days in the past."
|
||||
(defun org-roam-dailies-find-tomorrow (n)
|
||||
"Find the daily-note for tomorrow, creating it if necessary.
|
||||
|
||||
With numeric argument N, use the daily-note N days in the
|
||||
future."
|
||||
(interactive "p")
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(org-roam-dailies-tomorrow (- n)))
|
||||
(org-roam-dailies-capture-tomorrow n t))
|
||||
|
||||
(defun org-roam-dailies-date ()
|
||||
"Create the file for any date using the calendar interface."
|
||||
;;; Yesterday
|
||||
(defun org-roam-dailies-capture-yesterday (n &optional goto)
|
||||
"Create an entry in the daily-note for yesteday.
|
||||
|
||||
With numeric argument N, use the daily-note N days in the past.
|
||||
|
||||
When GOTO is non-nil, go the note without creating an entry."
|
||||
(interactive "p")
|
||||
(org-roam-dailies-capture-tomorrow (- n) goto))
|
||||
|
||||
(defun org-roam-dailies-find-yesterday (n)
|
||||
"Find the daily-note for yesterday, creating it if necessary.
|
||||
|
||||
With numeric argument N, use the daily-note N days in the
|
||||
future."
|
||||
(interactive "p")
|
||||
(org-roam-dailies-capture-tomorrow (- n) t))
|
||||
|
||||
;;; Calendar
|
||||
(defvar org-roam-dailies-calendar-hook (list 'org-roam-dailies-calendar-mark-entries)
|
||||
"Hooks to run when showing the `org-roam-dailies-calendar'.")
|
||||
|
||||
(defun org-roam-dailies-calendar--install-hook ()
|
||||
"Install Org-roam-dailies hooks to calendar."
|
||||
(add-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
|
||||
(add-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
|
||||
|
||||
(defun org-roam-dailies-calendar--run-hook ()
|
||||
"Run Org-roam-dailies hooks to calendar."
|
||||
(run-hooks 'org-roam-dailies-calendar-hook)
|
||||
(remove-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
|
||||
(remove-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
|
||||
|
||||
(defun org-roam-dailies-calendar--file-to-date (&optional file)
|
||||
"Convert FILE to date.
|
||||
|
||||
Return (MONTH DAY YEAR)."
|
||||
(let ((file (or file
|
||||
(-> (buffer-base-buffer)
|
||||
(buffer-file-name)))))
|
||||
(cl-destructuring-bind (_ _ _ d m y _ _ _)
|
||||
(-> file
|
||||
(file-name-nondirectory)
|
||||
(file-name-sans-extension)
|
||||
(org-parse-time-string))
|
||||
(list m d y))))
|
||||
|
||||
(defun org-roam-dailies-calendar--date-to-time (date)
|
||||
"Convert DATE as returned from the calendar (MONTH DAY YEAR) to a time."
|
||||
(encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
|
||||
|
||||
(defun org-roam-dailies-calendar-mark-entries ()
|
||||
"Mark days in the calendar for which a daily-note is present."
|
||||
(when (file-exists-p (org-roam-dailies-directory--get-absolute-path))
|
||||
(dolist (date (mapcar #'org-roam-dailies-calendar--file-to-date
|
||||
(org-roam-dailies--list-files)))
|
||||
(when (calendar-date-is-visible-p date)
|
||||
(calendar-mark-visible-date date 'org-roam-dailies-calendar-note)))))
|
||||
|
||||
;;; Date
|
||||
(defun org-roam-dailies-capture-date (&optional goto prefer-future)
|
||||
"Create an entry in the daily-note for a date using the calendar.
|
||||
|
||||
Prefer past dates, unless PREFER-FUTURE is non-nil.
|
||||
|
||||
With a `C-u' prefix or when GOTO is non-nil, go the note without
|
||||
creating an entry."
|
||||
(interactive "P")
|
||||
(org-roam-dailies-calendar--install-hook)
|
||||
(let* ((time-str (let ((org-read-date-prefer-future prefer-future))
|
||||
(org-read-date nil nil nil (if goto
|
||||
"Find daily-note: "
|
||||
"Capture to daily-note: "))))
|
||||
(time (org-read-date nil t time-str)))
|
||||
(org-roam-dailies--capture time goto)
|
||||
(when goto
|
||||
(run-hooks 'org-roam-dailies-find-file-hook)
|
||||
(message "Showing note for %s" time-str))))
|
||||
|
||||
(defun org-roam-dailies-find-date (&optional prefer-future)
|
||||
"Find the daily-note for a date using the calendar, creating it if necessary.
|
||||
|
||||
Prefer past dates, unless PREFER-FUTURE is non-nil."
|
||||
(interactive)
|
||||
(let ((time (org-read-date nil 'to-time nil "Date: ")))
|
||||
(org-roam-dailies--file-for-time time)))
|
||||
(org-roam-dailies-capture-date t prefer-future))
|
||||
|
||||
;;; Navigation
|
||||
(defun org-roam-dailies--list-files (&rest extra-files)
|
||||
"List all files in `org-roam-dailies-directory'.
|
||||
|
||||
EXTRA-FILES can be used to append extra files to the list."
|
||||
(let ((dir (org-roam-dailies-directory--get-absolute-path)))
|
||||
(append (--remove (let ((file (file-name-nondirectory it)))
|
||||
(when (or (auto-save-file-name-p file)
|
||||
(backup-file-name-p file)
|
||||
(string-match "^\\." file))
|
||||
it))
|
||||
(directory-files-recursively dir ""))
|
||||
extra-files)))
|
||||
|
||||
(defun org-roam-dailies--find-next-note-path (&optional n file)
|
||||
"Find next daily-note from FILE.
|
||||
|
||||
With numeric argument N, find note N days in the future. If N is
|
||||
negative, find note N days in the past.
|
||||
|
||||
If FILE is not provided, use the file visited by the current
|
||||
buffer."
|
||||
(unless (org-roam-dailies--daily-note-p file)
|
||||
(user-error "Not in a daily-note"))
|
||||
(let ((n (or n 1))
|
||||
(file (or file
|
||||
(-> (buffer-base-buffer)
|
||||
(buffer-file-name)))))
|
||||
;; Ensure that the buffer is saved before moving
|
||||
(save-buffer file)
|
||||
(let* ((list (org-roam-dailies--list-files))
|
||||
(position
|
||||
(cl-position-if (lambda (candidate)
|
||||
(string= file candidate))
|
||||
list)))
|
||||
(pcase n
|
||||
((pred (natnump))
|
||||
(if (eq position (- (length list) 1))
|
||||
(user-error "Already at newest note")
|
||||
(message "Showing next daily-note")))
|
||||
((pred (integerp))
|
||||
(if (eq position 0)
|
||||
(user-error "Already at oldest note")
|
||||
(message "Showing previous daily-note"))))
|
||||
(nth (+ position n) list))))
|
||||
|
||||
(defun org-roam-dailies-find-next-note (&optional n)
|
||||
"Find next daily-note.
|
||||
|
||||
With numeric argument N, find note N days in the future. If N is
|
||||
negative, find note N days in the past."
|
||||
(interactive "p")
|
||||
(let* ((n (or n 1))
|
||||
(next (org-roam-dailies--find-next-note-path n)))
|
||||
(find-file next)
|
||||
(run-hooks 'org-roam-dailies-find-file-hook)))
|
||||
|
||||
(defun org-roam-dailies-find-previous-note (&optional n)
|
||||
"Find previous daily-note.
|
||||
|
||||
With numeric argument N, find note N days in the past. If N is
|
||||
negative, find note N days in the future."
|
||||
(interactive "p")
|
||||
(let ((n (if n (- n) -1)))
|
||||
(org-roam-dailies-find-next-note n)))
|
||||
|
||||
;;;; Bindings
|
||||
(defvar org-roam-dailies-map (make-sparse-keymap)
|
||||
"Keymap for `org-roam-dailies'.")
|
||||
|
||||
(define-prefix-command 'org-roam-dailies-map)
|
||||
|
||||
(define-key org-roam-dailies-map (kbd "d") #'org-roam-dailies-find-today)
|
||||
(define-key org-roam-dailies-map (kbd "y") #'org-roam-dailies-find-yesterday)
|
||||
(define-key org-roam-dailies-map (kbd "t") #'org-roam-dailies-find-tomorrow)
|
||||
(define-key org-roam-dailies-map (kbd "n") #'org-roam-dailies-capture-today)
|
||||
(define-key org-roam-dailies-map (kbd "f") #'org-roam-dailies-find-next-note)
|
||||
(define-key org-roam-dailies-map (kbd "b") #'org-roam-dailies-find-previous-note)
|
||||
(define-key org-roam-dailies-map (kbd "c") #'org-roam-dailies-find-date)
|
||||
(define-key org-roam-dailies-map (kbd "v") #'org-roam-dailies-capture-date)
|
||||
(define-key org-roam-dailies-map (kbd ".") #'org-roam-dailies-find-directory)
|
||||
|
||||
(provide 'org-roam-dailies)
|
||||
|
||||
|
458
org-roam-db.el
458
org-roam-db.el
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -35,17 +35,22 @@
|
||||
(require 'emacsql)
|
||||
(require 'emacsql-sqlite3)
|
||||
(require 'seq)
|
||||
(require 'org-macs)
|
||||
(require 'org-roam-macs)
|
||||
|
||||
(eval-and-compile
|
||||
(require 'org-roam-macs)
|
||||
;; For `org-with-wide-buffer'
|
||||
(require 'org-macs))
|
||||
|
||||
(defvar org-roam-directory)
|
||||
(defvar org-roam-enable-headline-linking)
|
||||
(defvar org-roam-verbose)
|
||||
(defvar org-roam-file-name)
|
||||
|
||||
(defvar org-agenda-files)
|
||||
|
||||
(declare-function org-roam--org-roam-file-p "org-roam")
|
||||
(declare-function org-roam--extract-titles "org-roam")
|
||||
(declare-function org-roam--extract-ref "org-roam")
|
||||
(declare-function org-roam--extract-refs "org-roam")
|
||||
(declare-function org-roam--extract-tags "org-roam")
|
||||
(declare-function org-roam--extract-ids "org-roam")
|
||||
(declare-function org-roam--extract-links "org-roam")
|
||||
@ -79,11 +84,33 @@ value like `most-positive-fixnum'."
|
||||
:type 'int
|
||||
:group 'org-roam)
|
||||
|
||||
(defconst org-roam-db--version 9)
|
||||
(defconst org-roam-db--version 10)
|
||||
|
||||
(defvar org-roam-db--connection (make-hash-table :test #'equal)
|
||||
"Database connection to Org-roam database.")
|
||||
|
||||
(defvar org-roam-db-dirty nil
|
||||
"Whether the org-roam database is dirty and requires an update.
|
||||
Contains pairs of `org-roam-directory' and `org-roam-db-location'
|
||||
so that multi-directories are updated.")
|
||||
|
||||
(defcustom org-roam-db-update-method 'idle-timer
|
||||
"Method to update the Org-roam database.
|
||||
|
||||
`immediate'
|
||||
Update the database immediately upon file changes.
|
||||
|
||||
`idle-timer'
|
||||
Updates the database if dirty, if Emacs idles for `org-roam-db-update-idle-seconds'."
|
||||
:type '(set (const :tag "idle-timer" idle-timer)
|
||||
(const :tag "immediate" immediate))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-db-update-idle-seconds 2
|
||||
"Number of idle seconds before triggering an Org-roam database update."
|
||||
:type 'integer
|
||||
:group 'org-roam)
|
||||
|
||||
;;;; Core Functions
|
||||
|
||||
(defun org-roam-db--get-connection ()
|
||||
@ -141,8 +168,8 @@ SQL can be either the emacsql vector representation, or a string."
|
||||
(level :not-null)])
|
||||
|
||||
(links
|
||||
[(from :not-null)
|
||||
(to :not-null)
|
||||
[(source :not-null)
|
||||
(dest :not-null)
|
||||
(type :not-null)
|
||||
(properties :not-null)])
|
||||
|
||||
@ -191,6 +218,22 @@ the current `org-roam-directory'."
|
||||
(dolist (conn (hash-table-values org-roam-db--connection))
|
||||
(org-roam-db--close conn)))
|
||||
|
||||
;;;; Timer-based updating
|
||||
(defvar org-roam-db-file-update-timer nil
|
||||
"Timer for updating the database when dirty.")
|
||||
|
||||
(defun org-roam-db-mark-dirty ()
|
||||
"Mark the Org-roam database as dirty."
|
||||
(add-to-list 'org-roam-db-dirty (list org-roam-directory org-roam-db-location)
|
||||
nil #'equal))
|
||||
|
||||
(defun org-roam-db-update-cache-on-timer ()
|
||||
"Update the cache if the database is dirty.
|
||||
This function is called on `org-roam-db-file-update-timer'."
|
||||
(pcase-dolist (`(,org-roam-directory ,org-roam-db-location) org-roam-db-dirty)
|
||||
(org-roam-db-build-cache))
|
||||
(setq org-roam-db-dirty nil))
|
||||
|
||||
;;;; Database API
|
||||
;;;;; Initialization
|
||||
(defun org-roam-db--initialized-p ()
|
||||
@ -219,82 +262,137 @@ This is equivalent to removing the node from the graph."
|
||||
(buffer-file-name (buffer-base-buffer))))))
|
||||
(dolist (table (mapcar #'car org-roam-db--table-schemata))
|
||||
(org-roam-db-query `[:delete :from ,table
|
||||
:where (= ,(if (eq table 'links) 'from 'file) $s1)]
|
||||
:where (= ,(if (eq table 'links) 'source 'file) $s1)]
|
||||
file))))
|
||||
|
||||
;;;;; Insertion
|
||||
(defun org-roam-db--insert-meta (file hash meta)
|
||||
"Insert HASH and META for a FILE into the Org-roam cache."
|
||||
(org-roam-db-query
|
||||
[:insert :into files
|
||||
:values $v1]
|
||||
(list (vector file hash meta))))
|
||||
;;;;; Inserting
|
||||
(defun org-roam-db--insert-meta (&optional update-p)
|
||||
"Update the metadata of the current buffer into the cache.
|
||||
If UPDATE-P is non-nil, first remove the meta for the file in the database."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(attr (file-attributes file))
|
||||
(atime (file-attribute-access-time attr))
|
||||
(mtime (file-attribute-modification-time attr))
|
||||
(hash (org-roam-db--file-hash)))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from files
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(org-roam-db-query
|
||||
[:insert :into files
|
||||
:values $v1]
|
||||
(list (vector file hash (list :atime atime :mtime mtime))))))
|
||||
|
||||
(defun org-roam-db--insert-links (links)
|
||||
"Insert LINKS into the Org-roam cache."
|
||||
(org-roam-db-query
|
||||
[:insert :into links
|
||||
:values $v1]
|
||||
links))
|
||||
(defun org-roam-db--insert-titles (&optional update-p)
|
||||
"Update the titles of the current buffer into the cache.
|
||||
If UPDATE-P is non-nil, first remove titles for the file in the database.
|
||||
Returns the number of rows inserted."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(titles (or (org-roam--extract-titles)
|
||||
(list (org-roam--path-to-slug file))))
|
||||
(rows (mapcar (lambda (title)
|
||||
(vector file title)) titles)))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from titles
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(org-roam-db-query
|
||||
[:insert :into titles
|
||||
:values $v1]
|
||||
rows)
|
||||
(length rows)))
|
||||
|
||||
(defun org-roam-db--insert-titles (file titles)
|
||||
"Insert TITLES for a FILE into the Org-roam cache."
|
||||
(org-roam-db-query
|
||||
[:insert :into titles
|
||||
:values $v1]
|
||||
(mapcar (lambda (title)
|
||||
(vector file title)) titles)))
|
||||
(defun org-roam-db--insert-refs (&optional update-p)
|
||||
"Update the refs of the current buffer into the cache.
|
||||
If UPDATE-P is non-nil, first remove the ref for the file in the database."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(count 0))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from refs
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(when-let ((refs (org-roam--extract-refs)))
|
||||
(dolist (ref refs)
|
||||
(let ((key (cdr ref))
|
||||
(type (car ref)))
|
||||
(condition-case nil
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into refs :values $v1]
|
||||
(list (vector key file type)))
|
||||
(cl-incf count))
|
||||
(error
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
|
||||
key
|
||||
file
|
||||
(caar (org-roam-db-query
|
||||
[:select file :from refs
|
||||
:where (= ref $v1)]
|
||||
(vector key))))))))))
|
||||
count))
|
||||
|
||||
(defun org-roam-db--insert-ids (ids)
|
||||
"Insert IDS into the Org-roam cache.
|
||||
Returns t if the insertion was successful, nil otherwise.
|
||||
Insertions can fail when there is an ID conflict."
|
||||
(condition-case nil
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into ids
|
||||
:values $v1]
|
||||
ids)
|
||||
t)
|
||||
(error
|
||||
(unless (listp ids)
|
||||
(setq ids (list ids)))
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
|
||||
(aref (car ids) 1)
|
||||
(string-join (mapcar (lambda (hl)
|
||||
(aref hl 0)) ids) "\n")))
|
||||
nil)))
|
||||
|
||||
(defun org-roam-db--insert-tags (file tags)
|
||||
"Insert TAGS for a FILE into the Org-roam cache."
|
||||
(org-roam-db-query
|
||||
[:insert :into tags
|
||||
:values $v1]
|
||||
(list (vector file tags))))
|
||||
|
||||
(defun org-roam-db--insert-ref (file ref)
|
||||
"Insert REF for FILE into the Org-roam cache.
|
||||
Returns t if successful, and nil otherwise.
|
||||
Insertions can fail if the key is already in the database."
|
||||
(let ((key (cdr ref))
|
||||
(type (car ref)))
|
||||
(condition-case nil
|
||||
(defun org-roam-db--insert-links (&optional update-p)
|
||||
"Update the file links of the current buffer in the cache.
|
||||
If UPDATE-P is non-nil, first remove the links for the file in the database.
|
||||
Return the number of rows inserted."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from links
|
||||
:where (= source $s1)]
|
||||
file))
|
||||
(if-let ((links (org-roam--extract-links)))
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into refs :values $v1]
|
||||
(list (vector key file type)))
|
||||
t)
|
||||
(error
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
|
||||
key
|
||||
file
|
||||
(caar (org-roam-db-query
|
||||
[:select file :from refs
|
||||
:where (= ref $v1)]
|
||||
(vector key)))))
|
||||
nil))))
|
||||
[:insert :into links
|
||||
:values $v1]
|
||||
links)
|
||||
(length links))
|
||||
0)))
|
||||
|
||||
(defun org-roam-db--insert-ids (&optional update-p)
|
||||
"Update the ids of the current buffer into the cache.
|
||||
If UPDATE-P is non-nil, first remove ids for the file in the database.
|
||||
Returns the number of rows inserted."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from ids
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(if-let ((ids (org-roam--extract-ids file)))
|
||||
(condition-case nil
|
||||
(progn
|
||||
(org-roam-db-query
|
||||
[:insert :into ids
|
||||
:values $v1]
|
||||
ids)
|
||||
(length ids))
|
||||
(error
|
||||
(lwarn '(org-roam) :error
|
||||
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
|
||||
(aref (car ids) 1)
|
||||
(string-join (mapcar (lambda (hl)
|
||||
(aref hl 0)) ids) "\n")))
|
||||
0))
|
||||
0)))
|
||||
|
||||
(defun org-roam-db--insert-tags (&optional update-p)
|
||||
"Insert tags for the current buffer into the Org-roam cache.
|
||||
If UPDATE-P is non-nil, first remove tags for the file in the database.
|
||||
Return the number of rows inserted."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(tags (org-roam--extract-tags file)))
|
||||
(when update-p
|
||||
(org-roam-db-query [:delete :from tags
|
||||
:where (= file $s1)]
|
||||
file))
|
||||
(if tags
|
||||
(progn (org-roam-db-query
|
||||
[:insert :into tags
|
||||
:values $v1]
|
||||
(list (vector file tags)))
|
||||
1)
|
||||
0)))
|
||||
|
||||
;;;;; Fetching
|
||||
(defun org-roam-db--get-current-files ()
|
||||
@ -329,12 +427,12 @@ If the file does not have any connections, nil is returned."
|
||||
links_of(file, link) AS
|
||||
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
|
||||
citelinks AS (SELECT * FROM links
|
||||
JOIN refs ON links.\"to\" = refs.\"ref\"
|
||||
JOIN refs ON links.\"dest\" = refs.\"ref\"
|
||||
AND links.\"type\" = '\"cite\"')
|
||||
SELECT \"from\", \"to\" FROM filelinks UNION
|
||||
SELECT \"to\", \"from\" FROM filelinks UNION
|
||||
SELECT \"file\", \"from\" FROM citelinks UNION
|
||||
SELECT \"from\", \"file\" FROM citelinks),
|
||||
SELECT \"source\", \"dest\" FROM filelinks UNION
|
||||
SELECT \"dest\", \"source\" FROM filelinks UNION
|
||||
SELECT \"file\", \"source\" FROM citelinks UNION
|
||||
SELECT \"dest\", \"file\" FROM citelinks),
|
||||
connected_component(file) AS
|
||||
(SELECT link FROM links_of WHERE file = $s1
|
||||
UNION
|
||||
@ -351,12 +449,12 @@ connections, nil is returned."
|
||||
links_of(file, link) AS
|
||||
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
|
||||
citelinks AS (SELECT * FROM links
|
||||
JOIN refs ON links.\"to\" = refs.\"ref\"
|
||||
JOIN refs ON links.\"dest\" = refs.\"ref\"
|
||||
AND links.\"type\" = '\"cite\"')
|
||||
SELECT \"from\", \"to\" FROM filelinks UNION
|
||||
SELECT \"to\", \"from\" FROM filelinks UNION
|
||||
SELECT \"file\", \"from\" FROM citelinks UNION
|
||||
SELECT \"from\", \"file\" FROM citelinks),
|
||||
SELECT \"source\", \"dest\" FROM filelinks UNION
|
||||
SELECT \"dest\", \"source\" FROM filelinks UNION
|
||||
SELECT \"file\", \"source\" FROM citelinks UNION
|
||||
SELECT \"source\", \"file\" FROM citelinks),
|
||||
-- Links are traversed in a breadth-first search. In order to calculate the
|
||||
-- distance of nodes and to avoid following cyclic links, the visited nodes
|
||||
-- are tracked in 'trace'.
|
||||
@ -387,65 +485,6 @@ connections, nil is returned."
|
||||
(secure-hash 'sha1 (current-buffer)))))
|
||||
|
||||
;;;;; Updating
|
||||
(defun org-roam-db--update-meta ()
|
||||
"Update the metadata of the current buffer into the cache."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(attr (file-attributes file))
|
||||
(atime (file-attribute-access-time attr))
|
||||
(mtime (file-attribute-modification-time attr))
|
||||
(hash (org-roam-db--file-hash)))
|
||||
(org-roam-db-query [:delete :from files
|
||||
:where (= file $s1)]
|
||||
file)
|
||||
(org-roam-db--insert-meta file hash (list :atime atime :mtime mtime))))
|
||||
|
||||
(defun org-roam-db--update-titles ()
|
||||
"Update the title of the current buffer into the cache."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(titles (or (org-roam--extract-titles)
|
||||
(list (org-roam--path-to-slug file)))))
|
||||
(org-roam-db-query [:delete :from titles
|
||||
:where (= file $s1)]
|
||||
file)
|
||||
(org-roam-db--insert-titles file titles)))
|
||||
|
||||
(defun org-roam-db--update-tags ()
|
||||
"Update the tags of the current buffer into the cache."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
||||
(tags (org-roam--extract-tags file)))
|
||||
(org-roam-db-query [:delete :from tags
|
||||
:where (= file $s1)]
|
||||
file)
|
||||
(when tags
|
||||
(org-roam-db--insert-tags file tags))))
|
||||
|
||||
(defun org-roam-db--update-refs ()
|
||||
"Update the ref of the current buffer into the cache."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(org-roam-db-query [:delete :from refs
|
||||
:where (= file $s1)]
|
||||
file)
|
||||
(when-let ((ref (org-roam--extract-ref)))
|
||||
(org-roam-db--insert-ref file ref))))
|
||||
|
||||
(defun org-roam-db--update-links ()
|
||||
"Update the file links of the current buffer in the cache."
|
||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(org-roam-db-query [:delete :from links
|
||||
:where (= from $s1)]
|
||||
file)
|
||||
(when-let ((links (org-roam--extract-links)))
|
||||
(org-roam-db--insert-links links))))
|
||||
|
||||
(defun org-roam-db--update-ids ()
|
||||
"Update the ids of the current buffer into the cache."
|
||||
(let* ((file (or org-roam-file-name (buffer-file-name))))
|
||||
(org-roam-db-query [:delete :from ids
|
||||
:where (= file $s1)]
|
||||
file)
|
||||
(when-let ((ids (org-roam--extract-ids file)))
|
||||
(org-roam-db--insert-ids ids))))
|
||||
|
||||
(defun org-roam-db--update-file (&optional file-path)
|
||||
"Update Org-roam cache for FILE-PATH.
|
||||
If the file does not exist anymore, remove it from the cache.
|
||||
@ -460,13 +499,13 @@ If the file exists, update the cache with information."
|
||||
(save-buffer)))
|
||||
(org-roam--with-temp-buffer file-path
|
||||
(emacsql-with-transaction (org-roam-db)
|
||||
(org-roam-db--update-meta)
|
||||
(org-roam-db--update-tags)
|
||||
(org-roam-db--update-titles)
|
||||
(org-roam-db--update-refs)
|
||||
(org-roam-db--insert-meta 'update)
|
||||
(org-roam-db--insert-tags 'update)
|
||||
(org-roam-db--insert-titles 'update)
|
||||
(org-roam-db--insert-refs 'update)
|
||||
(when org-roam-enable-headline-linking
|
||||
(org-roam-db--update-ids))
|
||||
(org-roam-db--update-links)))))
|
||||
(org-roam-db--insert-ids 'update))
|
||||
(org-roam-db--insert-links 'update)))))
|
||||
|
||||
(defun org-roam-db-build-cache (&optional force)
|
||||
"Build the cache for `org-roam-directory'.
|
||||
@ -476,72 +515,67 @@ If FORCE, force a rebuild of the cache from scratch."
|
||||
(org-roam-db--close) ;; Force a reconnect
|
||||
(org-roam-db) ;; To initialize the database, no-op if already initialized
|
||||
(let* ((gc-cons-threshold org-roam-db-gc-threshold)
|
||||
(org-agenda-files nil)
|
||||
(org-agenda-files nil)
|
||||
(org-roam-files (org-roam--list-all-files))
|
||||
(current-files (org-roam-db--get-current-files))
|
||||
(file-count 0)
|
||||
(id-count 0)
|
||||
(link-count 0)
|
||||
(tag-count 0)
|
||||
(title-count 0)
|
||||
(ref-count 0)
|
||||
(deleted-count 0)
|
||||
(processed-count 0))
|
||||
(emacsql-with-transaction (org-roam-db)
|
||||
;; Two-step building
|
||||
;; First step: Rebuild files and ids
|
||||
(dolist (file org-roam-files)
|
||||
(org-roam-message "Processed %s/%s files..." processed-count (length org-roam-files))
|
||||
(let* ((attr (file-attributes file))
|
||||
(atime (file-attribute-access-time attr))
|
||||
(mtime (file-attribute-modification-time attr)))
|
||||
(let ((contents-hash (org-roam-db--file-hash file)))
|
||||
(unless (string= (gethash file current-files)
|
||||
contents-hash)
|
||||
(condition-case nil
|
||||
(org-roam--with-temp-buffer file
|
||||
(org-roam-db--clear-file file)
|
||||
(org-roam-db-query
|
||||
[:insert :into files
|
||||
:values $v1]
|
||||
(vector file contents-hash (list :atime atime :mtime mtime)))
|
||||
(setq file-count (1+ file-count))
|
||||
(when org-roam-enable-headline-linking
|
||||
(when-let ((ids (org-roam--extract-ids file)))
|
||||
(when (org-roam-db--insert-ids ids)
|
||||
(setq id-count (+ id-count (length ids))))))
|
||||
(when-let (links (org-roam--extract-links file))
|
||||
(org-roam-db-query
|
||||
[:insert :into links
|
||||
:values $v1]
|
||||
links)
|
||||
(setq link-count (1+ link-count)))
|
||||
(when-let (tags (org-roam--extract-tags file))
|
||||
(org-roam-db-query
|
||||
[:insert :into tags
|
||||
:values $v1]
|
||||
(vector file tags))
|
||||
(setq tag-count (1+ tag-count)))
|
||||
(let ((titles (or (org-roam--extract-titles)
|
||||
(list (org-roam--path-to-slug file)))))
|
||||
(org-roam-db--insert-titles file titles)
|
||||
(setq title-count (+ title-count (length titles))))
|
||||
(when-let* ((ref (org-roam--extract-ref)))
|
||||
(when (org-roam-db--insert-ref file ref)
|
||||
(setq ref-count (1+ ref-count)))))
|
||||
(file-error
|
||||
(setq org-roam-files (remove file org-roam-files))
|
||||
(org-roam-db--clear-file file)
|
||||
(lwarn '(org-roam) :warning
|
||||
"Skipping unreadable file while building cache: %s" file))))
|
||||
(remhash file current-files)
|
||||
(setq processed-count (+ processed-count 1)))))
|
||||
(dolist (file (hash-table-keys current-files))
|
||||
(modified-count 0)
|
||||
(modified-files nil))
|
||||
(dolist (file org-roam-files)
|
||||
(let ((contents-hash (org-roam-db--file-hash file)))
|
||||
(unless (string= (gethash file current-files)
|
||||
contents-hash)
|
||||
(push (cons file contents-hash) modified-files)))
|
||||
(remhash file current-files))
|
||||
(dolist (file (hash-table-keys current-files))
|
||||
;; These files are no longer around, remove from cache...
|
||||
(org-roam-db--clear-file file)
|
||||
(setq deleted-count (1+ deleted-count))))
|
||||
(org-roam-message "files: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
|
||||
file-count
|
||||
(setq deleted-count (1+ deleted-count)))
|
||||
(pcase-dolist (`(,file . _) modified-files)
|
||||
(org-roam-db--clear-file file))
|
||||
;; Process all the files for IDs first
|
||||
;;
|
||||
;; We do this so that link extraction is cheaper: this eliminates the need
|
||||
;; to read the file to check if the ID really exists
|
||||
(pcase-dolist (`(,file . ,contents-hash) modified-files)
|
||||
(let* ((attr (file-attributes file))
|
||||
(atime (file-attribute-access-time attr))
|
||||
(mtime (file-attribute-modification-time attr)))
|
||||
(condition-case nil
|
||||
(org-roam--with-temp-buffer file
|
||||
(org-roam-db-query
|
||||
[:insert :into files
|
||||
:values $v1]
|
||||
(vector file contents-hash (list :atime atime :mtime mtime)))
|
||||
(when org-roam-enable-headline-linking
|
||||
(setq id-count (+ id-count (org-roam-db--insert-ids)))))
|
||||
(file-error
|
||||
(setq org-roam-files (remove file org-roam-files))
|
||||
(org-roam-db--clear-file file)
|
||||
(lwarn '(org-roam) :warning
|
||||
"Skipping unreadable file while building cache: %s" file)))))
|
||||
(pcase-dolist (`(,file . _) modified-files)
|
||||
(org-roam-message "Processed %s/%s modified files..." modified-count (length modified-files))
|
||||
(condition-case nil
|
||||
(org-roam--with-temp-buffer file
|
||||
(setq modified-count (1+ modified-count))
|
||||
(setq link-count (+ link-count (org-roam-db--insert-links)))
|
||||
(setq tag-count (+ tag-count (org-roam-db--insert-tags)))
|
||||
(setq title-count (+ title-count (org-roam-db--insert-titles)))
|
||||
(setq ref-count (+ ref-count (org-roam-db--insert-refs))))
|
||||
(file-error
|
||||
(setq org-roam-files (remove file org-roam-files))
|
||||
(org-roam-db--clear-file file)
|
||||
(lwarn '(org-roam) :warning
|
||||
"Skipping unreadable file while building cache: %s" file))))
|
||||
(org-roam-message "total: Δ%s, files-modified: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
|
||||
(length org-roam-files)
|
||||
modified-count
|
||||
id-count
|
||||
link-count
|
||||
tag-count
|
||||
@ -549,6 +583,16 @@ If FORCE, force a rebuild of the cache from scratch."
|
||||
ref-count
|
||||
deleted-count)))
|
||||
|
||||
(defun org-roam-db-update ()
|
||||
"Update the database."
|
||||
(pcase org-roam-db-update-method
|
||||
('immediate
|
||||
(org-roam-db-build-cache))
|
||||
('idle-timer
|
||||
(org-roam-db-mark-dirty))
|
||||
(_
|
||||
(user-error "Invalid `org-roam-db-update-method'"))))
|
||||
|
||||
(provide 'org-roam-db)
|
||||
|
||||
;;; org-roam-db.el ends here
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/jethrokuan/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -65,6 +65,11 @@ This face is used on the region target by `org-roam-insertion'
|
||||
during an `org-roam-capture'."
|
||||
:group 'org-roam-faces)
|
||||
|
||||
(defface org-roam-dailies-calendar-note
|
||||
'((t :inherit (org-roam-link) :underline nil))
|
||||
"Face for dates with a daily-note in the calendar"
|
||||
:group 'org-roam-faces)
|
||||
|
||||
;;; _
|
||||
|
||||
(provide 'org-roam-faces)
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -32,7 +32,8 @@
|
||||
;;; Code:
|
||||
(require 'xml) ;xml-escape-string
|
||||
(require 's) ;s-truncate, s-replace
|
||||
(require 'org-roam-macs)
|
||||
(eval-and-compile
|
||||
(require 'org-roam-macs))
|
||||
(require 'org-roam-db)
|
||||
|
||||
;;;; Declarations
|
||||
@ -169,15 +170,15 @@ into a digraph."
|
||||
(let* ((nodes (org-roam-db-query node-query))
|
||||
(edges-query
|
||||
`[:with selected :as [:select [file] :from ,node-query]
|
||||
:select :distinct [to from] :from links
|
||||
:where (and (in to selected) (in from selected))])
|
||||
:select :distinct [dest source] :from links
|
||||
:where (and (in dest selected) (in source selected))])
|
||||
(edges-cites-query
|
||||
`[:with selected :as [:select [file] :from ,node-query]
|
||||
:select :distinct [file from]
|
||||
:from links :inner :join refs :on (and (= links:to refs:ref)
|
||||
:select :distinct [file source]
|
||||
:from links :inner :join refs :on (and (= links:dest refs:ref)
|
||||
(= links:type "cite")
|
||||
(= refs:type "cite"))
|
||||
:where (and (in file selected) (in from selected))])
|
||||
:where (and (in file selected) (in source selected))])
|
||||
(edges (org-roam-db-query edges-query))
|
||||
(edges-cites (org-roam-db-query edges-cites-query)))
|
||||
(insert "digraph \"org-roam\" {\n")
|
||||
|
@ -6,7 +6,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -36,6 +36,10 @@
|
||||
|
||||
(require 'ol)
|
||||
(require 'org-roam-compat)
|
||||
(require 'org-roam-macs)
|
||||
(require 'org-roam-db)
|
||||
|
||||
(require 'org-element)
|
||||
|
||||
(defvar org-roam-completion-ignore-case)
|
||||
(defvar org-roam-directory)
|
||||
@ -155,11 +159,11 @@ If there is no corresponding headline, return nil."
|
||||
(org-id-get-create))))))))
|
||||
|
||||
;;; Path-related functions
|
||||
(defun org-roam-link-get-path (path)
|
||||
(defun org-roam-link-get-path (path &optional type)
|
||||
"Return the PATH of the link to use.
|
||||
Respect `org-link-file-path-type', see the variable documentation for details.
|
||||
If DIR is passed, use DIR as the default directory."
|
||||
(pcase org-roam-link-file-path-type
|
||||
If TYPE is non-nil, create a link of TYPE. Otherwise, respect
|
||||
`org-link-file-path-type'."
|
||||
(pcase (or type org-roam-link-file-path-type)
|
||||
('absolute
|
||||
(abbreviate-file-name (expand-file-name path)))
|
||||
('noabbrev
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -34,9 +34,15 @@
|
||||
;;; Code:
|
||||
;;;; Library Requires
|
||||
(require 'dash)
|
||||
(require 's)
|
||||
|
||||
(defvar org-roam-verbose)
|
||||
|
||||
;; This is necessary to ensure all dependents on this module see
|
||||
;; `org-mode-hook' and `org-inhibit-startup' as dynamic variables,
|
||||
;; regardless of whether Org is loaded before their compilation.
|
||||
(require 'org)
|
||||
|
||||
;;;; Utility Functions
|
||||
(defun org-roam--list-interleave (lst separator)
|
||||
"Interleaves elements in LST with SEPARATOR."
|
||||
|
@ -4,7 +4,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (org "9.3"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -39,6 +39,11 @@
|
||||
(require 'org-roam)
|
||||
(require 'ol) ;; for org-link-decode
|
||||
|
||||
(defcustom org-roam-protocol-store-links nil
|
||||
"Whether to store links when capturing websites with `org-roam-protocol'."
|
||||
:type 'boolean
|
||||
:group 'org-roam)
|
||||
|
||||
;;;; Functions
|
||||
(defun org-roam-protocol-open-ref (info)
|
||||
"Process an org-protocol://roam-ref?ref= style url with INFO.
|
||||
@ -46,7 +51,7 @@
|
||||
It opens or creates a note with the given ref.
|
||||
|
||||
javascript:location.href = \\='org-protocol://roam-ref?template=r&ref=\\='+ \\
|
||||
encodeURIComponent(location.href) + \\='&title=\\=' \\
|
||||
encodeURIComponent(location.href) + \\='&title=\\=' + \\
|
||||
encodeURIComponent(document.title) + \\='&body=\\=' + \\
|
||||
encodeURIComponent(window.getSelection())"
|
||||
(when-let* ((alist (org-roam--plist-to-alist info))
|
||||
@ -58,6 +63,20 @@ It opens or creates a note with the given ref.
|
||||
(error "No ref key provided"))
|
||||
(when-let ((title (cdr (assoc 'title decoded-alist))))
|
||||
(push (cons 'slug (funcall org-roam-title-to-slug-function title)) decoded-alist))
|
||||
(let-alist decoded-alist
|
||||
(let* ((ref (org-protocol-sanitize-uri .ref))
|
||||
(type (and (string-match "^\\([a-z]+\\):" ref)
|
||||
(match-string 1 ref)))
|
||||
(title (or .title ""))
|
||||
(body (or .body ""))
|
||||
(orglink
|
||||
(org-link-make-string ref (or (org-string-nw-p title) ref))))
|
||||
(when org-roam-protocol-store-links
|
||||
(push (list ref title) org-stored-links))
|
||||
(org-link-store-props :type type
|
||||
:link ref
|
||||
:annotation orglink
|
||||
:initial body)))
|
||||
(let* ((org-roam-capture-templates org-roam-capture-ref-templates)
|
||||
(org-roam-capture--context 'ref)
|
||||
(org-roam-capture--info decoded-alist)
|
||||
|
491
org-roam.el
491
org-roam.el
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 1.2.2
|
||||
;; Version: 1.2.3
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -114,11 +114,6 @@ If nil, `find-file' is used."
|
||||
:type 'function
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-update-db-idle-seconds 2
|
||||
"Number of idle seconds before triggering an Org-roam database update."
|
||||
:type 'integer
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-include-type-in-ref-path-completions nil
|
||||
"When t, include the type in ref-path completions.
|
||||
Note that this only affects interactive calls.
|
||||
@ -147,6 +142,11 @@ Formatter may be a function that takes title as its only argument."
|
||||
(function :tag "Custom function"))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-prefer-id-links t
|
||||
"If non-nil, use ID for linking instead where available."
|
||||
:type 'boolean
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-list-files-commands
|
||||
(if (member system-type '(windows-nt ms-dos cygwin))
|
||||
nil
|
||||
@ -304,12 +304,6 @@ descriptive warnings when certain operations fail (e.g. parsing).")
|
||||
"]"))
|
||||
"Matches a typed link in double brackets.")
|
||||
|
||||
(defvar org-roam--file-update-timer nil
|
||||
"Timer for updating the database on file changes.")
|
||||
|
||||
(defvar org-roam--file-update-queue nil
|
||||
"List of files that need to be processed for a database update. Processed within `org-roam--file-update-timer'.")
|
||||
|
||||
;;;; Utilities
|
||||
(defun org-roam--plist-to-alist (plist)
|
||||
"Return an alist of the property-value pairs in PLIST."
|
||||
@ -372,13 +366,14 @@ Like `file-name-extension', but does not strip version number."
|
||||
If FILE is not specified, use the current buffer's file-path."
|
||||
(when-let ((path (or file
|
||||
org-roam-file-name
|
||||
(buffer-file-name))))
|
||||
(save-match-data
|
||||
(and
|
||||
(org-roam--org-file-p path)
|
||||
(not (and org-roam-file-exclude-regexp
|
||||
(string-match-p org-roam-file-exclude-regexp path)))
|
||||
(f-descendant-of-p path (expand-file-name org-roam-directory))))))
|
||||
(-> (buffer-base-buffer)
|
||||
(buffer-file-name)))))
|
||||
(save-match-data
|
||||
(and
|
||||
(org-roam--org-file-p path)
|
||||
(not (and org-roam-file-exclude-regexp
|
||||
(string-match-p org-roam-file-exclude-regexp path)))
|
||||
(f-descendant-of-p path (expand-file-name org-roam-directory))))))
|
||||
|
||||
(defun org-roam--shell-command-files (cmd)
|
||||
"Run CMD in the shell and return a list of files. If no files are found, an empty list is returned."
|
||||
@ -510,22 +505,30 @@ Use external shell commands if defined in `org-roam-list-files-commands'."
|
||||
|
||||
;;;; Org extraction functions
|
||||
(defun org-roam--extract-global-props (props)
|
||||
"Extract PROPS from the current org buffer.
|
||||
The search terminates when the first property is encountered."
|
||||
(if (functionp 'org-collect-keywords)
|
||||
(->> (org-collect-keywords props)
|
||||
;; convert (("TITLE" "my title")) to (("TITLE" . "my title"))
|
||||
(mapcar (pcase-lambda (`(,k ,v)) (cons k v))))
|
||||
(let ((buf (org-element-parse-buffer))
|
||||
res)
|
||||
(dolist (prop props)
|
||||
(let ((p (org-element-map buf 'keyword
|
||||
(lambda (kw)
|
||||
(when (string-equal (org-element-property :key kw) prop)
|
||||
(org-element-property :value kw)))
|
||||
:first-match t)))
|
||||
(push (cons prop p) res)))
|
||||
res)))
|
||||
"Extract PROPS from the current org buffer."
|
||||
(let ((collected
|
||||
;; Collect the raw props first
|
||||
;; It'll be returned in the form of
|
||||
;; (("PROP" "value" ...) ("PROP2" "value" ...))
|
||||
(if (functionp 'org-collect-keywords)
|
||||
(org-collect-keywords props)
|
||||
(let ((buf (org-element-parse-buffer))
|
||||
res)
|
||||
(dolist (prop props)
|
||||
(let ((p (org-element-map buf 'keyword
|
||||
(lambda (kw)
|
||||
(when (string-equal (org-element-property :key kw) prop)
|
||||
(org-element-property :value kw)))
|
||||
:first-match nil)))
|
||||
(push (cons prop p) res)))
|
||||
res))))
|
||||
;; convert (("TITLE" "a" "b") ("Another" "c"))
|
||||
;; to (("TITLE" . "a") ("TITLE" . "b") ("Another" . "c"))
|
||||
(let (ret)
|
||||
(pcase-dolist (`(,key . ,values) collected)
|
||||
(dolist (value values)
|
||||
(push (cons key value) ret)))
|
||||
ret)))
|
||||
|
||||
(defun org-roam--get-outline-path ()
|
||||
"Return the outline path to the current entry.
|
||||
@ -568,7 +571,7 @@ Assume buffer is widened and point is on a headline."
|
||||
"Extracts all link items within the current buffer.
|
||||
Link items are of the form:
|
||||
|
||||
[from to type properties]
|
||||
[source dest type properties]
|
||||
|
||||
This is the format that emacsql expects when inserting into the database.
|
||||
FILE-FROM is typically the buffer file path, but this may not exist, for example
|
||||
@ -586,20 +589,20 @@ it as FILE-PATH."
|
||||
(let* ((type (org-roam--collate-types (org-element-property :type link)))
|
||||
(path (org-element-property :path link))
|
||||
(element (org-element-at-point))
|
||||
(begin (or (org-element-property :content-begin element)
|
||||
(begin (or (org-element-property :contents-begin element)
|
||||
(org-element-property :begin element)))
|
||||
(end (or (org-element-property :contents-end element)
|
||||
(org-element-property :end element)))
|
||||
(content (or (org-element-property :raw-value element)
|
||||
(buffer-substring-no-properties
|
||||
begin
|
||||
(or (org-element-property :content-end element)
|
||||
(org-element-property :end element)))))
|
||||
(content (string-trim content))
|
||||
(when (and begin end)
|
||||
(string-trim (buffer-substring-no-properties begin end)))))
|
||||
(properties (list :outline (org-roam--get-outline-path)
|
||||
:content content
|
||||
:point begin))
|
||||
(names (pcase type
|
||||
("id"
|
||||
(list (car (org-roam-id-find path))))
|
||||
(when-let ((file-path (org-roam-id-get-file path)))
|
||||
(list file-path)))
|
||||
("cite" (list path))
|
||||
("website" (list path))
|
||||
("fuzzy" (list path))
|
||||
@ -623,8 +626,9 @@ If FILE-PATH is nil, use the current file."
|
||||
(let (result)
|
||||
;; We need to handle the special case of the file property drawer (at outline level 0)
|
||||
(org-with-point-at (point-min)
|
||||
(when-let ((id (org-entry-get nil "ID")))
|
||||
(push (vector id file-path (org-outline-level)) result)))
|
||||
(when-let ((before-first-heading (= 0 (org-outline-level)))
|
||||
(id (org-entry-get nil "ID")))
|
||||
(push (vector id file-path 0) result)))
|
||||
(org-map-region
|
||||
(lambda ()
|
||||
(when-let ((id (org-entry-get nil "ID")))
|
||||
@ -671,18 +675,19 @@ Reads from the \"roam_alias\" property."
|
||||
(defun org-roam--extract-titles (&optional sources nested)
|
||||
"Extract the titles from current buffer using SOURCES.
|
||||
If NESTED, return the first successful result from SOURCES."
|
||||
(let (coll res)
|
||||
(cl-dolist (source (or sources
|
||||
org-roam-title-sources))
|
||||
(setq res (if (symbolp source)
|
||||
(funcall (intern (concat "org-roam--extract-titles-" (symbol-name source))))
|
||||
(org-roam--extract-titles source t)))
|
||||
(when res
|
||||
(if (not nested)
|
||||
(setq coll (nconc coll res))
|
||||
(setq coll res)
|
||||
(cl-return))))
|
||||
coll))
|
||||
(org-with-wide-buffer
|
||||
(let (coll res)
|
||||
(cl-dolist (source (or sources
|
||||
org-roam-title-sources))
|
||||
(setq res (if (symbolp source)
|
||||
(funcall (intern (concat "org-roam--extract-titles-" (symbol-name source))))
|
||||
(org-roam--extract-titles source t)))
|
||||
(when res
|
||||
(if (not nested)
|
||||
(setq coll (nconc coll res))
|
||||
(setq coll res)
|
||||
(cl-return))))
|
||||
coll)))
|
||||
|
||||
(defun org-roam--extract-tags-all-directories (file)
|
||||
"Extract tags from using the directory path FILE.
|
||||
@ -759,20 +764,30 @@ backlinks."
|
||||
"website")
|
||||
(t type)))
|
||||
|
||||
(defun org-roam--extract-refs ()
|
||||
"Extract all refs (ROAM_KEY statements) from the current buffer.
|
||||
|
||||
Each ref is returned as a cons of its type and its key."
|
||||
(let (refs)
|
||||
(pcase-dolist
|
||||
(`(,_ . ,roam-key)
|
||||
(org-roam--extract-global-props '("ROAM_KEY")))
|
||||
(let (type path)
|
||||
(pcase roam-key
|
||||
('nil nil)
|
||||
((pred string-empty-p)
|
||||
(user-error "Org property #+roam_key cannot be empty"))
|
||||
(ref
|
||||
(when (string-match org-link-plain-re ref)
|
||||
(setq type (org-roam--collate-types (match-string 1 ref))
|
||||
path (match-string 2 ref)))))
|
||||
(when (and type path)
|
||||
(push (cons type path) refs))))
|
||||
refs))
|
||||
|
||||
(defun org-roam--extract-ref ()
|
||||
"Extract the ref from current buffer and return the type and the key of the ref."
|
||||
(let (type path)
|
||||
(pcase (cdr (assoc "ROAM_KEY"
|
||||
(org-roam--extract-global-props '("ROAM_KEY"))))
|
||||
('nil nil)
|
||||
((pred string-empty-p)
|
||||
(user-error "Org property #+roam_key cannot be empty"))
|
||||
(ref
|
||||
(when (string-match org-link-plain-re ref)
|
||||
(setq type (org-roam--collate-types (match-string 1 ref))
|
||||
path (match-string 2 ref)))))
|
||||
(when (and type path)
|
||||
(cons type path))))
|
||||
(car (org-roam--extract-refs)))
|
||||
|
||||
;;;; Title/Path/Slug conversion
|
||||
(defun org-roam--path-to-slug (path)
|
||||
@ -802,19 +817,14 @@ backlinks."
|
||||
(slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
|
||||
(downcase slug))))
|
||||
|
||||
(defun org-roam--format-link-title (title &optional type)
|
||||
"Return the link title, given the file TITLE.
|
||||
If `org-roam-link-title-format title' is defined, use it with TYPE."
|
||||
(if (functionp org-roam-link-title-format)
|
||||
(funcall org-roam-link-title-format title type)
|
||||
(format org-roam-link-title-format title)))
|
||||
|
||||
(defun org-roam-format-link (target &optional description type)
|
||||
(defun org-roam-format-link (target &optional description type link-type)
|
||||
"Formats an org link for a given file TARGET, link DESCRIPTION and link TYPE.
|
||||
TYPE defaults to \"file\".
|
||||
Here, we also check if there is an ID for the file."
|
||||
TYPE defaults to \"file\". LINK-TYPE is the type of file link to
|
||||
be generated. Here, we also check if there is an ID for the
|
||||
file."
|
||||
(setq type (or type "file"))
|
||||
(when-let ((id (and (string-equal type "file")
|
||||
(when-let ((id (and org-roam-prefer-id-links
|
||||
(string-equal type "file")
|
||||
(caar (org-roam-db-query [:select [id] :from ids
|
||||
:where (= file $s1)
|
||||
:and (= level 0)
|
||||
@ -822,7 +832,11 @@ Here, we also check if there is an ID for the file."
|
||||
target)))))
|
||||
(setq type "id" target id))
|
||||
(when (string-equal type "file")
|
||||
(setq target (org-roam-link-get-path target)))
|
||||
(setq target (org-roam-link-get-path target link-type)))
|
||||
(setq description
|
||||
(if (functionp org-roam-link-title-format)
|
||||
(funcall org-roam-link-title-format description type)
|
||||
(format org-roam-link-title-format description)))
|
||||
(org-link-make-string (concat type ":" target) description))
|
||||
|
||||
(defun org-roam--prepend-tag-string (str tags)
|
||||
@ -870,6 +884,26 @@ whose title is 'Index'."
|
||||
(concat (expand-file-name org-roam-directory) path)
|
||||
index)))
|
||||
|
||||
;;;; dealing with file-wide properties
|
||||
(defun org-roam--set-global-prop (name value)
|
||||
"Set a file property called NAME to VALUE.
|
||||
|
||||
If the property is already set, it's value is replaced."
|
||||
(save-excursion
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(if (re-search-forward (concat "^#\\+" name ": \\(.*\\)") (point-max) t)
|
||||
(replace-match (concat "#+" name ": " value) 'fixedcase)
|
||||
(while (and (not (eobp))
|
||||
(looking-at "^[#:]"))
|
||||
(if (save-excursion (end-of-line) (eobp))
|
||||
(progn
|
||||
(end-of-line)
|
||||
(insert "\n"))
|
||||
(forward-line)
|
||||
(beginning-of-line)))
|
||||
(insert "#+" name ": " value "\n"))))
|
||||
|
||||
;;;; org-roam-find-ref
|
||||
(defun org-roam--get-ref-path-completions (&optional arg filter)
|
||||
"Return an alist of refs to absolute path of Org-roam files.
|
||||
@ -973,24 +1007,18 @@ Return nil if the file does not exist."
|
||||
(and (boundp org-roam-backlinks-mode)
|
||||
org-roam-backlinks-mode))
|
||||
|
||||
(defun org-roam--retrieve-link-destination (&optional pom)
|
||||
"Retrieve the destination of the link at POM.
|
||||
The point-or-marker POM can either be a position in the current
|
||||
buffer or a marker."
|
||||
(let ((pom (or pom (point))))
|
||||
(org-with-point-at pom
|
||||
(let* ((context (org-element-context))
|
||||
(type (org-element-property :type context))
|
||||
(dest (org-element-property :path context)))
|
||||
(pcase type
|
||||
("id" (car (org-roam-id-find dest)))
|
||||
(_ dest))))))
|
||||
|
||||
(defun org-roam--backlink-to-current-p ()
|
||||
"Return t if backlink is to the current Org-roam file."
|
||||
(let ((current (buffer-file-name org-roam-buffer--current))
|
||||
(backlink-dest (org-roam--retrieve-link-destination)))
|
||||
(string= current backlink-dest)))
|
||||
"Return t if the link at point is to the current Org-roam file."
|
||||
(save-match-data
|
||||
(let ((current-file (buffer-file-name org-roam-buffer--current))
|
||||
(backlink-dest (save-excursion
|
||||
(let* ((context (org-element-context))
|
||||
(type (org-element-property :type context))
|
||||
(dest (org-element-property :path context)))
|
||||
(pcase type
|
||||
("id" (org-roam-id-get-file dest))
|
||||
(_ dest))))))
|
||||
(string= current-file backlink-dest))))
|
||||
|
||||
(defun org-roam-open-at-point ()
|
||||
"Open an Org-roam link or visit the text previewed at point.
|
||||
@ -1025,20 +1053,27 @@ citation key, for Org-ref cite links."
|
||||
(unless (listp targets)
|
||||
(setq targets (list targets)))
|
||||
(let ((conditions (--> targets
|
||||
(mapcar (lambda (i) (list '= 'to i)) it)
|
||||
(mapcar (lambda (i) (list '= 'dest i)) it)
|
||||
(org-roam--list-interleave it :or))))
|
||||
(org-roam-db-query `[:select [from to properties] :from links
|
||||
(org-roam-db-query `[:select [source dest properties] :from links
|
||||
:where ,@conditions
|
||||
:order-by (asc from)])))
|
||||
:order-by (asc source)])))
|
||||
|
||||
(defun org-roam-id-get-file (id)
|
||||
"Return the file if ID exists in the Org-roam database.
|
||||
(defun org-roam-id-get-file (id &optional strict)
|
||||
"Return the file if ID exists.
|
||||
When STRICT is non-nil, only consider Org-roam's database.
|
||||
Return nil otherwise."
|
||||
(caar (org-roam-db-query [:select [file]
|
||||
:from ids
|
||||
:where (= id $s1)
|
||||
:limit 1]
|
||||
id)))
|
||||
(or (caar (org-roam-db-query [:select [file]
|
||||
:from ids
|
||||
:where (= id $s1)
|
||||
:limit 1]
|
||||
id))
|
||||
(and (not strict)
|
||||
(progn
|
||||
(unless org-id-locations (org-id-locations-load))
|
||||
(or (and org-id-locations
|
||||
(hash-table-p org-id-locations)
|
||||
(gethash id org-id-locations)))))))
|
||||
|
||||
(defun org-roam-id-find (id &optional markerp strict keep-buffer-p)
|
||||
"Return the location of the entry with the id ID.
|
||||
@ -1046,8 +1081,7 @@ When MARKERP is non-nil, return a marker pointing to the headline.
|
||||
Otherwise, return a cons formatted as \(file . pos).
|
||||
When STRICT is non-nil, only consider Org-roam’s database.
|
||||
When KEEP-BUFFER-P is non-nil, keep the buffers navigated by Org-roam open."
|
||||
(let ((file (or (org-roam-id-get-file id)
|
||||
(unless strict (org-id-find-id-file id)))))
|
||||
(let ((file (org-roam-id-get-file id strict)))
|
||||
(when file
|
||||
(let ((existing-buf (find-buffer-visiting file))
|
||||
(res (org-id-find-id-in-file id file markerp)))
|
||||
@ -1068,6 +1102,7 @@ When STRICT is non-nil, only consider Org-roam’s database."
|
||||
(when-let ((marker (if (markerp id-or-marker)
|
||||
id-or-marker
|
||||
(org-roam-id-find id-or-marker t strict t))))
|
||||
(org-mark-ring-push)
|
||||
(org-goto-marker-or-bmk marker)
|
||||
(set-marker marker nil)))
|
||||
|
||||
@ -1170,7 +1205,7 @@ This is active when `org-roam-completion-everywhere' is non-nil."
|
||||
;;;; Function Faces
|
||||
;; These faces are used by `org-link-set-parameters', which take one argument,
|
||||
;; which is the path.
|
||||
(defcustom org-roam-link-use-custom-faces 'everywhere
|
||||
(defcustom org-roam-link-use-custom-faces t
|
||||
"Define where to apply custom faces to Org-roam links.
|
||||
|
||||
Valide values are:
|
||||
@ -1182,9 +1217,9 @@ everywhere Apply custom faces everywhere.
|
||||
|
||||
Otherwise, do not apply custom faces to Org-roam links."
|
||||
:type '(choice
|
||||
(const :tag "Use custom faces inside Org-roam notes" t)
|
||||
(const :tag "Apply custom faces everywhere" everywhere)
|
||||
(const :tag "Do not apply custom faces" nil))
|
||||
(const :tag "Use custom faces inside Org-roam notes" t)
|
||||
(const :tag "Apply custom faces everywhere" everywhere)
|
||||
(const :tag "Do not apply custom faces" nil))
|
||||
:group 'org-roam)
|
||||
|
||||
(defun org-roam--file-link-face (path)
|
||||
@ -1222,35 +1257,18 @@ file."
|
||||
(org-roam--org-roam-file-p)))
|
||||
(custom (or (and in-note org-roam-link-use-custom-faces)
|
||||
(eq org-roam-link-use-custom-faces 'everywhere))))
|
||||
(cond ((and custom
|
||||
(not (org-roam-id-get-file id)))
|
||||
'org-roam-link-invalid)
|
||||
((and (org-roam--in-buffer-p)
|
||||
(cond ((and (org-roam--in-buffer-p)
|
||||
(org-roam--backlink-to-current-p))
|
||||
'org-roam-link-current)
|
||||
((and custom
|
||||
(org-roam-id-get-file id))
|
||||
(org-roam-id-get-file id t))
|
||||
'org-roam-link)
|
||||
((and custom
|
||||
(not (org-roam-id-get-file id)))
|
||||
'org-roam-link-invalid)
|
||||
(t
|
||||
'org-link)))))
|
||||
|
||||
(defun org-roam--queue-file-for-update (&optional file-path)
|
||||
"Queue FILE-PATH for `org-roam' database update.
|
||||
This is a lightweight function that is called during `after-save-hook'
|
||||
and only schedules the current Org file to be `org-roam' updated
|
||||
during the next idle slot."
|
||||
(let ((fp (or file-path buffer-file-name)))
|
||||
(when (org-roam--org-roam-file-p file-path)
|
||||
(add-to-list 'org-roam--file-update-queue fp))))
|
||||
|
||||
(defun org-roam--process-update-queue ()
|
||||
"Process files queued in `org-roam--file-update-queue'."
|
||||
(when org-roam--file-update-queue
|
||||
(mapc #'org-roam-db--update-file org-roam--file-update-queue)
|
||||
(org-roam-message "database updated during idle: %s."
|
||||
(mapconcat #'file-name-nondirectory org-roam--file-update-queue ", ") )
|
||||
(setq org-roam--file-update-queue nil)))
|
||||
|
||||
;;;; Hooks and Advices
|
||||
(defcustom org-roam-file-setup-hook nil
|
||||
"Hook that is run on setting up an Org-roam file."
|
||||
@ -1265,7 +1283,7 @@ during the next idle slot."
|
||||
(org-roam--setup-title-auto-update)
|
||||
(add-hook 'post-command-hook #'org-roam-buffer--update-maybe nil t)
|
||||
(add-hook 'before-save-hook #'org-roam-link--replace-link-on-save nil t)
|
||||
(add-hook 'after-save-hook #'org-roam--queue-file-for-update nil t)
|
||||
(add-hook 'after-save-hook #'org-roam-db-update nil t)
|
||||
(dolist (fn org-roam-completion-functions)
|
||||
(add-hook 'completion-at-point-functions fn nil t))
|
||||
(org-roam-buffer--update-maybe :redisplay t)))
|
||||
@ -1276,44 +1294,51 @@ during the next idle slot."
|
||||
(org-roam--org-roam-file-p file))
|
||||
(org-roam-db--clear-file (expand-file-name file))))
|
||||
|
||||
(defun org-roam--get-link-replacement (old-path new-path &optional old-desc new-desc)
|
||||
"Create replacement text for link at point if OLD-PATH is a match.
|
||||
Will update link to NEW-PATH. If OLD-DESC is set, and is not the
|
||||
same as the link description, it is assumed that the user has
|
||||
modified the description, and the description will not be
|
||||
updated. Else, update with NEW-DESC."
|
||||
(let (type path link-type label new-label)
|
||||
(when-let ((link (org-element-lineage (org-element-context) '(link) t)))
|
||||
(setq type (org-element-property :type link)
|
||||
path (org-element-property :path link))
|
||||
(when (and (string-equal (expand-file-name path) old-path)
|
||||
(org-in-regexp org-link-bracket-re 1))
|
||||
(setq link-type (when (file-name-absolute-p path) 'absolute)
|
||||
label (if (match-end 2)
|
||||
(match-string-no-properties 2)
|
||||
(org-link-unescape (match-string-no-properties 1))))
|
||||
(setq new-label (if (string-equal label old-desc) new-desc label))
|
||||
(org-roam-format-link new-path new-label type link-type)))))
|
||||
|
||||
(defun org-roam--replace-link (old-path new-path &optional old-desc new-desc)
|
||||
"Replace Org-roam file links with path OLD-PATH to path NEW-PATH.
|
||||
If OLD-DESC is passed, and is not the same as the link
|
||||
description, it is assumed that the user has modified the
|
||||
description, and the description will not be updated. Else,
|
||||
update with NEW-DESC."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-link-any-re nil t)
|
||||
(when-let ((link (org-element-lineage (org-element-context) '(link) t)))
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link)))
|
||||
(when (and (string-equal (expand-file-name path) old-path)
|
||||
(org-in-regexp org-link-bracket-re 1))
|
||||
(let* ((label (if (match-end 2)
|
||||
(match-string-no-properties 2)
|
||||
(org-link-unescape (match-string-no-properties 1))))
|
||||
(new-label (if (string-equal label old-desc)
|
||||
new-desc
|
||||
label)))
|
||||
(replace-match (org-roam-format-link new-path new-label type)))))))))
|
||||
(org-with-point-at 1
|
||||
(while (re-search-forward org-link-bracket-re nil t)
|
||||
(when-let ((link (save-match-data (org-roam--get-link-replacement old-path new-path old-desc new-desc))))
|
||||
(replace-match link)))))
|
||||
|
||||
(defun org-roam--fix-relative-links (old-path)
|
||||
"Fix file-relative links in current buffer.
|
||||
File relative links are assumed to originate from OLD-PATH. The
|
||||
replaced links are made relative to the current buffer."
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-link-any-re nil t)
|
||||
(when-let ((link (org-element-lineage (org-element-context) '(link) t)))
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link)))
|
||||
(when (and (f-relative-p path)
|
||||
(org-in-regexp org-link-bracket-re 1))
|
||||
(let* ((file-path (expand-file-name path (file-name-directory old-path)))
|
||||
(new-path (org-roam-link-get-path file-path)))
|
||||
(replace-match (concat type ":" new-path)
|
||||
nil t nil 1))))))))
|
||||
(org-with-point-at 1
|
||||
(let (link new-link type path)
|
||||
(while (re-search-forward org-link-bracket-re nil t)
|
||||
(when (setq link (save-match-data (org-element-lineage (org-element-context) '(link) t)))
|
||||
(setq type (org-element-property :type link))
|
||||
(setq path (org-element-property :path link))
|
||||
(when (and (string= type "file")
|
||||
(f-relative-p path))
|
||||
(setq new-link
|
||||
(concat type ":" (org-roam-link-get-path (expand-file-name path (file-name-directory old-path)))))
|
||||
(replace-match new-link nil t nil 1)))))))
|
||||
|
||||
(defcustom org-roam-rename-file-on-title-change t
|
||||
"If non-nil, alter the filename on title change.
|
||||
@ -1356,9 +1381,9 @@ if applicable.
|
||||
|
||||
To be added to `org-roam-title-change-hook'."
|
||||
(let* ((current-path (buffer-file-name))
|
||||
(files-affected (org-roam-db-query [:select :distinct [from]
|
||||
(files-affected (org-roam-db-query [:select :distinct [source]
|
||||
:from links
|
||||
:where (= to $s1)]
|
||||
:where (= dest $s1)]
|
||||
current-path)))
|
||||
(dolist (file files-affected)
|
||||
(with-current-buffer (or (find-buffer-visiting (car file))
|
||||
@ -1381,10 +1406,11 @@ To be added to `org-roam-title-change-hook'."
|
||||
(when (string-match-p old-slug file-name)
|
||||
(let* ((new-slug (funcall org-roam-title-to-slug-function new-title))
|
||||
(new-file-name (replace-regexp-in-string old-slug new-slug file-name)))
|
||||
(rename-file file-name new-file-name)
|
||||
(set-visited-file-name new-file-name t t)
|
||||
(add-to-list 'org-roam--file-update-queue new-file-name)
|
||||
(org-roam-message "File moved to %S" (abbreviate-file-name new-file-name)))))))
|
||||
(unless (string-match-p file-name new-file-name)
|
||||
(rename-file file-name new-file-name)
|
||||
(set-visited-file-name new-file-name t t)
|
||||
(org-roam-db-update)
|
||||
(org-roam-message "File moved to %S" (abbreviate-file-name new-file-name))))))))
|
||||
|
||||
(defun org-roam--rename-file-advice (old-file new-file-or-dir &rest _args)
|
||||
"Rename backlinks of OLD-FILE to refer to NEW-FILE-OR-DIR.
|
||||
@ -1398,42 +1424,40 @@ When NEW-FILE-OR-DIR is a directory, we use it to compute the new file path."
|
||||
(not (backup-file-name-p new-file))
|
||||
(org-roam--org-roam-file-p old-file))
|
||||
(org-roam-db--ensure-built)
|
||||
(let* ((old-path (expand-file-name old-file))
|
||||
(new-path (expand-file-name new-file))
|
||||
(new-buffer (or (find-buffer-visiting new-path)
|
||||
(find-file-noselect new-path)))
|
||||
(files-affected (org-roam-db-query [:select :distinct [from]
|
||||
(let* ((new-buffer (or (find-buffer-visiting new-file)
|
||||
(find-file-noselect new-file)))
|
||||
(files-affected (org-roam-db-query [:select :distinct [source]
|
||||
:from links
|
||||
:where (= to $s1)]
|
||||
old-path)))
|
||||
:where (= dest $s1)]
|
||||
old-file)))
|
||||
;; Remove database entries for old-file.org
|
||||
(org-roam-db--clear-file old-file)
|
||||
;; Replace links from old-file.org -> new-file.org in all Org-roam files with these links
|
||||
(mapc (lambda (file)
|
||||
(setq file (if (string-equal (expand-file-name (car file)) old-path)
|
||||
new-path
|
||||
(setq file (if (string-equal (car file) old-file)
|
||||
new-file
|
||||
(car file)))
|
||||
(with-current-buffer (or (find-buffer-visiting file)
|
||||
(find-file-noselect file))
|
||||
(org-roam--replace-link old-path new-path)
|
||||
(org-roam--replace-link old-file new-file)
|
||||
(save-buffer)
|
||||
(org-roam-db--update-file)))
|
||||
files-affected)
|
||||
;; If the new path is in a different directory, relative links
|
||||
;; will break. Fix all file-relative links:
|
||||
(unless (string= (file-name-directory old-path)
|
||||
(file-name-directory new-path))
|
||||
(unless (string= (file-name-directory old-file)
|
||||
(file-name-directory new-file))
|
||||
(with-current-buffer new-buffer
|
||||
(org-roam--fix-relative-links old-path)
|
||||
(org-roam--fix-relative-links old-file)
|
||||
(save-buffer)))
|
||||
(when (org-roam--org-roam-file-p new-file)
|
||||
(org-roam-db--update-file new-path))))))
|
||||
(org-roam-db--update-file new-file))))))
|
||||
|
||||
(defun org-roam--id-new-advice (&rest _args)
|
||||
"Update the database if a new Org ID is created."
|
||||
(when (and org-roam-enable-headline-linking
|
||||
(org-roam--org-roam-file-p))
|
||||
(add-to-list 'org-roam--file-update-queue (buffer-file-name))))
|
||||
(org-roam-db-update)))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode org-roam-mode
|
||||
@ -1468,8 +1492,9 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
|
||||
(add-hook 'find-file-hook #'org-roam--find-file-hook-function)
|
||||
(add-hook 'kill-emacs-hook #'org-roam-db--close-all)
|
||||
(add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point)
|
||||
(unless org-roam--file-update-timer
|
||||
(setq org-roam--file-update-timer (run-with-idle-timer org-roam-update-db-idle-seconds t #'org-roam--process-update-queue)))
|
||||
(if (and org-roam-db-file-update-timer
|
||||
(eq org-roam-db-update-method 'idle-timer))
|
||||
(setq org-roam-db-file-update-timer (run-with-idle-timer org-roam-db-update-idle-seconds t #'org-roam-db-update-cache-on-timer)))
|
||||
(advice-add 'rename-file :after #'org-roam--rename-file-advice)
|
||||
(advice-add 'delete-file :before #'org-roam--delete-file-advice)
|
||||
(advice-add 'org-id-new :after #'org-roam--id-new-advice)
|
||||
@ -1482,8 +1507,8 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
|
||||
(remove-hook 'find-file-hook #'org-roam--find-file-hook-function)
|
||||
(remove-hook 'kill-emacs-hook #'org-roam-db--close-all)
|
||||
(remove-hook 'org-open-at-point-functions #'org-roam-open-id-at-point)
|
||||
(when org-roam--file-update-timer
|
||||
(cancel-timer org-roam--file-update-timer))
|
||||
(when org-roam-db-file-update-timer
|
||||
(cancel-timer org-roam-db-file-update-timer))
|
||||
(advice-remove 'rename-file #'org-roam--rename-file-advice)
|
||||
(advice-remove 'delete-file #'org-roam--delete-file-advice)
|
||||
(advice-remove 'org-id-new #'org-roam--id-new-advice)
|
||||
@ -1496,7 +1521,7 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
|
||||
(with-current-buffer buf
|
||||
(remove-hook 'post-command-hook #'org-roam-buffer--update-maybe t)
|
||||
(remove-hook 'before-save-hook #'org-roam-link--replace-link-on-save t)
|
||||
(remove-hook 'after-save-hook #'org-roam--queue-file-for-update t))))))
|
||||
(remove-hook 'after-save-hook #'org-roam-db-update t))))))
|
||||
|
||||
;;; Interactive Commands
|
||||
;;;###autoload
|
||||
@ -1584,8 +1609,8 @@ included as a candidate."
|
||||
(defun org-roam-insert (&optional lowercase completions filter-fn description link-type)
|
||||
"Find an Org-roam file, and insert a relative org link to it at point.
|
||||
Return selected file if it exists.
|
||||
If LOWERCASE is non-nil, downcase the link description.
|
||||
LINK-TYPE is the type of link to be created. It defaults to \"file\".
|
||||
If LOWERCASE, downcase the title before insertion.
|
||||
COMPLETIONS is a list of completions to be used instead of
|
||||
`org-roam--get-title-path-completions`.
|
||||
FILTER-FN is the name of a function to apply on the candidates
|
||||
@ -1616,17 +1641,16 @@ If DESCRIPTION is provided, use this as the link label. See
|
||||
title-with-tags))
|
||||
(target-file-path (plist-get res :path))
|
||||
(description (or description region-text title))
|
||||
(link-description (org-roam--format-link-title (if lowercase
|
||||
(downcase description)
|
||||
description)
|
||||
link-type)))
|
||||
(description (if lowercase
|
||||
(downcase description)
|
||||
description)))
|
||||
(cond ((and target-file-path
|
||||
(file-exists-p target-file-path))
|
||||
(when region-text
|
||||
(delete-region beg end)
|
||||
(set-marker beg nil)
|
||||
(set-marker end nil))
|
||||
(insert (org-roam-format-link target-file-path link-description link-type)))
|
||||
(insert (org-roam-format-link target-file-path description link-type)))
|
||||
(t
|
||||
(let ((org-roam-capture--info `((title . ,title-with-tags)
|
||||
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))
|
||||
@ -1634,7 +1658,7 @@ If DESCRIPTION is provided, use this as the link label. See
|
||||
(setq org-roam-capture-additional-template-props (list :region (org-roam-shield-region beg end)
|
||||
:insert-at (point-marker)
|
||||
:link-type link-type
|
||||
:link-description link-description
|
||||
:link-description description
|
||||
:finalize 'insert-link))
|
||||
(org-roam-capture--capture))))
|
||||
res))
|
||||
@ -1680,6 +1704,68 @@ command will offer you to create one."
|
||||
(when (y-or-n-p "Index file does not exist. Would you like to create it? ")
|
||||
(org-roam-find-file "Index")))))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-alias-add ()
|
||||
"Add an alias to Org-roam file.
|
||||
|
||||
Return added alias."
|
||||
(interactive)
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(let ((alias (read-string "Alias: " )))
|
||||
(when (string-empty-p alias)
|
||||
(user-error "Alias can't be empty"))
|
||||
(org-roam--set-global-prop
|
||||
"ROAM_ALIAS"
|
||||
(combine-and-quote-strings
|
||||
(seq-uniq (cons alias
|
||||
(org-roam--extract-titles-alias)))))
|
||||
(org-roam-db--update-file (buffer-file-name (buffer-base-buffer)))
|
||||
alias))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-alias-delete ()
|
||||
"Delete an alias from Org-roam file."
|
||||
(interactive)
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(if-let ((aliases (org-roam--extract-titles-alias)))
|
||||
(let ((alias (completing-read "Alias: " aliases nil 'require-match)))
|
||||
(org-roam--set-global-prop
|
||||
"ROAM_ALIAS"
|
||||
(combine-and-quote-strings (delete alias aliases)))
|
||||
(org-roam-db--update-file (buffer-file-name (buffer-base-buffer))))
|
||||
(user-error "No aliases to delete")))
|
||||
|
||||
(defun org-roam-tag-add ()
|
||||
"Add a tag to Org-roam file.
|
||||
|
||||
Return added tag."
|
||||
(interactive)
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(let* ((all-tags (org-roam-db--get-tags))
|
||||
(tag (completing-read "Tag: " all-tags))
|
||||
(file (buffer-file-name (buffer-base-buffer)))
|
||||
(existing-tags (org-roam--extract-tags-prop file)))
|
||||
(when (string-empty-p tag)
|
||||
(user-error "Tag can't be empty"))
|
||||
(org-roam--set-global-prop
|
||||
"ROAM_TAGS"
|
||||
(combine-and-quote-strings (seq-uniq (cons tag existing-tags))))
|
||||
(org-roam-db--insert-tags 'update)
|
||||
tag))
|
||||
|
||||
(defun org-roam-tag-delete ()
|
||||
"Delete a tag from Org-roam file."
|
||||
(interactive)
|
||||
(unless org-roam-mode (org-roam-mode))
|
||||
(if-let* ((file (buffer-file-name (buffer-base-buffer)))
|
||||
(tags (org-roam--extract-tags-prop file)))
|
||||
(let ((tag (completing-read "Tag: " tags nil 'require-match)))
|
||||
(org-roam--set-global-prop
|
||||
"ROAM_TAGS"
|
||||
(combine-and-quote-strings (delete tag tags)))
|
||||
(org-roam-db--insert-tags 'update))
|
||||
(user-error "No tag to delete")))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-switch-to-buffer ()
|
||||
"Switch to an existing Org-roam buffer."
|
||||
@ -1719,12 +1805,17 @@ means that the results can be noisy, and may not truly indicate
|
||||
an unlinked reference.
|
||||
|
||||
Users are encouraged to think hard about whether items should be
|
||||
linked, lest the network graph get too crowded."
|
||||
linked, lest the network graph get too crowded.
|
||||
|
||||
Requires a version of Ripgrep with PCRE2 support installed, with
|
||||
the executable 'rg' in variable `exec-path'."
|
||||
(interactive)
|
||||
(unless (org-roam--org-roam-file-p)
|
||||
(user-error "Not in org-roam file"))
|
||||
(if (not (executable-find "rg"))
|
||||
(error "Cannot find the ripgrep executable \"rg\". Check that it is installed and available on `exec-path'")
|
||||
(when (string-match "PCRE2 is not available" (shell-command-to-string "rg --pcre2-version"))
|
||||
(error "\"rg\" must be compiled with PCRE2 support"))
|
||||
(let* ((titles (org-roam--extract-titles))
|
||||
(rg-command (concat "rg -o --vimgrep -P -i "
|
||||
(string-join (mapcar (lambda (glob) (concat "-g " glob))
|
||||
@ -1771,7 +1862,9 @@ linked, lest the network graph get too crowded."
|
||||
(concat "sed -n "
|
||||
row
|
||||
"p "
|
||||
file))))
|
||||
"\""
|
||||
file
|
||||
"\""))))
|
||||
(insert "\n")))))))
|
||||
(read-only-mode +1)
|
||||
(dolist (title titles)
|
||||
|
1
tests/roam-files/cite_ref.org
Normal file
1
tests/roam-files/cite_ref.org
Normal file
@ -0,0 +1 @@
|
||||
#+roam_key: cite:mitsuha2007
|
2
tests/roam-files/multiple-refs.org
Normal file
2
tests/roam-files/multiple-refs.org
Normal file
@ -0,0 +1,2 @@
|
||||
#+roam_key: https://www.orgroam.com/
|
||||
#+roam_key: cite:orgroam2020
|
@ -45,7 +45,7 @@
|
||||
(pcase (benchmark-run 1 (org-roam-db-build-cache t))
|
||||
(`(,time ,gcs ,time-in-gc)
|
||||
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
|
||||
(expect time :to-be-less-than 90))))
|
||||
(expect time :to-be-less-than 110))))
|
||||
(it "builds quickly without change"
|
||||
(pcase (benchmark-run 1 (org-roam-db-build-cache))
|
||||
(`(,time ,gcs ,time-in-gc)
|
||||
|
@ -71,6 +71,40 @@
|
||||
(expect (org-roam--str-to-list "\"hello")
|
||||
:to-throw)))
|
||||
|
||||
(describe "Ref extraction"
|
||||
(before-all
|
||||
(test-org-roam--init))
|
||||
|
||||
(after-all
|
||||
(test-org-roam--teardown))
|
||||
|
||||
(cl-flet
|
||||
((test (fn file)
|
||||
(let* ((fname (test-org-roam--abs-path file))
|
||||
(buf (find-file-noselect fname)))
|
||||
(with-current-buffer buf
|
||||
;; Unlike tag extraction, it doesn't make sense to
|
||||
;; pass a filename.
|
||||
(funcall fn)))))
|
||||
;; Enable "cite:" link parsing
|
||||
(org-link-set-parameters "cite")
|
||||
(it "extracts web keys"
|
||||
(expect (test #'org-roam--extract-ref
|
||||
"web_ref.org")
|
||||
:to-equal
|
||||
'("website" . "//google.com/")))
|
||||
(it "extracts cite keys"
|
||||
(expect (test #'org-roam--extract-ref
|
||||
"cite_ref.org")
|
||||
:to-equal
|
||||
'("cite" . "mitsuha2007")))
|
||||
(it "extracts all keys"
|
||||
(expect (test #'org-roam--extract-refs
|
||||
"multiple-refs.org")
|
||||
:to-have-same-items-as
|
||||
'(("cite" . "orgroam2020")
|
||||
("website" . "//www.orgroam.com/"))))))
|
||||
|
||||
(describe "Title extraction"
|
||||
:var (org-roam-title-sources)
|
||||
(before-all
|
||||
@ -300,21 +334,21 @@
|
||||
|
||||
;; Links
|
||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
||||
:where (= from $s1)]
|
||||
:where (= source $s1)]
|
||||
(test-org-roam--abs-path "foo.org"))) :to-be 1)
|
||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
||||
:where (= from $s1)]
|
||||
:where (= source $s1)]
|
||||
(test-org-roam--abs-path "nested/bar.org"))) :to-be 2)
|
||||
|
||||
;; Links -- File-to
|
||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
||||
:where (= to $s1)]
|
||||
:where (= dest $s1)]
|
||||
(test-org-roam--abs-path "nested/foo.org"))) :to-be 1)
|
||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
||||
:where (= to $s1)]
|
||||
:where (= dest $s1)]
|
||||
(test-org-roam--abs-path "nested/bar.org"))) :to-be 1)
|
||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
||||
:where (= to $s1)]
|
||||
:where (= dest $s1)]
|
||||
(test-org-roam--abs-path "unlinked.org"))) :to-be 0)
|
||||
;; TODO Test titles
|
||||
(expect (org-roam-db-query [:select * :from titles])
|
||||
|
Reference in New Issue
Block a user