Docker Compose (recommended)

First, create a folder notegraf. Then, within that folder, create a docker-compose.yml file.

notegraf
└── notegraf_config.yml
# docker-compose.yml
version: '3'
services:
  db:
    image: postgres:14
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: notegraf
      LANG: C.UTF-8
    volumes:
      - dbdata:/var/lib/postgresql/data
  notegraf:
    image: ghcr.io/caizixian/notegraf:master
    restart: always
    depends_on:
      - "db"
    ports:
      - "8000:8000"
    environment:
      NOTEGRAF_HOST: "0.0.0.0"
      NOTEGRAF_PORT: 8000
      NOTEGRAF_NOTESTORETYPE: "PostgreSQL"
      NOTEGRAF_DATABASE_HOST: "db"
      NOTEGRAF_DATABASE_PORT: 5432
      NOTEGRAF_DATABASE_USERNAME: postgres
      NOTEGRAF_DATABASE_PASSWORD: password
      NOTEGRAF_DATABASE_NAME: notegraf
      NOTEGRAF_DEBUG: false
volumes:
  dbdata:

Within the folder, run docker-compose up -d. Your Notegraf instance should be up and running. Open http://localhost:8000 in your browser and see for yourself.

To update Notegraf, run docker pull ghcr.io/caizixian/notegraf:master and run docker-compose up -d again.

Developer Guide

Prerequisite

  • Rust toolchain installed via https://rustup.rs/. Tested with 1.62.0.
  • Node.js. Tested with 16.x LTS and 18.x.
  • PostgreSQL. Tested with 14. On macOS with Homebrew installed, simply brew install postgresql, and follow the instructions in the installation caveats.

Build from Source

git clone https://github.com/caizixian/notegraf.git
cd notegraf/notegraf-web
npm install
cargo build

Setup Development Server

First, you need to create a database, e.g., via createdb notegraf.

Then, under notegraf/notegraf-web, create the following two files.

notegraf
├── ...
└── notegraf-web
    ├── ...
    ├── .proxyrc.js
    └── configuration.yml
// .proxyrc.js
const {createProxyMiddleware} = require("http-proxy-middleware");

module.exports = function (app) {
    app.use(
        createProxyMiddleware({
            // localhost on macOS can also resolve to ::1
            // python3 -c 'import socket; print(socket.getaddrinfo("localhost", 8000))'
            // https://stackoverflow.com/questions/15227154/inexplicable-node-js-http-throwing-connect-econnrefused-ipv6
            target: "http://localhost:8000/",
            pathFilter: ["/**", "!/", "!**/*.html", "!**/*.js", "!*.css", "!**/*.css", "!**/*.map", "!**/*.ttf", "!**/*.woff", "!**/*.woff2", "!**/*.svg", "!**/*.png"],
        })
    );
};
# configuration.yml
port: 8000
notestoretype: "PostgreSQL"
debug: true
database:
  host: localhost
  port: 5432
  name: notegraf

Finally, open two terminal windows. In the first window, run cargo run under notegraf/notegraf-web, and in the other window, run npm start. Your browser should automatically navigate to http://localhost:1234.

Project Structure

The repo is set up as a cargo workspace with two crates.

notegraf
├── ...
├── notegraf     <- Notegraf core data types and persistence logics
└── notegraf-web <- Notegraf HTTP frontend and web UI

Pre-commit

cargo check && cargo test && cargo clippy && cargo fmt.

Search Syntax

A search query contains zero or more search terms, separated by spaces.

If any positive lexeme term is specified, results are ordered by relevance for the PostgreSQL backend. If no positive lexeme term is specified, results are ordered by their creation time (newer notes come first) regardless the backend. In any other case, the order is unspecified.

If no positive lexeme term is specified, results are limited to 10 notes by default, unless a !limit=<integer> modifier is used.

Lexeme Terms

  • Positive lexeme term: a plain word, such as token, will be searched against the title and the note body.
  • Negative lexeme term: prefix a word with - to exclude the word, such as -exclude.

Tag Terms

  • Positive tag term: a hashtag, such as #token.
  • Negative tag term: prefix a hashtag with - to exclude the tag, such as -#exclude.

Modifier Terms

  • !notag: match notes with no tags.
  • !orphan: match notes that have no previous note, no parent note (i.e., not a branch of another note), and not referenced by other notes.
  • !limit=<integer>: control the number of notes returned in the result to be <integer>.
  • !nolimit: return all notes that match. This takes precedence over !limit=<integer>.

Changelog

Unreleased

Added

Changed

Deprecated

Removed

Fixed

Security

v0.1.1 (2022-09-12)

Changed

  • [Web UI] "Hyperlinks" in the note title and the backlinks/branches are now real hyperlinks, rather than clickable texts. (#206)
  • [Web UI] Any occurrence of <URL origin>/note/ in the note body will be treated as cross-links, and therefore be replaced by notegraf:/note/. (#206)

Fixed

  • [Web UI] When showing note titles for branches or backlinks, the (transitive) suffix (indicating that the title shown is actually the title of the closest ancestor) will be placed on the same line as the title. (#206)
  • [Web UI] When a new editing session (editing an existing note, creating a new note, etc.) is created without user interaction, the current page is replaced by, instead of being navigated away from, the editor UI. This is so that when a user navigate back to the previous page, it expectedly shows the original page where the user clicked a button. (#206)
  • [Web UI] When creating a task with a link (such as - [x] [link](https://example.com)) in Markdown lists, the checkbox is now aligned properly with the link. (#227)

v0.1.0 (2022-08-30)

Initial release.