Welcome to Edna
Edna is a note taking app with super powers.
Integrated AI chat, blocks, markdown, syntax highlighting for 40+ languages (JavaScript, Python, C++, Go, Java, C#, Ruby and more), command palette, programmability.
Keyboard shortcuts
Ctrl + K Open, create or delete a note
Ctrl + P
Ctrl + O
Ctrl + H Open recent note (from history)
Ctrl + Shift + P Command Palette
Ctrl + Shift + K
Ctrl + Shift + O
Ctrl + L Change block language
Ctrl + B Navigate to a block
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 + Down Goto next block
Ctrl + Up Goto previous block
Ctrl + Shift + [ Fold code
Ctrl + Shift + ] Unfold code
Ctrl + Alt + [ Fold block
Ctrl + Alt + ] Unfold block
Ctrl + Alt + . Toggle block fold
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
Ctrl + Shift + F Search in all notes
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 text and delete)
Use keyboard shortcuts to create blocks, move between blocks.
Navigating between blocks with Ctrl + B
:
Changing block language with Ctrl + L
:
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.
Lots of functionality, minimalist UI
UI is optimized for writing. Editor uses most of the space.
To acess more functionality:
Ctrl + Shift + K
for command palette- use keyboard shortcuts
- context menu (right-click)
- top navigation bar
- status bar
Command palette
Open command palette with Ctrl + Shift + K
:
Quickly access all the functionality, find commands with partial name match.
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 + K
for note switcher and:
- switch between notes
- create new note
- delete a note
- assign a quick access
Alt + <n>
shortcut
Note switcher with Ctrl + K
:
Multiple tabs
We support multiple tabs:
To open a note in a new tab, press Ctrl
when opening a note.
Minimize top navigation bar
You can minimize / maxmize top navigation bar by pressing an icon in top right corner:
When minimized, navigation bar is hidden by default and only shows when you move mouse or hover it over the bar.
It also uses only as much space as needed for tabs and UI elements, not the whole width of window.
The intent is to maximize amount of space dedicated to the editor.
Context menu
Press right mouse button for context menu.
Context menu with mouse right click:
For native context menu do Ctrl + right-click
. This is useful when spell checking to correct mis-spellings.
Alternatively mouse over menu icon in top right corner.
Quick access shortcut
You can assign Alt + 1
to Alt + 9
for quickly accessing notes:
Ctrl + K
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 + K
) and quick access UI (Ctrl + H
)
Open starred, recent notes
Use Ctrl + H
:
Alternatively mouse over ⏷ in top navigation bar:
Drop-down list has:
- notes that have quick access shortcut
- starred notes
- recently opened notes
To open last note: Ctrl + H
, Enter
.
When triggering the list with Ctrl + H
, last 10 recently opened notes get a shortcut 0
to 9
.
You can Ctrl + H
and then press 0
to 9
to re-open recent note just using the keyboard.
To open in new tab: hold Ctrl
key when clicking.
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 + K
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 + K
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>
Archive and unarchive note
To keep the note but put it aside, archive it.
Context menu: This note
/ Archive
.
Command Archive note
in command palette (Ctrl + Shift + K
).
Open note selector with Ctrl + K
, click archive icon:
To open archive note, click show archived
link in note selector:
Archived notes show at the top. You can unarchive with an icon or open the note and use context menu This note
/ Un-archive
or Un-archive note
command.
Delete a note
Context menu: This Note / Delete
or:
Ctrl + K
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
Rename current note
in command palette (Ctrl + Shift + K
).
Sidebar
Click icon in top left corner to show / hide sidebar:
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.
Search and replace
Press Ctrl + F
for search / replace UI:
We support:
- match case
- match whole word
- regular expression
Search across notes
To invoke full-text search in notes:
Ctrl + Shift + F
Find in notes
in Command Palette (Mod + Shift + K)
The search algorightm is:
- if you search for
foo,
it'll find all lines that havefoo
- if you search for
foo bar
, it'll find all lines that havefoo
ANDbar
- if you want exact search:
"foo bar"
Ask AI
Edna has a basic AI chat functionality. You can ask AI a question and get a response.
It's optimized for the following workflow:
- type your question in a block
- invoke
Ask AI
UI - pick AI model to which you'll send your query
- send the query to AI / LLM and get a response
- optionally insert the response as a new block
To invoke AI chat:
- context menu
Ask AI
Ask AI
command in command palette (Ctrl + Shift + K
)
The question is pre-populated with the content of selection or current block.
You can edit your question then click Ask AI
button to send the question and receive the response.
Once you receive the response, you can insert it as new block.
API keys
But first, you'll need to provide an API key.
We don't charge for AI but AI providers do.
We support:
- OpenAI for ChatGPT etc. Read how to get OpenAI key
- xAI for Grok. Read how to get xAI key
- OpenRouter for all models (including OpenAI / xAI models). Read how to get OpenRouter key
We recommend creating a unique API key for Edna.
If you provide both xAI/OpenAI API key and OpenRouter API key, we'll use xAI/OpenAI.
AI Model Picker
We support 169 AI models. You can pick the model using picker in upper right corner.
We don't charge for AI but AI providers do.
Different models have different prices charged by API providers.
They charge per usage, not as a subscription. If you don't use the API, you are not charged.
There's a fee for the size of your question (input tokens) and size of the response (output tokens).
Token is pretty much a word so think "number of words in the question or response".
We show the prices per million input tokens and million output tokens on the right side of the model picker.
Free models
Some OpenRouter models are free. They show 0 for the price. You can also type free in model picker to see free models.
You still need an OpenRouter API key and setup billing to use free models.
Getting OpenAI API key
To use OpenAI models you need to obtain API key from OpenAI.
You need to create an account and setup billing. OpenAI charges for usage of API calls so you don't pay if you don't use the API.
They charge per input tokens and output tokens. A token is more or less a word.
To make it easier to compare prices, we show the price for million input tokens and a million output tokens in AI model picker.
API keys are separate from ChatGPT subscription. Confusing, I know, but I didn't make the rules.
You can also use OpenRouter API key (they charge ~5% on top of OpenAI prices).
Getting xAI (Grok) API Key
To use xAI models you need to obtain API key from xAI, click API Keys
section in left sidebar
You need to create an account and setup billing. xAI charges for usage of API calls so you don't pay if you don't use the API.
They charge per input tokens and output tokens. A token is more or less a word.
To make it easier to compare prices, we show the price for million input tokens and a million output tokens in AI model picker.
API keys are separate from Grok subscription. Confusing, I know, but I didn't make the rules.
You can also use OpenRouter API key (they charge ~5% on top of xAI prices).
Getting OpenRouter API Key
All models can be used via OpenRouter. You need to obtain API key from OpenRouter.
You need to create an account and setup billing. OpenRouter charges for usage of API calls so you don't pay if you don't use the API.
They charge per input tokens and output tokens. A token is more or less a word.
To make it easier to compare prices, we show the price for million input tokens and a million output tokens in AI model picker.
OpenRouter provides a unified interface for accessing models from many providers.
They charge the provider's price plus around 5%.
OpenRouter provides free models but you still need to have an API key.
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 `Mod + L`
// or `Block: Change Language` command in command palette (`Mod + Shift + K`)
// 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.
Super-powers
What makes Edna different from other note taking applications?
Blocks
Notes can be sub-divided into blocks.
Each block has a type (markdown, text, JavaScript and 40+ programming languages) which dictates syntax highlighting.
Blocks can be folded so that you an focus on the block you're currently editing.
You can quickly navigate between blocks with Ctrl + B
.
Scratch note
Scratch note is one of 3 special notes.
It's meant for things that are not permanent.
Quickly switch to Scratch note with Alt + 1
, jot your temporary note, delete when no longer needed.
Lots of features yet minimalistic UI
Writing is Edna's core functionality so I strive to use maxium of screen space for the editor.
At the same time Edna is packed with functionality accessible via several paths:
- context menu
- command palette (
Ctrl + Shift + K
) - navigation bar at the top
- toggling sidebar
- menu drop-down
- help drop-down
- tabs
- open note trigger
- drop-down with frequently accessed notes (those with quick access shortcut, starred and recently opened)
- minimize / maximize navigation bar
- status bar at the bottom
- toggling spell-checking
- changing language of a block
- execute JavaScript code
- format block
Fast to use
I use Edna daily and efficiency is important to me.
Switching between notes, executing commands etc. are accessible via keyboard shortcuts or mouse.
Quick access shortcut
For the fastest way to switch between notes, you can assign quick access shortcut (Alt + <n>
) to notes you work on frequently.
Keyboard shortcuts
Lots of keyboard shortcuts.
Command Palette
Command palette allows quick access to all functionality (Ctrl + Shift + K
).
AI chat built in
You're already in the editor. It's silly to switch to a different app just to send a question to AI.
Ask AI chat UI pre-populates question from selection or current block and allows to insert the response as a new block.
We support virtually any AI provider.
We aim to support 80% of AI use cases, not replicate the advanced features of specilized AI apps.
Programmability
You can write and run JavaScript functions that take content, transform it and insert the result back into the note.
Daily journal
The intent of daily journal
note (Alt + 2
) is to record things you do. We auto-create a new markdown block for each day.
Inbox
The intent of inbox
note (Alt + 3
) is to jot things you want to process in short term future, like a link to an article you want to read or a link to a video you want to watch.
Encryption
You can encrypt notes with a password. Even Mossad agents won't be able to crack them.
How I use Edna
Edna is flexible and you will 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 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.