In January through April of 2015, I attempted to fill in a gap which I thought was missing in the area of digital note-taking. I have recipes I want to remember, general to-do lists, packing lists for camping and hunting which could come in handy in the future, a wish list, work notes, journals and more. Although there are several note-taking applications out there, I wanted something a little different than what they all offered. Here is my list of requirements:
- a way that I can edit files in my favorite text editor locally without an internet connection
- these notes will be taken in markdown syntax so they can be beautified into html
- these notes can be organized in an arbitrarily complex structure of directories
- at a time of my choosing, when connected to the internet, I can push the changes to the cloud
- these pushed notes can be accessed in a read-only fashion through a browser
- the web app would allow a viewer to easily navigate the directory structure previously mentioned in order to find notes
- I'd prefer that these notes remain private once pushed to the cloud
I posted in reddit asking for suggestions that would meet those requirements. Some decent answers were given, but nothing I was very excited about using. This is when I decided to create something for myself using the rust language. Writing this program would be a useful exercise in familiarizing myself with the language, and, practically, it would be very useful in day-to-day note taking.
Design
I decided to use markdown as the note syntax (as mentioned above) and handlebars as the templating engine for building the read-only site. Also, for my case, I'm using git for version control and distributing the content of the site. To keep everything straight, I'll try to refer to pieces of this application like so:
- rust-notes - a rust project that compiles to a runnable binary which converts content into a content website - github
- content - a collection of styles, handlebars templates, notes, images, and directories which is used as input for rust-notes - github example
- content website - the output of rust-notes which contains css, javascript, and html which an be served statically by a web server (I use apache) - example output
rust-notes
This is the main part of this project and the part written in rust. It loads all the handlebars templates from content, then recursively navigates through all files in the notes/
directory in content. I think I've made a decent mechanism for converting each file encountered in notes/
to a web page. I've made a FileType
trait which can be implemented for any type of file and knows how to convert the input to some web asset. These get created by FileTypeFactory
s. I'll mention these further down. The FileType
trait can be seen here:
pub trait FileType {
fn get_url(&self, context: &::AppContext) -> String;
fn convert(&self, context: &::AppContext);
fn get_type_str(&self) -> &'static str;
}
When implementing these different FileType
s, the constructor usually takes the actual file in question, so it's within the context of that FileType
. get_url
returns the url which will be used for this file in question once it's converted for the content website. convert
does the physical conversion (creates html in a separate directory usually), and get_type_str
simply returns a string so this FileType
can be differentiated from others without knowing what actual type is being used.
Another trait I've created is FileTypeFactory
.
trait FileTypeFactory {
fn try_create(&self, path: &Path) -> Option<Box<FileType>>;
fn initialize(&self, app_context: &mut ::AppContext) -> Result<(), &'static str>;
}
Each file type I'm handling (markdown, directories, and unknown) must also implement one of these. initialize
allows all handlebars templates to be pre-loaded and other initialization code. try_create
is the factory part of the trait. It will return Some(Box<FileType>)
if the file existing at the input path
is of the correct type, or None
if it isn't. Using the try_create
method on all factories lets me avoid making a large clunky-looking if/elseif statement, since I farm out the logic to these factories. The code which receives a path
and outputs a Boxself.factories
is a list of Box<FileTypeFactory>
.
pub fn create_file_type<P: AsRef<Path>>(&self, path: P) -> Box<FileType> {
for factory in self.factories.iter() {
let result_maybe = factory.try_create(path.as_ref());
if result_maybe.is_some() {
return result_maybe.unwrap();
}
}
self.unknown_factory.try_create(path.as_ref()).unwrap()
}
The last line of this function will use the unknown file type since none of the others worked.
Handled File Types
At this point, rust-notes can convert markdown files (I started with calling these types 'notes' so you may see that sprinkled throughout code), directories, and unknown files (a catch-all).
directory
Each time a directory is encountered, a corresponding directory and index.html
file within that directory is created in the content website. These index.html
files use the template file located at layouts/dir.hbs
in content. Mine looks like this at the moment:
<div class="container">
<div class="row">
{{#each children}}
<div class="col-xs-6 col-sm-3 col-lg-2 dir-item">
<a href="{{url}}">
<span class="icon-{{file_type}} icon"></span>
<span>{{name}}</span>
</a>
</div>
{{/each}}
</div>
</div>
This list called children
you see in the above template is sorted with directories at first and then alphabetically. The file_type
property simply provides some javascript code information for getting the correct icon.
markdown
Markdown files are handled similarly to directories in that they also use a handlebars template. Only one item gets made in the content website and that is <markdown-file-name>.html
. The template used to create this html file is located at layouts/note.hbs
and mine looks like this:
<div class="container">
{{{content}}}
</div>
Quite simple! The non-trivial part of this conversion is using a rust markdown library (I'm using this) to generate html.
unknown
Every other type of file (images, videos, text files, etc.) are handled in this way: copying. The file simply gets copied from content over to the content website.
Extensibility
In the end, I think this program turned out decently extensible. In order to add a different file type handler, you'd need to implement FileType
, FileTypeFactory
and add the factory to the list of factories. Eventually I'd like to write a handler for images. Perhaps there are other handlers which would be nice to have for this website-generating program.
Output
I'm not much of a CSS or design wizard, but I did my best in styling the content website so it doesn't scar your eyes.
directory
markdown file