Welcome to Edna
Edna is a note taking app for developers and power users. A cross between Obsidian and Notational Velocity.
Keyboard shortcuts
Ctrl + P Open, create or delete a note
Ctrl + K
Ctrl + O
Alt + 0
Ctrl + Shift + P Command Palette
Ctrl + Shift + K
Ctrl + Shift + O
Ctrl + B Navigate to a block
Ctrl + H Open recent note (from history)
Alt + N Create a new scratch note
Ctrl + Enter Add new block below the current block
Alt + Enter Add new block before the current block
Ctrl + Shift + Enter Add new block at the end of the buffer
Alt + Shift + Enter Add new block at the start of the buffer
Ctrl + Alt + Enter Split the current block at cursor position
Ctrl + L Change block language
Ctrl + Down Goto next block
Ctrl + Up Goto previous block
Ctrl + A Select all text in a note block
Press again to select the whole buffer
Ctrl + Alt + Up/Down Add additional cursor above/below
Alt + Shift + F Format block content
Supports Go, JSON, JavaScript, HTML, CSS and Markdown
Ctrl + E Smart Code Run
Alt + Shift + R Run function with block content
Supports Go
Ctrl + F Search / replace within a note
Why Edna?
Notes and blocks
In Edna a note is divided into blocks. Each block has a type: markdown, plain text, math block, JavaScript code, Go code etc.
Use:
Ctrl + L
to assign a type for current blockCtrl + B
to navigate between blocksCtrl + A
andDelete
to delete block (select block test and delete)
Use keyboard shortcuts to create blocks, move between blocks.
No installation required
Edna is a web-based application but can store notes on disk, like a desktop app (when using Chrome or Edge).
You can use it offline (without network connection) and for desktop-like experince you can install a desktop shortcut.
Miminalist UI, lots of functionality
UI is optimized for writing. Editor uses most of the space.
To acess more functionality:
Ctrl + Shift + P
for command pallete- use keyboard shortcuts)
- context menu (right-click)
- top bar and status bar
Speed
Edna is optimized for speed of writing, creating new notes, switching between notes.
Like in Notational Velocity, you can switch between notes, create and delete notes in the same note switcher UI.
Press Ctrl + P
for note switcher and:
- switch between notes
- create new note
- delete a note
- assign a quick access
Alt + <n>
shortcut
Context menu
Right-click for context menu.
For native context menu do Ctrl + right-click
. This is useful when spell checking to correct mis-spellings.
Switch to recently opened note
Press Ctrl + E
to switch to a note from list of recently opened notes.
Last 9 recently opened notes can be opened with 0
... 9
shortcut.
To switch to previous note: press Ctrl + E
and Enter
.
Quick access shortcut
You can assign Alt + 1
to Alt + 9
for quickly accessing notes:
Ctrl + P
for note selector dialog- select a note in the list
- press
Alt + 1
toAlt + 9
to assign it as quick access shortcut
Default shortcuts are:
Alt + 1
: scratch noteAlt + 2
: daily journalAlt + 3
: inbox
Notes with quick access shortcut are shown at the top of note selector (Ctrl + P
).
Syntax highlighting of blocks
Blocks are syntax highlighted based on their type.
Formatting of blocks
You can format current block using:
Alt + Shift + F
keyboard shortcut- right-click and use context menu
Block / Format as ${type}
- press format icon in status bar (bottom right)
We support formatting of Go, JSON, JavaScript, HTML, CSS and Markdown blocks.
Multiple notes
Open another note
Ctrl + P
for note switcher- click on note to open
or:
- enter text to narrow down list of notes
up
/down
arrow keys to select a noteEnter
to open selected note
Create a new note
Ctrl + P
to open note switcher- type name of the note to create
Enter
to create if the name doesn't match any existing noteCtrl + Enter
to create if the name partially matches an existing note
Or: context menu: Create New note
.
Create a new scratch note
Alt + N
to create temporary scratch note. We'll pick a unique name scratch-<n>
Delete a note
Context menu: This Note / Delete
or:
Ctrl + P
for note switcher- select a note with arrow key or by typing a partial name match
Ctrl + Delete
orCtrl + Backspace
to delete selected note
A scratch
note cannot be deleted.
Rename a note
context menu: This Note / Rename
Quick access shortcut
You can assign Alt + 1
to Alt + 9
keyboard shortcuts for quickly opening up to 10 notes.
Ctrl + P
- select a note
- press
Alt + <n>
shortcut to re-assign it to selected note
Notes with assigned shortcut show up at the top of note switcher.
Open recent note
Press Ctrl + E
to open a note from history. You can press 0
to 9
to open one of the last 10 notes.
Default notes
At first run we create 3 default notes:
scratch
,Alt + 1
daily journal
,Alt + 2
inbox
,Alt + 3
You can delete them (except the scratch
note).
scratch
note is meant for temporary notes.
inbox
for storing things to process later e.g. links to YouTube videos to watch later or web pages to read later.
daily journal
is for daily notes. We auto-create a block for each day.
Storing notes on disk
By default notes are stored in the browser (localStorage).
If your browser supports file system access (currently Chrome and Edge) you can store notes on disk.
You can do one time export from localStorage to a directory on disk:
- context menu:
Notes storage / Move notes from browser to directory
- pick a directory on disk
- we save notes on disk as
${name}.edna.txt
files in chosen directory and delete notes in localStorage
You can have multiple directories with notes. Think of each directory with notes as a separate Workspace.
Use context menu Notes storage / Switch to notes in directory
to switch to notes in a different directory. If it's an empty directory, without existing notes, we'll create default scratch
note.
You can go back to storing notes in the browser with context menu Notes storage / Switch to browser (localStorage)
. Unlike going from browser => directory, it doesn't import the notes from directory.
Accessing notes on multiple computers
If you pick a directory managed by Dropbox or One Drive or Google Drive etc. then you'll be able to access notes on multiple computers.
On the first computer export notes from browser to disk:
- context menu:
Notes storage / Move notes from browser to directory
- pick a directory shared by Dropbox / One Drive / Google Drive etc.
On other computers:
- context menu:
Notes storage / Switch to notes in directory
- pick the same directory
Please note that that the last written version wins. If you switch really quickly between computers, before the directory with notes has been replicated, you might over-write previous content.
Encryption
When storing notes on disk, you can encrypt them with a password.
The password is only stored in your browser (in local storage). It never leaves your computer.
Encryption and decryption takes place on your computer.
If you lose the password, you'll lose access to your notes.
Don't lose the password.
Notes are encrypted with ChaCha20-Poly1305 algorithm via kiss-crypto library.
Picking good password
Good password is:
- long (short passwords can be cracked via brute force; long passwords cannot)
- easy to type
- easy to remember
We recommend passwords that are long, memorable phrases.
Example: Blue bear attacked a tiny dog
.
Such passwords are easier to type and remember than typical "8a$7y!glo" passwords.
Running code
Run code blocks
If current block is Go or JavaScript block, you can run it:
Ctrl + E
keyboard shortcut (Smart Run
)- context menu:
Run
/Run <language> block
orSmart Run
- command palette:
Block: Run <language> block
Run
button in status bar (bottom right)
The output of execution will be shown in a new block created below the executed block.
For Go:
- code block must be a valid, complete Go program with
main
function - we capture and show stdout and sterr output
- we have the same execution capability as https://tools.arslexis.io/goplayground/
A starting point:
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
For JavaScript:
- code is executed in the browser with
eval()
- the output is the value of last expression
- don't run untrusted code
Examples of JavaScript code and its output.
"foo";
Last expression is string "foo"
so the output will be foo
.
let a = [1, 2, 3];
There is no last expression so the output will be undefined
let a = [1, 2, 3];
a;
Last expression is an array a
so output is 1,2,3
.
let a = [1, 2, 3];
JSON.stringify(a);
Last expression is an array formatted as json so output is [1,2,3]
.
We also capture console.log()
, console.warn()
, console.debug()
, console.error()
calls.
console.log("hello");
Output is:
undefined
console output:
hello
undefined
because there was no last expressionconsole output:
to indicate those are capture console.log callshello
from console.log calls in your code
async function main() {
return "foo";
}
main();
The last expression is return value of main()
function, so output is foo
.
We handle async functions.
Run JavaScript functions over content
You can run JavaScript functions with the content of a current block or a selection.
The function gets the content as argument, can traform it, and we show the output in a block below.
For example a function can sort the lines in a block, calculate md5 hash or transform it to upper case. The possibilities are literally limitless.
To run a JavasScript function with content of block:
- context menu:
Run
/Run function with block content
- command palette:
Run function with block content
- pick a function from the list
To run a JavasScript function with selection:
- context menu:
Run
/Run function with selection
- command palette:
Run function with selection
- pick a function from the list
If you want to see all built-in functions use:
- context menu:
Run
,Show built-in functions
- command palette:
Shw built-in functions
You can see what built-in functions are available, how they are implemented and use them as an example of how to write your own functions.
This functionality is inspired by https://boop.okat.best/ so our built-in functions come from Boop.
Write your own functions
You can write custom JavaScript functions that operate on text (content of the block or a selection), transform it in some way to create an output block.
For example you can write a function that sorts lines of text.
To write custom functions create a special edna: my functions
note:
- context menu:
Run
,Create your own functions
- command palette:
Create your own functions
Each JavaScript block is a function.
The code must be implemented as function main(input) { ... your code }
.
We use the same format as https://boop.okat.best/.
input
is an object:
{
text: string,
fullText: string,
postInfo: (message: string) => void,
postError: (message: string) => void,
}
To return value you assign it to input.text
and input.fullText
. postInfo()
and postError()
functions show a notification message.
We show input.text
or input.fullText
as output block if, after execution, it differs from the original value.
Why text
and fullText
? I have no idea, I just copied Boop and that's how their API works.
In my implementation text
and fullText
are the same.
You also need to provide metadata at the top in the format:
/**
{
"api":1,
"name":"Add Slashes",
"description":"Escapes your text.",
"author":"Ivan",
"icon":"quote",
"tags":"add,slashes,escape"
}
**/
Don't get cute and reformat it any way or change /**
and **/
to something else. Best to copy it and change values.
Currently the important fields are name
, and description
because they are shown in function selection dialog.
In future I might add more api
versions, but at the moment there's just 1.
Here's the simplest function that returns foo
as a result:
/**
{
"api":1,
"name":"Show foo",
"description":"Returns string 'foo'",
"author":"Chris",
"icon":"quote",
"tags":""
}
**/
async function main(input) {
input.text = "foo";
}
As you can see, we support async
functions.
Using external libraries
You can use any JavaScript library available via https://esm.sh or https://www.jsdelivr.com/ (or similar).
Here's an example function that uses camelCase
functio from lodash
package imported from https://esm.sh
async function main(input) {
let lodash = (await import("https://esm.sh/[email protected]")).default;
input.text = lodash.camelCase(input.text);
}
Debugging tip: sometimes module exports functions direct, sometimes as default
.
To figure this out for a library, in browser's dev tools console do:
let m = (await import("https://esm.sh/[email protected]"))
then inspect the m
object in console to see available functions.
Share your JavaScript functions with others
Share your functions with other via https://github.com/kjk/edna/discussions/categories/share-javascript-functions
Smart Run
Ctrl + E
is a shortcut for smart run, which has the following logic:
- if there is selection, it does
Run functions with selection
command - if current block is runnable (JavaScript or Go) it'll run the block as code (command
Block: Run <language> block
) - otherwise it does
Run function with block content
command
Use Alt + Shift + R
to force Run function with block content
command.
Exporting notes to a .zip file
Edna files are just text files with .edna.txt
extension.
You can export all the notes to a .zip file.
Context menu: Export notes to zip file
menu.
We pack all the notes into a .zip file and initiate auto-download as edna.notes.export-YYYY-MM-DD.zip
file to browser's downloads directory.
You can then e.g. restore the notes by unzipping it to a directory and opening that directory in Edna with Notes storage
/ Switch to notes in a directory
context menu.
Lists with TODO items
In Markdown blocks, lists with [x] and [ ] are rendered as checkboxes:
- [ ] Try out Edna
- [ ] Do laundry
// code blocks
// Edna is great for storing code snippets
// this is a javascript block
// change type of block with `Ctrl + L`
// or `Block: Change Language` command in command palette (`Ctrl + P`)
// you can format it with `Alt + Shift + F`
let x = 5
console.log("x is", x)
Privacy and security
Your notes are private and secure.
Notes are stored on your computer: in the browser (local storage) or on disk.
For additional security, you can encrypt notes with a password.
The code is open source so you can audit it.
No lock in
The notes are stored in plain text files on disk (or in localStorage under note:${name}
key)
Blocks are marked with \n∞∞∞${type}\n
e.g. \n∞∞∞markdown\n
marks the beginning of markdown block.
You can edit the notes in any text editor (just be mindful of the above block markers).
You can back them up, store in git repositories, write scripts to process them.
They are not locked in a proprietary Edna format.
How I use Edna
Edna is flexible and you should find your own way of using it.
I use Edna daily::
- I use
scratch
note for temporary notes - I use
daily journal
for keeping track of what I do - I have
todo
note for keeping track of short term todos - I have a note for each project I work on
- I have a note for each programming language / technology I use. I keep code snippets and notes about it
- I have
ideas
note for jotting down ideas - I have
investing
note for keeping track of various stock investment ideas
Open source
Edna is open source: https://github.com/kjk/edna
To report a bug or request a feature: https://github.com/kjk/edna/issues
Contact
You can contact me via https://blog.kowalczyk.info/contactme
You can find more software by me on https://arslexis.io
Credits
Edna is a fork of Heynote with the following differences:
- web first (no desktop apps)
- multiple notes
- ability to access notes on multiple devices by storing them on folder managed by Dropbox / One Drive / Google Drive
There's a spirit of Notational Velocity and Simplenote in Edna in how it allows quickly creating notes and switching between them.
Edna is built on CodeMirror, Svelte 5, Math.js, Prettier.