mirror of
https://github.com/org-roam/org-roam
synced 2025-08-03 12:27:23 -05:00
Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
5eb1a87123 | |||
914bbe3b53 | |||
01130b49e1 | |||
684ab67952 | |||
60eeb3985a | |||
a6cdc77980 | |||
9cd12a4f11 | |||
e00538f909 | |||
270995b2d4 | |||
1cfd71f5a8 | |||
ede33d7411 | |||
7817116403 | |||
efd2072070 | |||
791c059200 |
69
.github/workflows/test.yml
vendored
Normal file
69
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,69 @@
|
||||
# * test.yml --- Test Emacs packages using makem.sh on GitHub Actions
|
||||
|
||||
# https://github.com/alphapapa/makem.sh
|
||||
|
||||
# Based on Steve Purcell's examples at
|
||||
# <https://github.com/purcell/setup-emacs/blob/master/.github/workflows/test.yml>,
|
||||
# <https://github.com/purcell/package-lint/blob/master/.github/workflows/test.yml>.
|
||||
|
||||
# * License:
|
||||
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
# * Code:
|
||||
|
||||
name: "CI"
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
emacs_version:
|
||||
- 26.3
|
||||
- snapshot
|
||||
steps:
|
||||
- uses: purcell/setup-emacs@master
|
||||
with:
|
||||
version: ${{ matrix.emacs_version }}
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Initialize sandbox
|
||||
run: |
|
||||
SANDBOX_DIR=$(mktemp -d) || exit 1
|
||||
echo ::set-env name=SANDBOX_DIR::$SANDBOX_DIR
|
||||
./makem.sh -vv --sandbox $SANDBOX_DIR --install-deps --install-linters
|
||||
|
||||
# The "all" rule is not used, because it treats compilation warnings
|
||||
# as failures, so linting and testing are run as separate steps.
|
||||
|
||||
- name: Lint
|
||||
continue-on-error: true
|
||||
run: ./makem.sh -vv --sandbox $SANDBOX_DIR lint
|
||||
|
||||
- name: Test
|
||||
if: always() # Run test even if linting fails.
|
||||
run: ./makem.sh -vv --sandbox $SANDBOX_DIR test
|
||||
|
||||
# Local Variables:
|
||||
# eval: (outline-minor-mode)
|
||||
# End:
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/.sandbox/
|
37
CHANGELOG.md
Normal file
37
CHANGELOG.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## 0.1.1 (2020-02-15)
|
||||
|
||||
Mostly a documentation/cleanup release.
|
||||
|
||||
### New Features
|
||||
* [#62][gh-62] Add the options `org-roam-use-timestamps-as-filename` and `org-roam-file-format`, more in documentation.
|
||||
|
||||
### Breaking Changes
|
||||
* [#62][gh-62] The ID (file-name) workflow is no longer first-class, but a fallback when titles don't exist.
|
||||
|
||||
### Changes
|
||||
* [#66][gh-66], [#68][gh-68]: Improved the quality of the package in preparation of submission to MELPA
|
||||
* [#73][gh-73]: Added CI to the project via Github Issues (Thanks [@alphapapa](https://github.com/alphapapa/) for scripts and setup)
|
||||
* [#69][gh-69], [#72][gh-72], [#75][gh-75]: Major cleanup and de-duplication of code
|
||||
|
||||
### Bugfixes
|
||||
* [#67][gh-67]: Fixed `org-roam--make-file` not creating files with extensions
|
||||
* [#71][gh-71], [#78][gh-78]: Fixed `org-roam-insert` not inserting correct paths
|
||||
* [#82][gh-82]: Fixed nested Org-roam files not being detected as part of Org-roam
|
||||
|
||||
[gh-62]: https://github.com/jethrokuan/org-roam/pull/66
|
||||
[gh-66]: https://github.com/jethrokuan/org-roam/pull/66
|
||||
[gh-67]: https://github.com/jethrokuan/org-roam/pull/67
|
||||
[gh-68]: https://github.com/jethrokuan/org-roam/pull/68
|
||||
[gh-69]: https://github.com/jethrokuan/org-roam/pull/69
|
||||
[gh-71]: https://github.com/jethrokuan/org-roam/pull/71
|
||||
[gh-72]: https://github.com/jethrokuan/org-roam/pull/72
|
||||
[gh-73]: https://github.com/jethrokuan/org-roam/pull/73
|
||||
[gh-75]: https://github.com/jethrokuan/org-roam/pull/75
|
||||
[gh-78]: https://github.com/jethrokuan/org-roam/pull/78
|
||||
[gh-82]: https://github.com/jethrokuan/org-roam/pull/82
|
||||
|
||||
# Local Variables:
|
||||
# eval: (auto-fill-mode -1)
|
||||
# End:
|
34
CONTRIBUTING.md
Normal file
34
CONTRIBUTING.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Contributing
|
||||
|
||||
If you discover issues, have ideas for improvements or new features, please
|
||||
report them to the [issue tracker][1] of the repository or submit a pull
|
||||
request. Please, try to follow these guidelines when you do so.
|
||||
|
||||
## Issue reporting
|
||||
|
||||
* Check that the issue has not already been reported.
|
||||
* Check that the issue has not already been fixed in the latest code
|
||||
(a.k.a. `develop`).
|
||||
* Be clear, concise and precise in your description of the problem.
|
||||
* Open an issue with a descriptive title and a summary in grammatically correct,
|
||||
complete sentences.
|
||||
* Include any relevant code to the issue summary.
|
||||
* If you're reporting performance issues it'd be nice if you added some profiling data (Emacs has a built-in profiler).
|
||||
|
||||
## Pull requests
|
||||
|
||||
* Read [how to properly contribute to open source projects on Github][2].
|
||||
* Use a topic branch to easily amend a pull request later, if necessary.
|
||||
* Write [good commit messages][3].
|
||||
* Mention related tickets in the commit messages (e.g. `[Fix #N] Add missing autoload cookies`)
|
||||
* Update the [changelog][5].
|
||||
* Use the same coding conventions as the rest of the project.
|
||||
* Verify your Emacs Lisp code with `checkdoc` (<kbd>C-c ? d</kbd>).
|
||||
* Open a [pull request][4] that relates to *only* one subject with a clear title
|
||||
and description in grammatically correct, complete sentences.
|
||||
|
||||
[1]: https://github.com/jethrokuan/org-roam/issues
|
||||
[2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request
|
||||
[3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html
|
||||
[4]: https://help.github.com/articles/using-pull-requests
|
||||
[5]: https://github.com/jethrokuan/org-roam/blob/master/CHANGELOG.md
|
56
Makefile
Normal file
56
Makefile
Normal file
@ -0,0 +1,56 @@
|
||||
# * makem.sh/Makefile --- Script to aid building and testing Emacs Lisp packages
|
||||
|
||||
# This Makefile is from the makem.sh repo: <https://github.com/alphapapa/makem.sh>.
|
||||
|
||||
# * Arguments
|
||||
|
||||
# For consistency, we use only var=val options, not hyphen-prefixed options.
|
||||
|
||||
# NOTE: I don't like duplicating the arguments here and in makem.sh,
|
||||
# but I haven't been able to find a way to pass arguments which
|
||||
# conflict with Make's own arguments through Make to the script.
|
||||
# Using -- doesn't seem to do it.
|
||||
|
||||
ifdef install-deps
|
||||
INSTALL_DEPS = "--install-deps"
|
||||
endif
|
||||
ifdef install-linters
|
||||
INSTALL_LINTERS = "--install-linters"
|
||||
endif
|
||||
|
||||
ifdef sandbox
|
||||
ifeq ($(sandbox), t)
|
||||
SANDBOX = --sandbox
|
||||
else
|
||||
SANDBOX = --sandbox $(sandbox)
|
||||
endif
|
||||
endif
|
||||
|
||||
ifdef debug
|
||||
DEBUG = "--debug"
|
||||
endif
|
||||
|
||||
# ** Verbosity
|
||||
|
||||
# Since the "-v" in "make -v" gets intercepted by Make itself, we have
|
||||
# to use a variable.
|
||||
|
||||
verbose = $(v)
|
||||
|
||||
ifneq (,$(findstring vv,$(verbose)))
|
||||
VERBOSE = "-vv"
|
||||
else ifneq (,$(findstring v,$(verbose)))
|
||||
VERBOSE = "-v"
|
||||
endif
|
||||
|
||||
# * Rules
|
||||
|
||||
# TODO: Handle cases in which "test" or "tests" are called and a
|
||||
# directory by that name exists, which can confuse Make.
|
||||
|
||||
%:
|
||||
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS) $(@)
|
||||
|
||||
.DEFAULT: init
|
||||
init:
|
||||
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS)
|
64
README.md
64
README.md
@ -1,28 +1,26 @@
|
||||
[![License GPL 3][badge-license]](http://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
[](https://org-roam.readthedocs.io/en/latest/?badge=latest)
|
||||
[](https://img.shields.io/github/v/release/jethrokuan/org-roam)
|
||||
|
||||
## Synopsis
|
||||
|
||||
Org-roam is a rudimentary [Roam][roamresearch] replica built around
|
||||
the all-powerful [Org-mode][org].
|
||||
|
||||
Like Roam, Org-roam offers a powerful and effortless non-hierarchical
|
||||
note-taking approach. With Org-roam, notes flow naturally, making
|
||||
note-taking fun and easy.
|
||||
note-taking fun and easy. Org-roam *enables* a note-taking workflow that
|
||||
is not fluid with vanilla Org-mode (more in [this blog
|
||||
post](https://blog.jethro.dev/posts/how_to_take_smart_notes_org/)).
|
||||
|
||||
|
||||
The goal of the project is to implement core features of Roam around
|
||||
Org-mode, and eventually introduce features enabled by the Emacs
|
||||
ecosystem.
|
||||
ecosystem.
|
||||
|
||||
For more documentation, see [the documentation page](https://org-roam.readthedocs.io/en/latest/).
|
||||
|
||||
## Understanding Roam
|
||||
|
||||
To understand more about Roam, I recommend the following links:
|
||||
|
||||
- [Building a second brain in
|
||||
Roam](https://reddit.com/r/RoamResearch/comments/eho7de/building_a_second_brain_in_roamand_why_you_might)
|
||||
- [Roam: Why I Love It and How I Use
|
||||
It](https://www.nateliason.com/blog/roam)
|
||||
|
||||
## Project Status
|
||||
Visit [the documentation
|
||||
page](https://org-roam.readthedocs.io/en/latest/) for a tutorial and
|
||||
more links.
|
||||
|
||||
As of February 2020, it is in a very early stage of development.
|
||||
|
||||
@ -37,16 +35,52 @@ used to navigate to the respective files.
|
||||
|
||||

|
||||
|
||||
## Installation
|
||||
|
||||
The recommended method is using use-package and straight, or a similar package manager.
|
||||
|
||||
```emacs-lisp
|
||||
(use-package org-roam
|
||||
:after org
|
||||
:hook
|
||||
((org-mode . org-roam-mode)
|
||||
(after-init . org-roam--build-cache-async) ;; optional!
|
||||
)
|
||||
:straight (:host github :repo "jethrokuan/org-roam" :branch "develop")
|
||||
:custom
|
||||
(org-roam-directory "/path/to/org-files/")
|
||||
:bind
|
||||
("C-c n l" . org-roam)
|
||||
("C-c n t" . org-roam-today)
|
||||
("C-c n f" . org-roam-find-file)
|
||||
("C-c n i" . org-roam-insert)
|
||||
("C-c n g" . org-roam-show-graph))
|
||||
```
|
||||
|
||||
For more detailed installation instructions, please see [the
|
||||
installation
|
||||
documentation](https://org-roam.readthedocs.io/en/develop/installation/).
|
||||
|
||||
## Knowledge Bases using Org-Roam
|
||||
|
||||
- [Jethro Kuan](https://braindump.jethro.dev/)
|
||||
([Source](https://github.com/jethrokuan/braindump/tree/master/org))
|
||||
|
||||
## Changelog
|
||||
|
||||
A changelog is being maintained [here](CHANGELOG.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
To report bugs and suggest new feature use the issue tracker. If you
|
||||
have some code which you would like to be merged, then open a pull
|
||||
request. Please also see CONTRIBUTING.md.
|
||||
request. Please also see [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
Copyright © Jethro Kuan and contributors. Distributed under the GNU
|
||||
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
|
||||
|
116
doc/configuration.md
Normal file
116
doc/configuration.md
Normal file
@ -0,0 +1,116 @@
|
||||
To ensure that Org-roam remains manageable, the number of
|
||||
configuration options is deliberately kept small. However, we have
|
||||
attempted to accommodate as many usage styles as possible.
|
||||
|
||||
In this section, we'll go over the main customization options
|
||||
available to Org-Roam. This section is *crucial*. We need to exploit
|
||||
the flexibility of Emacs, and mould our tools exactly to our liking.
|
||||
|
||||
All of Org-roam's customization options can be viewed via `M-x
|
||||
customize-group org-roam`.
|
||||
|
||||
## Setting the Org-roam Directory
|
||||
|
||||
Perhaps the single most important variable to set is
|
||||
`org-roam-directory`. Set `org-roam-directory` to the folder
|
||||
containing all your Org files:
|
||||
|
||||
```emacs-lisp
|
||||
(setq org-roam-directory "/path/to/org/")
|
||||
```
|
||||
|
||||
Every Org file, at any level of nesting, within `/path/to/org/` is
|
||||
considered part of the Org-roam ecosystem.
|
||||
|
||||
## Org-roam Buffer
|
||||
|
||||
The Org-roam buffer defaults to popping up from the right. You may
|
||||
choose to set it to pop up from the left with `(setq
|
||||
org-roam-buffer-position 'left)`.
|
||||
|
||||
The Org-roam buffer name can also be renamed: e.g. `(setq
|
||||
org-roam-buffer "*my-buffer-name*")`.
|
||||
|
||||
The Org-roam buffer width is adjustable via `org-roam-buffer-width`.
|
||||
The value of `org-roam-buffer-width` set as a percentage of the total
|
||||
frame width. For example:
|
||||
|
||||
```emacs-lisp
|
||||
(setq org-roam-buffer-width 0.4)
|
||||
```
|
||||
|
||||
Will result in the Org-roam buffer taking up 40% of the screen width.
|
||||
I have found this to be a good number.
|
||||
|
||||
## Org-roam Links
|
||||
|
||||
By default, links are inserted with the title as the link description.
|
||||
This can make them hard to distinguish from external links. If you
|
||||
wish, you may choose add special indicators for Org-roam links by
|
||||
tweaking `org-roam-link-title-format`, for example:
|
||||
|
||||
```emacs-lisp
|
||||
(setq org-roam-link-title-format "R:%s")
|
||||
```
|
||||
|
||||
## Org-roam Files
|
||||
|
||||
These customization options revolve around the Org files created and
|
||||
managed by Org-roam.
|
||||
|
||||
### Automatically Creating Files Using Timestamp
|
||||
|
||||
A common hassle is ensuring that files are uniquely named within the
|
||||
Org-roam directory. Org-roam's default workflow utilizes the title of
|
||||
Org files in all of its main commands (`org-roam-insert`,
|
||||
`org-roam-find-file`). Hence, having any unique file name is a decent
|
||||
option, and the default workflow uses the timestamp as the filename.
|
||||
|
||||
The format of the filename is specified by the string
|
||||
`org-roam-file-format`, which defaults to `"%Y%m%d%H%M%S"`. To see
|
||||
valid specifications, see the help (`C-h f`) for `format-time-string`.
|
||||
|
||||
There are several reasons for keeping filenames meaningful. For
|
||||
example, one may wish to publish the Org files, and some publishing
|
||||
methods such as Org-publish use the file names as slugs for the URLs.
|
||||
|
||||
If you wish to maintain manual control of filenames, set
|
||||
`org-roam-use-timestamp-as-filename` to `nil`:
|
||||
|
||||
```emacs-lisp
|
||||
(setq org-roam-use-timestamp-as-filename nil)
|
||||
```
|
||||
|
||||
When this setting is turned off, the user is instead manually prompted
|
||||
for a filename. It is then the user's responsibility to ensure that
|
||||
the file names are unique.
|
||||
|
||||
### Autopopulating Titles
|
||||
|
||||
The default workflow uses the title of the Org file in several
|
||||
commands. The title is specified via the `#+TITLE:` attribute,
|
||||
typically near the top of the file. The option
|
||||
`org-roam-autopopulate-title` defaults to `t`. When true, the title
|
||||
attribute is automatically inserted into the files created via
|
||||
Org-roam commands. Setting it to `nil` will disable this behaviour.
|
||||
|
||||
|
||||
## Org-roam Graph Viewer
|
||||
|
||||
Org-roam generates an SVG image using
|
||||
[Graphviz](https://graphviz.org/). To setup graph navigation, see the
|
||||
[Graph Setup](graph_setup.md) page.
|
||||
|
||||
Org-roam tries its best to locate the Graphviz executable from your
|
||||
PATH, but if it fails to do so, you may set it manually:
|
||||
|
||||
```
|
||||
(setq org-roam-graphviz-executable "/path/to/dot")
|
||||
```
|
||||
|
||||
Org-roam also attempts to use Firefox (located on PATH) to view the
|
||||
SVG, you may choose to set it to any compatible program:
|
||||
|
||||
```
|
||||
(setq org-roam-graph-viewer "/path/to/image-viewer")
|
||||
```
|
@ -1,10 +1,9 @@
|
||||
## Ecosystem
|
||||
|
||||
A number of packages work well combined with Org-Roam:
|
||||
|
||||
### Deft
|
||||
[Deft](https://jblevins.org/projects/deft/) provides a nice
|
||||
interface for browsing and filtering org-roam notes.
|
||||
## Deft
|
||||
|
||||
[Deft][deft] provides a nice interface for browsing and filtering
|
||||
org-roam notes.
|
||||
|
||||
```
|
||||
(use-package deft
|
||||
@ -19,7 +18,12 @@ interface for browsing and filtering org-roam notes.
|
||||
(deft-use-filename-as-title t))
|
||||
```
|
||||
|
||||
### Org-journal
|
||||
The Deft interface can slow down quickly when the number of files get
|
||||
huge. [Notdeft][notdeft] is a fork of Deft that uses an external
|
||||
search engine and indexer.
|
||||
|
||||
## Org-journal
|
||||
|
||||
[Org-journal](https://github.com/bastibe/org-journal) is a more
|
||||
powerful alternative to the simple function `org-roam-today`. It
|
||||
provides better journaling capabilities, and a nice calendar interface
|
||||
@ -35,3 +39,59 @@ to see all dated entries.
|
||||
(org-journal-dir "/path/to/org-roam-files/")
|
||||
(org-journal-date-format "%A, %d %B %Y"))
|
||||
```
|
||||
|
||||
## Note-taking Add-ons
|
||||
|
||||
These are some plugins that make note-taking in Org-mode more
|
||||
enjoyable.
|
||||
|
||||
### Org-download
|
||||
|
||||
[Org-download][org-download] lets you screenshot and yank images from
|
||||
the web into your notes:
|
||||
|
||||

|
||||
|
||||
```emacs-lisp
|
||||
(use-package org-download
|
||||
:after org
|
||||
:bind
|
||||
(:map org-mode-map
|
||||
(("s-Y" . org-download-screenshot)
|
||||
("s-y" . org-download-yank))))
|
||||
```
|
||||
|
||||
### mathpix.el
|
||||
|
||||
[mathpix.el][mathpix-el] uses [Mathpix's](https://mathpix.com/) API to convert clips into
|
||||
latex equations:
|
||||
|
||||

|
||||
|
||||
```emacs-lisp
|
||||
(use-package mathpix.el
|
||||
:straight (:host github :repo "jethrokuan/mathpix.el")
|
||||
:custom ((mathpix-app-id "app-id")
|
||||
(mathpix-app-key "app-key"))
|
||||
:bind
|
||||
("C-x m" . mathpix-screenshot))
|
||||
```
|
||||
|
||||
### Org-noter / Interleave
|
||||
|
||||
[Org-noter][org-noter] and [Interleave][interleave] are both projects
|
||||
that allow synchronised annotation of documents (PDF, EPUB etc.)
|
||||
within Org-mode.
|
||||
|
||||
### Org-ref
|
||||
|
||||
[Org-ref][org-ref] does citation and bibliography management in
|
||||
Org-mode, and a great tool for scientific notes.
|
||||
|
||||
[deft]: https://jblevins.org/projects/deft/
|
||||
[notdeft]: https://github.com/hasu/notdeft
|
||||
[org-download]: https://github.com/abo-abo/org-download
|
||||
[mathpix-el]: https://github.com/jethrokuan/mathpix.el
|
||||
[org-noter]: https://github.com/weirdNox/org-noter
|
||||
[interleave]: https://github.com/rudolfochrist/interleave
|
||||
[org-ref]: https://github.com/jkitchin/org-ref
|
||||
|
39
doc/graph_setup.md
Normal file
39
doc/graph_setup.md
Normal file
@ -0,0 +1,39 @@
|
||||
The setup is similar to that of org-protocol. Here `roam://` links are
|
||||
defined, and need to be associated with an application.
|
||||
|
||||
The gist of the setup is setting up a Bash script to trim off the
|
||||
`roam://` prefix from the link, causing the desktop application to
|
||||
call `emacsclient path/to/org-roam-file.org`.
|
||||
|
||||
## Setting Up for Linux
|
||||
|
||||
Create a desktop application. I place mine in
|
||||
`~/.local/share/applications/roam.desktop`:
|
||||
|
||||
```
|
||||
[Desktop Entry]
|
||||
Name=Org-Roam Client
|
||||
Exec=/home/jethro/.local/bin/launch_emacs %u
|
||||
Icon=emacs-icon
|
||||
Type=Application
|
||||
Terminal=false
|
||||
MimeType=x-scheme-handler/roam
|
||||
```
|
||||
|
||||
Note the `Exec` key is set to a bash script poorly named
|
||||
`launch_emacs`. You can set it to whatever you want.
|
||||
|
||||
Create the corresponding bash script, and make it executable. Here's
|
||||
how it looks like:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
emacsclient "${1#*:}"
|
||||
```
|
||||
|
||||
Finally, associate `roam://` links with the desktop application by
|
||||
running in your shell:
|
||||
|
||||
```bash
|
||||
xdg-mime default roam.desktop x-scheme-handler/roam
|
||||
```
|
BIN
doc/images/mathpix.gif
Normal file
BIN
doc/images/mathpix.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 781 KiB |
BIN
doc/images/org-download.gif
Normal file
BIN
doc/images/org-download.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 MiB |
BIN
doc/images/org-roam-find-file.gif
Normal file
BIN
doc/images/org-roam-find-file.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 578 KiB |
BIN
doc/images/org-roam-insert-filetag.gif
Normal file
BIN
doc/images/org-roam-insert-filetag.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 774 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.2 MiB |
@ -6,13 +6,15 @@ The recommended method is using [use-package][use-package] and
|
||||
```
|
||||
(use-package org-roam
|
||||
:after org
|
||||
:hook (org-mode . org-roam-mode)
|
||||
:straight (:host github :repo "jethrokuan/org-roam")
|
||||
:hook
|
||||
((org-mode . org-roam-mode)
|
||||
(after-init . org-roam--build-cache-async) ;; optional!
|
||||
)
|
||||
:straight (:host github :repo "jethrokuan/org-roam" :branch "develop")
|
||||
:custom
|
||||
(org-roam-directory "/path/to/org-files/")
|
||||
(org-roam-link-representation 'title) ;; or keep it as 'id
|
||||
:bind
|
||||
("C-c n l" . org-roam)
|
||||
("C-c n l" . org-roam)
|
||||
("C-c n t" . org-roam-today)
|
||||
("C-c n f" . org-roam-find-file)
|
||||
("C-c n i" . org-roam-insert)
|
||||
@ -26,10 +28,34 @@ directory and add it to your load path:
|
||||
git clone https://github.com/jethrokuan/org-roam/ ~/.emacs.d/elisp/org-roam
|
||||
```
|
||||
|
||||
```
|
||||
(use-package org-roam
|
||||
:after org
|
||||
:load-path "elisp/"
|
||||
:hook
|
||||
((org-mode . org-roam-mode)
|
||||
(after-init . org-roam--build-cache-async) ;; optional!
|
||||
)
|
||||
:custom
|
||||
(org-roam-directory "/path/to/org-files/")
|
||||
:bind
|
||||
("C-c n l" . org-roam)
|
||||
("C-c n t" . org-roam-today)
|
||||
("C-c n f" . org-roam-find-file)
|
||||
("C-c n i" . org-roam-insert)
|
||||
("C-c n g" . org-roam-show-graph))
|
||||
```
|
||||
|
||||
Or without use-package:
|
||||
|
||||
```
|
||||
(add-to-list 'load-path "./elisp")
|
||||
(require 'org-roam)
|
||||
```
|
||||
|
||||
There are a number of important configuration options, that greatly
|
||||
affect the Roam workflow. Do look through them at the
|
||||
[Configuration](configuration.md) page.
|
||||
|
||||
[use-package]: https://github.com/jwiegley/use-package
|
||||
[straight]: https://github.com/raxod502/straight.el
|
||||
|
20
doc/notetaking_workflow.md
Normal file
20
doc/notetaking_workflow.md
Normal file
@ -0,0 +1,20 @@
|
||||
## Recommended Books
|
||||
- [How to Take Smart Notes][1]
|
||||
|
||||
## Articles
|
||||
- [How to Take Smart Notes in Org-mode - Jethro Kuan][7]
|
||||
- [The Zettelkasten Method - LessWrong 2.0][3]
|
||||
- [Building a second brain in Roam][4]
|
||||
- [Roam: Why I Love It and How I Use It][5]
|
||||
- [Adam Keesling's Twitter Thread][6]
|
||||
|
||||
## What to Do With Your Notes
|
||||
- [How to Use Roam to Outline a New Article in Under 20 Minutes][2]
|
||||
|
||||
[1]: https://www.goodreads.com/book/show/34507927-how-to-take-smart-notes?ac=1&from_search=true&qid=6L8iEE1FIA&rank=1
|
||||
[2]: https://www.youtube.com/watch?v=RvWic15iXjk
|
||||
[3]: https://www.lesswrong.com/posts/NfdHG6oHBJ8Qxc26s/the-zettelkasten-method-1
|
||||
[4]: https://reddit.com/r/RoamResearch/comments/eho7de/building_a_second_brain_in_roamand_why_you_might
|
||||
[5]: https://www.nateliason.com/blog/roam
|
||||
[6]: https://twitter.com/adam_keesling/status/1196864424725774336?s=20
|
||||
[7]: https://blog.jethro.dev/posts/how_to_take_smart_notes_org/
|
73
doc/tour.md
73
doc/tour.md
@ -1,20 +1,69 @@
|
||||
### A Tour of Org-Roam
|
||||
Org-roam was built to support a workflow that was not possible with
|
||||
vanilla Org-mode. This flow is modelled after the [Zettelkasten
|
||||
method][zettelkasten], and many of [Roam Research][roam]'s workflows.
|
||||
Understanding this flow is crucial! Org-roam doesn't auto-magically
|
||||
make your note-taking better -- it's changing the note-taking workflow
|
||||
that does.
|
||||
|
||||
All of this starts from the note. A note is just a simple `.org` file
|
||||
in the directory. Any org file in the directory is considered part of
|
||||
the org-roam ecosystem. Notes are quickly linked together (and created
|
||||
if necessary) using `org-roam-insert`.
|
||||
To understand more the methods and madness, the [Note-Taking
|
||||
Workflow][appendix:ntw] page contains a page of useful references.
|
||||
I've also written [a post][jethro-blog-post] about how I use Org-roam.
|
||||
|
||||

|
||||
Without further ado, let's begin!
|
||||
|
||||
Org-roam tracks all of these file links, and builds a cache
|
||||
asynchronously in the background. This cache is used to populate the
|
||||
backlinks buffer, which shows files that link to the current file, as
|
||||
well as some preview contents:
|
||||
## Building the Cache
|
||||
|
||||
Assuming you've set `org-roam-directory` appropriately, running `M-x
|
||||
org-roam--build-cache-async` should build up the caches that will
|
||||
allow you to begin using Org-roam. I do this on startup:
|
||||
|
||||
```emacs-lisp
|
||||
(add-hook 'after-init-hook 'org-roam--build-cache-async)
|
||||
```
|
||||
|
||||
## Finding a Note
|
||||
|
||||
`org-roam-find-file` shows you the list of notes you currently have in
|
||||
Org-roam. Selecting the title will bring you to the corresponding
|
||||
note. Entering a title of a note that does not yet exist will create a
|
||||
new note with that title.
|
||||
|
||||

|
||||
|
||||
## Inserting Links
|
||||
|
||||
Within your Org-roam notes, you are encouraged to liberally insert
|
||||
links to existing (or new) Org-roam notes with `org-roam-insert`.
|
||||
Entering a non-existent title will also create a new note with that
|
||||
title.
|
||||
|
||||

|
||||
|
||||
It is crucial for good usage of Org-roam to insert links liberally,
|
||||
where you want them the notes to resurface!
|
||||
|
||||
## The Org-roam Buffer
|
||||
|
||||
All of Org-roam's operations are designed such that the built cache is
|
||||
a consistent view of the inter-connectivity between your notes. The
|
||||
Org-roam buffer shows backlinks: i.e. the files that link to the
|
||||
currently viewed file, along with some surrounding context. The
|
||||
Org-roam buffer will always show the backlinks for the current
|
||||
Org-roam file in view.
|
||||
|
||||

|
||||
|
||||
These file links also form a graph. The generated graph is navigable
|
||||
in Emacs.
|
||||
## Exporting the Graph
|
||||
|
||||
It's also possible to export the links as a graph, using graphviz. The
|
||||
generated graph is navigable in Emacs, but requires some additional
|
||||
setup, which I describe in the [Graph Appendix][appendix:graph-setup]
|
||||
page.
|
||||
|
||||

|
||||
|
||||
[zettelkasten]: https://zettelkasten.de/
|
||||
[appendix:ntw]: notetaking_workflow.md
|
||||
[appendix:graph-setup]: graph_setup.md
|
||||
[roam]: https://www.roamresearch.com/
|
||||
[jethro-blog-post]: https://blog.jethro.dev/posts/how_to_take_smart_notes_org/
|
||||
|
@ -7,8 +7,11 @@ nav:
|
||||
- Home: index.md
|
||||
- A Tour of Org-Roam: tour.md
|
||||
- Installation: installation.md
|
||||
- Configuration: configuration.md
|
||||
- Ecosystem: ecosystem.md
|
||||
- Similar Packages: comparison.md
|
||||
- "Appendix: Note-taking Workflow": notetaking_workflow.md
|
||||
- "Appendix: Graph Setup": graph_setup.md
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.betterem:
|
||||
|
121
org-roam-utils.el
Normal file
121
org-roam-utils.el
Normal file
@ -0,0 +1,121 @@
|
||||
;;; org-roam-utils.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
|
||||
|
||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/jethrokuan/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "26.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3, or (at your option)
|
||||
;; any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
;; Boston, MA 02110-1301, USA.
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This library is an attempt at injecting Roam functionality into Org-mode.
|
||||
;; This is achieved primarily through building caches for forward links,
|
||||
;; backward links, and file titles.
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(require 'org)
|
||||
(require 'org-element)
|
||||
(require 'subr-x)
|
||||
(require 'cl-lib)
|
||||
|
||||
(defun org-roam--find-files (dir)
|
||||
"Return all `org-roam' files in `DIR'."
|
||||
(if (file-exists-p dir)
|
||||
(let ((files (directory-files dir t "." t))
|
||||
(dir-ignore-regexp (concat "\\(?:"
|
||||
"\\."
|
||||
"\\|\\.\\."
|
||||
"\\)$"))
|
||||
result)
|
||||
(dolist (file files)
|
||||
(cond
|
||||
((file-directory-p file)
|
||||
(when (not (string-match dir-ignore-regexp file))
|
||||
(setq result (append (org-roam--find-files file) result))))
|
||||
((and (file-readable-p file)
|
||||
(string= (file-name-extension file) "org"))
|
||||
(setq result (cons (file-truename file) result)))))
|
||||
result)))
|
||||
|
||||
(defun org-roam--parse-content (&optional file-path)
|
||||
"Parse the current buffer, and return a list of items for processing."
|
||||
(org-element-map (org-element-parse-buffer) 'link
|
||||
(lambda (link)
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link))
|
||||
(start (org-element-property :begin link)))
|
||||
(when (and (string= type "file")
|
||||
(string= (file-name-extension path) "org"))
|
||||
(goto-char start)
|
||||
(let* ((element (org-element-at-point))
|
||||
(content (or (org-element-property :raw-value element)
|
||||
(buffer-substring
|
||||
(or (org-element-property :content-begin element)
|
||||
(org-element-property :begin element))
|
||||
(or (org-element-property :content-end element)
|
||||
(org-element-property :end element))))))
|
||||
(list :from (or file-path
|
||||
(file-truename (buffer-file-name (current-buffer))))
|
||||
:to (file-truename (expand-file-name path org-roam-directory))
|
||||
:content (string-trim content))))))))
|
||||
|
||||
(cl-defun org-roam--insert-item (item &key forward backward)
|
||||
"Insert ITEM into FORWARD and BACKWARD cache.
|
||||
|
||||
ITEM is of the form: (:from from-path :to to-path :content preview-content)."
|
||||
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
|
||||
;; Build forward-links
|
||||
(let ((links (gethash p-from forward)))
|
||||
(if links
|
||||
(puthash p-from
|
||||
(if (member p-to links)
|
||||
links
|
||||
(cons p-to links)) forward)
|
||||
(puthash p-from (list p-to) forward)))
|
||||
;; Build backward-links
|
||||
(let ((contents-hash (gethash p-to backward)))
|
||||
(if contents-hash
|
||||
(if-let ((contents-list (gethash p-from contents-hash)))
|
||||
(let ((updated (cons content contents-list)))
|
||||
(puthash p-from updated contents-hash)
|
||||
(puthash p-to contents-hash backward))
|
||||
(progn
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash backward)))
|
||||
(let ((contents-hash (make-hash-table :test #'equal)))
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash backward))))))
|
||||
|
||||
(defun org-roam--extract-title ()
|
||||
"Extract the title from `BUFFER'."
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'keyword
|
||||
(lambda (kw)
|
||||
(when (string= (org-element-property :key kw) "TITLE")
|
||||
(org-element-property :value kw)))
|
||||
:first-match t))
|
||||
|
||||
(provide 'org-roam-utils)
|
||||
|
||||
;;; org-roam-utils.el ends here
|
461
org-roam.el
461
org-roam.el
@ -1,9 +1,39 @@
|
||||
;;; org-roam.el --- Roam Research replica with Org-mode -*- coding: utf-8; lexical-binding: t -*-
|
||||
|
||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/jethrokuan/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 0.1.0
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (async "1.9.4"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation; either version 3, or (at your option)
|
||||
;; any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
||||
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
;; Boston, MA 02110-1301, USA.
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
|
||||
;; This library is an attempt at injecting Roam functionality into Org-mode.
|
||||
;; This is achieved primarily through building caches for forward links,
|
||||
;; backward links, and file titles.
|
||||
;;
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(eval-when-compile (require 'cl-lib))
|
||||
(require 'dash)
|
||||
(require 'org-element)
|
||||
@ -11,19 +41,24 @@
|
||||
(require 'subr-x)
|
||||
(require 's)
|
||||
(require 'f)
|
||||
(require 'org-roam-utils)
|
||||
|
||||
;;; Customizations
|
||||
(defgroup org-roam nil
|
||||
"Roam Research replica in Org-mode."
|
||||
:group 'org
|
||||
:prefix "org-roam-")
|
||||
:prefix "org-roam-"
|
||||
:link '(url-link :tag "Github" "https://github.com/jethrokuan/org-roam")
|
||||
:link '(url-link :tag "Online Manual" "https://org-roam.readthedocs.io/"))
|
||||
|
||||
(defcustom org-roam-directory (expand-file-name "~/org-roam/")
|
||||
"Org-roam directory."
|
||||
:type 'directory
|
||||
"Path to Org-roam files.
|
||||
|
||||
All Org files, at any level of nesting, is considered part of the Org-roam."
|
||||
:type 'directoy
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-position 'right
|
||||
(defcustom org-roam-buffer-position 'right
|
||||
"Position of `org-roam' buffer.
|
||||
|
||||
Valid values are
|
||||
@ -33,26 +68,8 @@ Valid values are
|
||||
(const right))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-link-representation 'id
|
||||
"The value used to represent an org-roam link.
|
||||
|
||||
Valid values are
|
||||
* file,
|
||||
* title."
|
||||
:type '(choice (const id)
|
||||
(const title))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-timestamped-files nil
|
||||
"Whether to use timestamps to generate unique filenames."
|
||||
:type 'boolean
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-timestamp-format "%Y-%m-%d%H%M%S"
|
||||
"The timestamp format to use filenames.")
|
||||
|
||||
(defcustom org-roam-link-id-format "§%s"
|
||||
"The format string used when inserting org-roam links that use id."
|
||||
(defcustom org-roam-file-format "%Y%m%d%H%M%S"
|
||||
"The timestamp format to use filenames."
|
||||
:type 'string
|
||||
:group 'org-roam)
|
||||
|
||||
@ -61,6 +78,11 @@ Valid values are
|
||||
:type 'string
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-use-timestamp-as-filename t
|
||||
"Whether to use timestamp as a file name. If not true, prompt for a file name each time."
|
||||
:type 'boolean
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-autopopulate-title t "Whether to autopopulate the title."
|
||||
:type 'boolean
|
||||
:group 'org-roam)
|
||||
@ -130,35 +152,16 @@ If called interactively, then PARENTS is non-nil."
|
||||
(defun org-roam--org-roam-file-p ()
|
||||
"Return t if file is part of org-roam system, false otherwise."
|
||||
(and (buffer-file-name (current-buffer))
|
||||
(f-child-of-p (file-truename (buffer-file-name (current-buffer)))
|
||||
org-roam-directory)))
|
||||
(f-descendant-of-p (file-truename (buffer-file-name (current-buffer)))
|
||||
org-roam-directory)))
|
||||
|
||||
(defun org-roam--get-title (file)
|
||||
"Return title of `FILE'.
|
||||
|
||||
It first tries the cache. If the cache does not contain the file,
|
||||
it will return the title by loading the file."
|
||||
(defun org-roam--get-title-from-cache (file)
|
||||
"Return title of `FILE' from the cache."
|
||||
(or (gethash file org-roam-titles-cache)
|
||||
(org-roam--extract-file-title file)))
|
||||
|
||||
(defun org-roam--find-files (dir)
|
||||
"Return all org-roam files in `DIR'."
|
||||
(if (file-exists-p dir)
|
||||
(let ((files (directory-files dir t "." t))
|
||||
(dir-ignore-regexp (concat "\\(?:"
|
||||
"\\."
|
||||
"\\|\\.\\."
|
||||
"\\)$"))
|
||||
result)
|
||||
(dolist (file files)
|
||||
(cond
|
||||
((file-directory-p file)
|
||||
(when (not (string-match dir-ignore-regexp file))
|
||||
(setq result (append (org-roam--find-files file) result))))
|
||||
((and (file-readable-p file)
|
||||
(string= (file-name-extension file) "org"))
|
||||
(setq result (cons (file-truename file) result)))))
|
||||
result)))
|
||||
(progn
|
||||
(unless org-roam-cache-initialized
|
||||
(user-error "The Org-Roam caches aren't built! Please run org-roam--build-cache-async"))
|
||||
nil)))
|
||||
|
||||
(defun org-roam--find-all-files ()
|
||||
"Return all org-roam files."
|
||||
@ -177,26 +180,22 @@ If `ABSOLUTE', return the absolute file-path. Else, return the relative file-pat
|
||||
(file-relative-name absolute-file-path
|
||||
(file-truename org-roam-directory)))))
|
||||
|
||||
(defun org-roam--get-id (file-path)
|
||||
"Convert `FILE-PATH' to the org-roam id."
|
||||
(file-name-sans-extension
|
||||
(file-relative-name
|
||||
(file-truename file-path)
|
||||
(file-truename org-roam-directory))))
|
||||
(defun org-roam--get-title-or-slug (file-path)
|
||||
"Convert `FILE-PATH' to the file title, if it exists. Else, return the path."
|
||||
(or (org-roam--get-title-from-cache file-path)
|
||||
(-> file-path
|
||||
(file-relative-name (file-truename org-roam-directory))
|
||||
(file-name-sans-extension))))
|
||||
|
||||
(defun org-roam--get-title-or-id (file-path)
|
||||
"Convert `FILE-PATH' to the file title, if it exists. Else, return the id."
|
||||
(or (org-roam--get-title file-path)
|
||||
(org-roam--get-id file-path)))
|
||||
|
||||
(defun org-roam--title-to-id (title)
|
||||
"Convert TITLE to id."
|
||||
(defun org-roam--title-to-slug (title)
|
||||
"Convert TITLE to a filename-suitable slug."
|
||||
(let* ((s (s-downcase title))
|
||||
(s (replace-regexp-in-string "[^a-zA-Z0-9_ ]" "" s))
|
||||
(s (s-split " " s))
|
||||
(s (s-join "_" s)))
|
||||
s))
|
||||
|
||||
|
||||
;;; Creating org-roam files
|
||||
(defun org-roam--populate-title (file &optional title)
|
||||
"Populate title line for FILE using TITLE, if provided.
|
||||
@ -218,7 +217,7 @@ If not provided, derive the title from the file name."
|
||||
(defun org-roam--make-file (file-path &optional title)
|
||||
"Create an org-roam file at FILE-PATH, optionally setting the TITLE attribute."
|
||||
(if (file-exists-p file-path)
|
||||
(error (format "Aborting, file already exists at " file-path))
|
||||
(error (format "Aborting, file already exists at %s" file-path))
|
||||
(if org-roam-autopopulate-title
|
||||
(org-roam--populate-title file-path title)
|
||||
(make-empty-file file-path))))
|
||||
@ -232,9 +231,20 @@ If not provided, derive the title from the file name."
|
||||
(org-roam--make-file file-path))
|
||||
(find-file file-path)))
|
||||
|
||||
(defun org-roam--get-new-id ()
|
||||
"Return a new ID, generated from the current time."
|
||||
(format-time-string org-roam-timestamp-format (current-time)))
|
||||
(defun org-roam--get-new-id (&optional title)
|
||||
"Return a new ID, generated from the current time.
|
||||
|
||||
Optionally pass it the title, for a smart file name."
|
||||
(if org-roam-use-timestamp-as-filename
|
||||
(format-time-string org-roam-file-format (current-time))
|
||||
(let* ((slug (read-string "Enter ID (without extension): "
|
||||
(if title
|
||||
(org-roam--title-to-slug title)
|
||||
"")))
|
||||
(file-path (org-roam--get-file-path slug t)))
|
||||
(if (file-exists-p file-path)
|
||||
(user-error "There's already a file at %s")
|
||||
slug))))
|
||||
|
||||
(defun org-roam-new-file ()
|
||||
"Quickly create a new file, using the current timestamp."
|
||||
@ -243,59 +253,40 @@ If not provided, derive the title from the file name."
|
||||
|
||||
;;; Inserting org-roam links
|
||||
(defun org-roam-insert ()
|
||||
"Insert an org-roam link."
|
||||
"Find an org-roam file, and insert a relative org link to it at point."
|
||||
(interactive)
|
||||
(pcase org-roam-link-representation
|
||||
('id (org-roam--insert-id))
|
||||
('title (org-roam--insert-title))))
|
||||
|
||||
(defun org-roam--insert-title ()
|
||||
"Find `ID', and insert a relative org link to it at point."
|
||||
(let* ((completions (mapcar (lambda (file)
|
||||
(list (org-roam--get-title-or-id file)
|
||||
(org-roam--get-id file)))
|
||||
(list (org-roam--get-title-or-slug file)
|
||||
file))
|
||||
(org-roam--find-all-files)))
|
||||
(title (completing-read "File: " completions))
|
||||
(id (cadr (assoc title completions))))
|
||||
(when (not id)
|
||||
(if org-roam-timestamped-files
|
||||
(setq id (org-roam--get-new-id)))
|
||||
(read-string "Enter new file id: " (org-roam--title-to-id title)))
|
||||
(let ((file-path (org-roam--get-file-path id)))
|
||||
(unless (file-exists-p file-path)
|
||||
(org-roam--make-file file-path title))
|
||||
(insert (format "[[%s][%s]]"
|
||||
(concat "file:" file-path)
|
||||
(format org-roam-link-title-format title))))))
|
||||
|
||||
(defun org-roam--insert-id ()
|
||||
"Find `ID', and insert a relative org link to it at point."
|
||||
(let* ((id (completing-read "File: " (mapcar #'org-roam--get-id (org-roam--find-all-files))))
|
||||
(file-path (org-roam--get-file-path id)))
|
||||
(unless (file-exists-p file-path)
|
||||
(org-roam--make-file file-path))
|
||||
(absolute-file-path (or (cadr (assoc title completions))
|
||||
(org-roam--get-file-path (org-roam--get-new-id title) t)))
|
||||
(current-file-path (-> (current-buffer)
|
||||
(buffer-file-name)
|
||||
(file-truename)
|
||||
(file-name-directory))))
|
||||
(unless (file-exists-p absolute-file-path)
|
||||
(org-roam--make-file absolute-file-path title))
|
||||
(insert (format "[[%s][%s]]"
|
||||
(concat "file:" file-path)
|
||||
(format org-roam-link-id-format id)))))
|
||||
(concat "file:" (file-relative-name absolute-file-path
|
||||
current-file-path))
|
||||
(format org-roam-link-title-format title)))))
|
||||
|
||||
;;; Finding org-roam files
|
||||
(defun org-roam-find-file ()
|
||||
"Find and open an org-roam file."
|
||||
(interactive)
|
||||
(let* ((completions (mapcar (lambda (file)
|
||||
(list (org-roam--get-title-or-id file) file))
|
||||
(list (org-roam--get-title-or-slug file) file))
|
||||
(org-roam--find-all-files)))
|
||||
(title-or-id (completing-read "File: " completions))
|
||||
(file-path (cadr (assoc title-or-id completions))))
|
||||
(unless file-path
|
||||
(let ((id (if org-roam-timestamped-files
|
||||
(org-roam--get-new-id)
|
||||
(read-string "Enter new file id: "
|
||||
(org-roam--title-to-id title-or-id)))))
|
||||
(setq file-path (org-roam--get-file-path id t))))
|
||||
(unless (file-exists-p file-path)
|
||||
(org-roam--make-file file-path title-or-id))
|
||||
(find-file file-path)))
|
||||
(title-or-slug (completing-read "File: " completions))
|
||||
(absolute-file-path (or (cadr (assoc title-or-slug completions))
|
||||
(org-roam--get-file-path
|
||||
(org-roam--get-new-id title-or-slug) t))))
|
||||
(unless (file-exists-p absolute-file-path)
|
||||
(org-roam--make-file absolute-file-path title-or-slug))
|
||||
(find-file absolute-file-path)))
|
||||
|
||||
;;; Building the org-roam cache (asynchronously)
|
||||
(defun org-roam--build-cache-async ()
|
||||
@ -303,101 +294,30 @@ If not provided, derive the title from the file name."
|
||||
(interactive)
|
||||
(async-start
|
||||
`(lambda ()
|
||||
(require 'org)
|
||||
(require 'org-element)
|
||||
(require 'subr-x) ; temp-fix
|
||||
(require 'cl-lib)
|
||||
(setq load-path ',load-path)
|
||||
(package-initialize)
|
||||
(require 'org-roam-utils)
|
||||
,(async-inject-variables "org-roam-directory")
|
||||
(let ((backward-links (make-hash-table :test #'equal))
|
||||
(forward-links (make-hash-table :test #'equal))
|
||||
(file-titles (make-hash-table :test #'equal)))
|
||||
(cl-labels ((org-roam--find-files
|
||||
(dir)
|
||||
(if (file-exists-p dir)
|
||||
(let ((files (directory-files dir t "." t))
|
||||
(dir-ignore-regexp (concat "\\(?:"
|
||||
"\\."
|
||||
"\\|\\.\\."
|
||||
"\\)$"))
|
||||
result)
|
||||
(dolist (file files)
|
||||
(cond
|
||||
((file-directory-p file)
|
||||
(when (not (string-match dir-ignore-regexp file))
|
||||
(setq result (append (org-roam--find-files file) result))))
|
||||
((and (file-readable-p file)
|
||||
(string= (file-name-extension file) "org"))
|
||||
(setq result (cons (file-truename file) result)))))
|
||||
result)))
|
||||
(org-roam--parse-content
|
||||
(file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(with-current-buffer (current-buffer)
|
||||
(org-element-map (org-element-parse-buffer) 'link
|
||||
(lambda (link)
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link))
|
||||
(start (org-element-property :begin link)))
|
||||
(when (and (string= type "file")
|
||||
(string= (file-name-extension path) "org"))
|
||||
(goto-char start)
|
||||
(let* ((element (org-element-at-point))
|
||||
(content (or (org-element-property :raw-value element)
|
||||
(buffer-substring
|
||||
(or (org-element-property :content-begin element)
|
||||
(org-element-property :begin element))
|
||||
(or (org-element-property :content-end element)
|
||||
(org-element-property :end element))))))
|
||||
(list :from file
|
||||
:to (file-truename (expand-file-name path org-roam-directory))
|
||||
:content (string-trim content))))))))))
|
||||
(org-roam--process-items
|
||||
(items)
|
||||
(mapcar
|
||||
(lambda (item)
|
||||
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
|
||||
;; Build forward-links
|
||||
(let ((links (gethash p-from forward-links)))
|
||||
(if links
|
||||
(puthash p-from
|
||||
(if (member p-to links)
|
||||
links
|
||||
(cons p-to links)) forward-links)
|
||||
(puthash p-from (list p-to) forward-links)))
|
||||
;; Build backward-links
|
||||
(let ((contents-hash (gethash p-to backward-links)))
|
||||
(if contents-hash
|
||||
(if-let ((contents-list (gethash p-from contents-hash)))
|
||||
(let ((updated (cons content contents-list)))
|
||||
(puthash p-from updated contents-hash)
|
||||
(puthash p-to contents-hash backward-links))
|
||||
(progn
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash backward-links)))
|
||||
(let ((contents-hash (make-hash-table :test #'equal)))
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash backward-links))))))
|
||||
items))
|
||||
(org-roam--extract-title
|
||||
(buffer)
|
||||
(with-current-buffer buffer
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'keyword
|
||||
(lambda (kw)
|
||||
(when (string= (org-element-property :key kw) "TITLE")
|
||||
(org-element-property :value kw)))
|
||||
:first-match t))))
|
||||
(let ((org-roam-files (org-roam--find-files org-roam-directory)))
|
||||
(mapcar #'org-roam--process-items
|
||||
(mapcar #'org-roam--parse-content org-roam-files))
|
||||
(mapcar (lambda (file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when-let ((title (org-roam--extract-title (current-buffer))))
|
||||
(puthash file title file-titles))))
|
||||
org-roam-files)))
|
||||
(let* ((org-roam-files (org-roam--find-files org-roam-directory))
|
||||
(file-items (mapcar (lambda (file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-roam--parse-content file))) org-roam-files)))
|
||||
(dolist (items file-items)
|
||||
(dolist (item items)
|
||||
(org-roam--insert-item
|
||||
item
|
||||
:forward forward-links
|
||||
:backward backward-links)))
|
||||
(mapcar (lambda (file)
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(when-let ((title (org-roam--extract-title)))
|
||||
(puthash file title file-titles))))
|
||||
org-roam-files))
|
||||
(list
|
||||
:forward forward-links
|
||||
:backward backward-links
|
||||
@ -409,92 +329,43 @@ If not provided, derive the title from the file name."
|
||||
(setq org-roam-cache-initialized t)
|
||||
(message "Org-roam cache built!"))))
|
||||
|
||||
(defun org-roam--insert-item (item)
|
||||
"Insert `ITEM' into org-roam caches.
|
||||
|
||||
`ITEM' is of the form: (:from from-path :to to-path :content preview-content)
|
||||
|
||||
Before calling this function, `org-roam-cache' should be already populated."
|
||||
(pcase-let ((`(:from ,p-from :to ,p-to :content ,content) item))
|
||||
;; Build forward-links
|
||||
(let ((links (gethash p-from org-roam-forward-links-cache)))
|
||||
(if links
|
||||
(puthash p-from
|
||||
(if (member p-to links)
|
||||
links
|
||||
(cons p-to links)) org-roam-forward-links-cache)
|
||||
(puthash p-from (list p-to) org-roam-forward-links-cache)))
|
||||
;; Build backward-links
|
||||
(let ((contents-hash (gethash p-to org-roam-backward-links-cache)))
|
||||
(if contents-hash
|
||||
(if-let ((contents-list (gethash p-from contents-hash)))
|
||||
(let ((updated (cons content contents-list)))
|
||||
(puthash p-from updated contents-hash)
|
||||
(puthash p-to contents-hash org-roam-backward-links-cache))
|
||||
(progn
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash org-roam-backward-links-cache)))
|
||||
(let ((contents-hash (make-hash-table :test #'equal)))
|
||||
(puthash p-from (list content) contents-hash)
|
||||
(puthash p-to contents-hash org-roam-backward-links-cache))))))
|
||||
|
||||
(defun org-roam--parse-content ()
|
||||
"Parse the current buffer, and return a list of items for processing."
|
||||
(with-current-buffer (current-buffer)
|
||||
(org-element-map (org-element-parse-buffer) 'link
|
||||
(lambda (link)
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link))
|
||||
(start (org-element-property :begin link)))
|
||||
(when (and (string= type "file")
|
||||
(string= (file-name-extension path) "org"))
|
||||
(goto-char start)
|
||||
(let* ((element (org-element-at-point))
|
||||
(content (or (org-element-property :raw-value element)
|
||||
(buffer-substring
|
||||
(or (org-element-property :content-begin element)
|
||||
(org-element-property :begin element))
|
||||
(or (org-element-property :content-end element)
|
||||
(org-element-property :end element))))))
|
||||
(list :from (file-truename (buffer-file-name (current-buffer)))
|
||||
:to (file-truename (expand-file-name path org-roam-directory))
|
||||
:content (string-trim content)))))))))
|
||||
|
||||
(defun org-roam--clear-cache-for-buffer (buffer)
|
||||
"Remove any related links to the file for `BUFFER'.
|
||||
(defun org-roam--clear-cache ()
|
||||
"Remove any related links to the file.
|
||||
|
||||
This is equivalent to removing the node from the graph."
|
||||
(with-current-buffer (current-buffer)
|
||||
(let ((file (file-truename (buffer-file-name buffer))))
|
||||
;; Step 1: Remove all existing links for file
|
||||
(when-let ((forward-links (gethash file org-roam-forward-links-cache)))
|
||||
;; Delete backlinks to file
|
||||
(dolist (link forward-links)
|
||||
(when-let ((backward-links (gethash link org-roam-backward-links-cache)))
|
||||
(remhash file backward-links)
|
||||
(puthash link backward-links org-roam-backward-links-cache)))
|
||||
;; Clean out forward links
|
||||
(remhash file org-roam-forward-links-cache))
|
||||
;; Step 2: Remove from the title cache
|
||||
(remhash file org-roam-titles-cache))))
|
||||
(let ((file (file-truename (buffer-file-name (current-buffer)))))
|
||||
;; Step 1: Remove all existing links for file
|
||||
(when-let ((forward-links (gethash file org-roam-forward-links-cache)))
|
||||
;; Delete backlinks to file
|
||||
(dolist (link forward-links)
|
||||
(when-let ((backward-links (gethash link org-roam-backward-links-cache)))
|
||||
(remhash file backward-links)
|
||||
(puthash link backward-links org-roam-backward-links-cache)))
|
||||
;; Clean out forward links
|
||||
(remhash file org-roam-forward-links-cache))
|
||||
;; Step 2: Remove from the title cache
|
||||
(remhash file org-roam-titles-cache)))
|
||||
|
||||
(defun org-roam--update-cache-title (buffer)
|
||||
"Inserts the `TITLE' of file in buffer into the cache."
|
||||
(when-let ((title (org-roam--extract-title buffer)))
|
||||
(puthash (file-truename (buffer-file-name buffer))
|
||||
(defun org-roam--update-cache-title ()
|
||||
"Insert the title of the current buffer into the cache."
|
||||
(when-let ((title (org-roam--extract-title)))
|
||||
(puthash (file-truename (buffer-file-name (current-buffer)))
|
||||
title
|
||||
org-roam-titles-cache)))
|
||||
|
||||
(defun org-roam--update-cache ()
|
||||
"Update org-roam caches for the current buffer file."
|
||||
(save-excursion
|
||||
(org-roam--clear-cache-for-buffer (current-buffer))
|
||||
(org-roam--clear-cache)
|
||||
;; Insert into title cache
|
||||
(org-roam--update-cache-title (current-buffer))
|
||||
(org-roam--update-cache-title)
|
||||
;; Insert new items
|
||||
(let ((items (org-roam--parse-content)))
|
||||
(dolist (item items)
|
||||
(org-roam--insert-item item)))
|
||||
(org-roam--insert-item
|
||||
item
|
||||
:forward org-roam-forward-links-cache
|
||||
:backward org-roam-backward-links-cache)))
|
||||
;; Rerender buffer
|
||||
(org-roam--maybe-update-buffer :redisplay t)))
|
||||
|
||||
@ -516,27 +387,10 @@ This is equivalent to removing the node from the graph."
|
||||
(org-roam--new-file-named (format-time-string "%Y-%m-%d" time))))
|
||||
|
||||
;;; Org-roam buffer updates
|
||||
(defun org-roam--extract-title (buffer)
|
||||
"Extract the title from `BUFFER'."
|
||||
(with-current-buffer buffer
|
||||
(org-element-map
|
||||
(org-element-parse-buffer)
|
||||
'keyword
|
||||
(lambda (kw)
|
||||
(when (string= (org-element-property :key kw) "TITLE")
|
||||
(org-element-property :value kw)))
|
||||
:first-match t)))
|
||||
|
||||
(defun org-roam--extract-file-title (file)
|
||||
"Extract the title from `FILE'."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(org-roam--extract-title (current-buffer))))
|
||||
|
||||
(defun org-roam-update (file-path)
|
||||
"Show the backlinks for given org file for file at `FILE-PATH'."
|
||||
(org-roam--ensure-cache-built)
|
||||
(let ((buffer-title (org-roam--get-title-or-id file-path)))
|
||||
(let ((buffer-title (org-roam--get-title-or-slug file-path)))
|
||||
(with-current-buffer org-roam-buffer
|
||||
(let ((inhibit-read-only t))
|
||||
(erase-buffer)
|
||||
@ -553,7 +407,7 @@ This is equivalent to removing the node from the graph."
|
||||
(maphash (lambda (file-from contents)
|
||||
(insert (format "** [[file:%s][%s]]\n"
|
||||
file-from
|
||||
(org-roam--get-title-or-id file-from)))
|
||||
(org-roam--get-title-or-slug file-from)))
|
||||
(dolist (content contents)
|
||||
(insert (concat (propertize (s-trim (s-replace "\n" " " content))
|
||||
'font-lock-face 'org-block)
|
||||
@ -586,11 +440,11 @@ Valid states are 'visible, 'exists and 'none."
|
||||
(enlarge-window-horizontally (- w (window-width))))))))
|
||||
|
||||
(defun org-roam--setup-buffer ()
|
||||
"Setup the `org-roam' buffer at the `org-roam-position'."
|
||||
"Setup the `org-roam' buffer at the `org-roam-buffer-position'."
|
||||
(let ((window (get-buffer-window)))
|
||||
(-> (get-buffer-create org-roam-buffer)
|
||||
(display-buffer-in-side-window
|
||||
`((side . ,org-roam-position)))
|
||||
`((side . ,org-roam-buffer-position)))
|
||||
(select-window))
|
||||
(org-roam--set-width
|
||||
(round (* (frame-width)
|
||||
@ -654,19 +508,17 @@ This needs to be quick/infrequent, because this is run at
|
||||
(org-roam--ensure-cache-built)
|
||||
(with-temp-buffer
|
||||
(insert "digraph {\n")
|
||||
(mapcar (lambda (file)
|
||||
(insert
|
||||
(format " \"%s\" [URL=\"roam://%s\"];\n"
|
||||
(org-roam--get-id file)
|
||||
file)))
|
||||
(org-roam--find-all-files))
|
||||
(dolist (file (org-roam--find-all-files))
|
||||
(insert
|
||||
(format " \"%s\" [URL=\"roam://%s\"];\n"
|
||||
(org-roam--get-title-or-slug file)
|
||||
file)))
|
||||
(maphash
|
||||
(lambda (from-link to-links)
|
||||
(dolist (to-link to-links)
|
||||
(insert (format " \"%s\" -> \"%s\";\n"
|
||||
(org-roam--get-id from-link)
|
||||
(org-roam--get-id to-link))))
|
||||
)
|
||||
(org-roam--get-title-or-slug from-link)
|
||||
(org-roam--get-title-or-slug to-link)))))
|
||||
org-roam-forward-links-cache)
|
||||
(insert "}")
|
||||
(buffer-string)))
|
||||
@ -687,7 +539,6 @@ This needs to be quick/infrequent, because this is run at
|
||||
(call-process org-roam-graphviz-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph)
|
||||
(call-process org-roam-graph-viewer nil 0 nil temp-graph)))
|
||||
|
||||
|
||||
(provide 'org-roam)
|
||||
|
||||
;;; org-roam.el ends here
|
||||
|
@ -5,6 +5,5 @@ pkgs.mkShell {
|
||||
name = "docs";
|
||||
buildInput = with pkgs; [
|
||||
mkdocs
|
||||
python3Packages.alabaster
|
||||
];
|
||||
}
|
||||
|
35
tests/test-org-roam.el
Normal file
35
tests/test-org-roam.el
Normal file
@ -0,0 +1,35 @@
|
||||
;;; test-org-roam.el --- Tests for org-roam -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2020 Jethro Kuan
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; Package-Requires: ((buttercup))
|
||||
|
||||
;; This program is free software; you can redistribute it and/or modify
|
||||
;; it under the terms of the GNU General Public License as published by
|
||||
;; the Free Software Foundation, either version 3 of the License, or
|
||||
;; (at your option) any later version.
|
||||
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
;;;; Requirements
|
||||
|
||||
(require 'buttercup)
|
||||
(require 'org-roam)
|
||||
|
||||
;;; Tests
|
||||
(describe "Org-roam cache"
|
||||
(it "Mock Test"
|
||||
(expect t :to-be t)))
|
Reference in New Issue
Block a user