#!/usr/bin/env bash # note (chatgpt 18.4.26) # needs: fzf, rg (ripgrep) # likes: batcat (bat) set -euo pipefail # config dirs and files NOTES_DIR="${NOTES_DIR:-$HOME/notes}" INBOX="$NOTES_DIR/flow.md" EDITOR="${EDITOR:-hx}" # check for required for cmd in fzf rg; do if ! command -v "$cmd" &>/dev/null; then echo "Error: $cmd is required but not installed" >&2 exit 1 fi done # make notes root dir mkdir -p "$NOTES_DIR" today() { date +"%Y-%m-%d" } slugify() { echo "$1" \ | tr '[:upper:]' '[:lower:]' \ | tr -cd 'a-z0-9 -' \ | tr ' ' '-' \ | sed 's/-\+/-/g' } latest_note() { find "$NOTES_DIR" -maxdepth 1 -type f -name '*.md' \ -printf '%T@ %p\n' \ | sort -nr \ | head -n1 \ | cut -d' ' -f2- } # open_note_fzf() { # local file # file=$(find "$NOTES_DIR" -maxdepth 1 -type f -name '*.md' \ # | sort -r \ # | fzf --prompt="notes> " \ # --preview="batcat --style=minimal --color=always {} 2>/dev/null || cat {}" \ # --height=80% \ # --reverse) # [[ -z "$file" ]] && return # "$EDITOR" "$file" # } open_note_fzf() { local file file=$( find "$NOTES_DIR" -maxdepth 1 -type f -name '*.md' \ -printf '%T@ %p\n' \ | sort -nr \ | cut -d' ' -f2- \ | fzf --prompt="notes> " \ --preview="batcat --style=minimal --color=always {} 2>/dev/null || cat {}" \ --height=80% \ --reverse ) [[ -z "$file" ]] && return "$EDITOR" "$file" } # search_notes() { # [[ -z "${1:-}" ]] && return # local file # file=$(grep -RIl \ # --exclude-dir=.git \ # --exclude-dir=node_modules \ # --exclude='*.swp' \ # -- "$1" "$NOTES_DIR" \ # | fzf --prompt="search> " \ # --preview="batcat --style=minimal --color=always {} 2>/dev/null || cat {}" \ # --height=80% \ # --reverse) # [[ -z "$file" ]] && return # "$EDITOR" "$file" # } # search_notes() { # local query="${1:-}" # rg -m1 --no-heading --line-number --color=always "" "$NOTES_DIR" \ # | fzf --ansi \ # --delimiter : \ # --prompt="search> " \ # --preview='batcat --style=numbers --color=always {1} --highlight-line {2} 2>/dev/null || cat {1}' \ # --bind "change:reload:rg --no-heading --line-number --color=always {q} '$NOTES_DIR' || true" \ # --phony \ # --query "$query" --reverse # [[ -z "$file" ]] && return # "$EDITOR" "$file" # } search_notes() { local selection file line selection=$( rg -m1 --no-heading --line-number --color=always "" "$NOTES_DIR" \ | fzf --ansi \ --delimiter : \ --prompt="search> " \ --preview='batcat --style=numbers --color=always {1} --highlight-line {2} 2>/dev/null || cat {1}' \ --bind "change:reload:rg -m1 --no-heading --line-number --color=always {q} '$NOTES_DIR' || true" \ --phony --reverse ) [[ -z "$selection" ]] && return file="${selection%%:*}" line="${selection#*:}" line="${line%%:*}" "$EDITOR" "$file" "+$line" } new_note() { local title file slug title="${1:-note}" slug="$(slugify "$title")" file="$NOTES_DIR/$(today)-$slug.md" if [[ ! -f "$file" ]]; then cat > "$file" <> "$INBOX" } usage() { echo "Usage:" echo " note + | new \"title\"" echo " note l | list" echo " note | open [query]" echo " note c | cat [query]" echo " note g | grep | s " echo " note \"quick note\" (flow.md)" echo " note h | -h | --help" echo " (pipe stands for or)" echo "Storage: $NOTES_DIR" } cmd="${1:-}" case "$cmd" in new|+) shift new_note "$@" ;; list|l) list_notes ;; ""|open) shift || true open_note "$@" ;; cat|c) shift || true cat_note "$@" ;; grep|search|g|s) shift search_notes "$@" ;; h|-h|--help) usage ;; *) inbox_add "$@" ;; esac