698 lines
23 KiB
Bash
Executable File
698 lines
23 KiB
Bash
Executable File
#!/bin/bash
|
|
# notestorm
|
|
# Description: Easily take and read notes from the CLI.
|
|
#
|
|
# Copyright 2019, F123 Consulting, <information@f123.org>
|
|
# Copyright 2019, Storm Dragon, <storm_dragon@linux-a11y.org>
|
|
#
|
|
# This is free software; you can redistribute it and/or modify it under the
|
|
# terms of the GNU General Public License as published by the Free
|
|
# Software Foundation; either version 3, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This software is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
# General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this package; see the file COPYING. If not, write to the Free
|
|
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
# 02110-1301, USA.
|
|
#
|
|
#--code--
|
|
|
|
# the gettext essentials
|
|
export TEXTDOMAIN=notestorm
|
|
export TEXTDOMAINDIR=/usr/share/locale
|
|
source gettext.sh
|
|
# Track modifications to notes
|
|
unset modified
|
|
|
|
|
|
# Functions section
|
|
|
|
check_dependencies() {
|
|
# Check for required dependencies
|
|
local missing_deps=()
|
|
|
|
# Critical dependencies
|
|
if ! command -v dialog &> /dev/null; then
|
|
missing_deps+=("dialog")
|
|
fi
|
|
|
|
if ! command -v markdown &> /dev/null; then
|
|
missing_deps+=("markdown (discount package)")
|
|
fi
|
|
|
|
# Check for at least one pager
|
|
if ! command -v w3m &> /dev/null && \
|
|
! command -v elinks &> /dev/null && \
|
|
! command -v lynx &> /dev/null; then
|
|
missing_deps+=("w3m, elinks, or lynx")
|
|
fi
|
|
|
|
# Optional but recommended
|
|
if ! command -v gpg &> /dev/null; then
|
|
echo "Warning: GPG not found. Note encryption will not be available." >&2
|
|
fi
|
|
|
|
if ! command -v git &> /dev/null; then
|
|
echo "Warning: Git not found. Git backup functionality will not be available." >&2
|
|
fi
|
|
|
|
if ! command -v zip &> /dev/null; then
|
|
echo "Warning: Zip not found. Zip backup functionality will not be available." >&2
|
|
fi
|
|
|
|
if ! command -v unzip &> /dev/null; then
|
|
echo "Warning: Unzip not found. Zip restore functionality will not be available." >&2
|
|
fi
|
|
|
|
# Report missing critical dependencies
|
|
if [[ ${#missing_deps[@]} -gt 0 ]]; then
|
|
echo "Error: Missing required dependencies:" >&2
|
|
for dep in "${missing_deps[@]}"; do
|
|
echo " - $dep" >&2
|
|
done
|
|
echo >&2
|
|
echo "Please install the missing dependencies and try again." >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
cleanup() {
|
|
if [[ ${#modified} -lt 1 ]]; then
|
|
exit 0
|
|
fi
|
|
if [[ ! -d "$xdgPath/notestorm/notes/.git" ]]; then
|
|
exit 0
|
|
fi
|
|
if [[ "$(yesno "Push changes to git?")" == "No" ]]; then
|
|
exit 0
|
|
fi
|
|
git -C "$xdgPath/notestorm/notes" add -A :/
|
|
git -C "$xdgPath/notestorm/notes" commit -m "$(gettext "Automatic push from notestorm, ${modified}.")"
|
|
git -C "$xdgPath/notestorm/notes" push
|
|
}
|
|
|
|
infobox() {
|
|
# Returns: None
|
|
# Shows the provided message on the screen with no buttons.
|
|
local continue
|
|
dialog --infobox "$*" 10 80
|
|
read -n1 -t $messageTimeout continue
|
|
}
|
|
|
|
inputbox() {
|
|
# Returns: text entered by the user
|
|
# Args 1, Instructions for box.
|
|
# args: 2 initial text (optional)
|
|
dialog --clear --backtitle "$(gettext "Enter text and press enter.")" \
|
|
--inputbox "$1" 0 0 "$2" --stdout
|
|
}
|
|
|
|
yesno() {
|
|
# Returns: Yes or No
|
|
# Args: Question to user.
|
|
# Called in if $(yesno) == "Yes"
|
|
# Or variable=$(yesno)
|
|
dialog --clear --backtitle "$(gettext "Press 'Enter' for \"yes\" or 'Escape' for \"no\".")" --yesno "$*" 10 80 --stdout
|
|
if [[ $? -eq 0 ]]; then
|
|
echo "Yes"
|
|
else
|
|
echo "No"
|
|
fi
|
|
}
|
|
|
|
menulist() {
|
|
# Args: Tag, description.
|
|
# returns: selected tag
|
|
dialog --backtitle "$(gettext "Use the up and down arrow keys to find the option you want, then press enter to select it.")" \
|
|
--clear \
|
|
--cancel-label "$(gettext "Exit")" \
|
|
--extra-button \
|
|
--extra-label "$(gettext "New")" \
|
|
--help-button \
|
|
--help-label "$(gettext "More")" \
|
|
--ok-label "$(gettext "View")" \
|
|
--no-tags \
|
|
--menu "$(gettext "Please select one")" 0 0 0 $@ --stdout
|
|
}
|
|
|
|
more_menu() {
|
|
# Options for the submenu go in the options array, see options+=
|
|
declare -a options
|
|
if [[ "${1##*.}" == "gpg" ]]; then
|
|
options+=("decrypt" "$(gettext "Decrypt")")
|
|
else
|
|
options=("encrypt" "$(gettext "Encrypt")")
|
|
fi
|
|
options+=(
|
|
"edit" "$(gettext "Edit")"
|
|
"delete" "$(gettext "Delete")")
|
|
local action="$(dialog --backtitle "$(gettext "Use the up and down arrow keys to find the option you want, then press enter to select it.")" \
|
|
--no-tags \
|
|
--menu "$(gettext "Please select one")" \
|
|
0 0 0 ${options[@]} --stdout)"
|
|
case "$action" in
|
|
"decrypt") decrypt_note "$1";;
|
|
"delete") delete_note "$1";;
|
|
"edit") edit_note "$1";;
|
|
"encrypt") encrypt_note "$1";;
|
|
*) return;;
|
|
esac
|
|
}
|
|
|
|
add_note() {
|
|
# Notes are named based on number of seconds since 1970.
|
|
# If the note exists it is incremented until the name is available.
|
|
local noteName=$(date '+%s')
|
|
while [ -f "${xdgPath}/notestorm/notes/${noteName}.md" ]; do
|
|
((noteName++))
|
|
done
|
|
$editor "${xdgPath}/notestorm/notes/${noteName}.md"
|
|
newName="$(head -n 1 "${xdgPath}/notestorm/notes/${noteName}.md")"
|
|
newName="${newName//[[:space:]]/_}"
|
|
if [[ ! -e "${xdgPath}/notestorm/notes/${newName}.md" ]]; then
|
|
mv "${xdgPath}/notestorm/notes/${noteName}.md" "${xdgPath}/notestorm/notes/${newName}.md"
|
|
noteName="${newName}"
|
|
fi
|
|
if [ -f "${xdgPath}/notestorm/notes/${noteName}.md" ]; then
|
|
modified="note added"
|
|
infobox "Note added."
|
|
else
|
|
infobox "Note canceled."
|
|
fi
|
|
}
|
|
|
|
decrypt_note() {
|
|
#returns the note to its unencrypted state.
|
|
local output_file="${1%.gpg}"
|
|
|
|
# Check if output file already exists
|
|
if [[ -f "$output_file" ]]; then
|
|
if [[ "$(yesno "$(gettext "File already exists. Overwrite?")")" != "Yes" ]]; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Attempt decryption with better error handling
|
|
local gpg_error
|
|
if gpg_error=$(gpg -o "$output_file" -d "$1" 2>&1); then
|
|
rm -f "$1"
|
|
infobox "$(gettext "Note decrypted and saved as: ") \"$output_file\""
|
|
modified="note decrypted"
|
|
return
|
|
fi
|
|
|
|
# Provide more specific error messages
|
|
case "$gpg_error" in
|
|
*"Bad passphrase"*|*"bad passphrase"*)
|
|
infobox "$(gettext "Decryption failed: Incorrect passphrase.")"
|
|
;;
|
|
*"No such file"*)
|
|
infobox "$(gettext "Decryption failed: File not found.")"
|
|
;;
|
|
*)
|
|
infobox "$(gettext "Decryption failed: ") $gpg_error"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
delete_note() {
|
|
local text="$(head -1 "$1")"
|
|
if [[ "${1##*.}" == "gpg" ]]; then
|
|
local answer="$(yesno "$(gettext "Really delete note named:") \"${1##*/}\"?")"
|
|
else
|
|
local answer="$(yesno "$(gettext "Really delete note starting with text:") \"$text\"?")"
|
|
fi
|
|
if [ "$answer" == "Yes" ]; then
|
|
rm -f "$1"
|
|
infobox "$(gettext "Note deleted.")"
|
|
if [[ -d "$xdgPath/notestorm/notes/.git" ]]; then
|
|
git -C "$xdgPath/notestorm/notes" rm -f "$1" &&
|
|
modified="note deleted"
|
|
fi
|
|
else
|
|
infobox "$(gettext "Action canceled.")"
|
|
fi
|
|
}
|
|
|
|
display_note() {
|
|
if [[ "$1" =~ ^[0-9]*$ ]]; then
|
|
mapfile -t notes < <(find "$xdgPath/notestorm/notes" -type f -iname '*.md')
|
|
if [[ $1 -ge "${#notes[@]}" ]]; then
|
|
gettext "The requested note could not be found. Try using -l to get a list."
|
|
echo " ($1)"
|
|
exit 1
|
|
fi
|
|
cat "${notes[$1]}"
|
|
exit 0
|
|
fi
|
|
if [[ "${1##*.}" == "gpg" ]]; then
|
|
# Handle GPG decryption errors gracefully
|
|
if ! gpg -d "$1" 2>/dev/null | markdown | "${pager[@]}"; then
|
|
infobox "$(gettext "Failed to decrypt or display note. Check your passphrase and try again.")"
|
|
return 1
|
|
fi
|
|
else
|
|
if ! markdown "$1" | "${pager[@]}"; then
|
|
infobox "$(gettext "Failed to display note. Check if file exists and markdown is installed.")"
|
|
return 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
edit_note() {
|
|
local oldMd5="$(md5sum "$1")"
|
|
$editor "$1"
|
|
local newMd5="$(md5sum "$1")"
|
|
if [ "$oldMd5" != "$newMd5" ]; then
|
|
infobox "$(gettext "Changes saved.")"
|
|
modified="note updated"
|
|
else
|
|
infobox "$(gettext "Changes discarded.")"
|
|
fi
|
|
}
|
|
|
|
encrypt_note() {
|
|
# function requires exactly 1 argument.
|
|
if [[ $# -ne 1 ]]; then
|
|
return
|
|
fi
|
|
|
|
# Validate input file exists
|
|
if [[ ! -f "$1" ]]; then
|
|
infobox "$(gettext "Error: Source file does not exist.")"
|
|
return
|
|
fi
|
|
|
|
# Get a human readable name for the new note.
|
|
local noteName="$(inputbox "$(gettext "Please enter a descriptive name for the encrypted note:")")"
|
|
if [[ -z "$noteName" ]]; then
|
|
# No name supplied, so return
|
|
return
|
|
fi
|
|
|
|
# Sanitize filename - remove dangerous characters
|
|
noteName="${noteName//[^a-zA-Z0-9._-]/_}"
|
|
local output_file="${1%/*}/${noteName%.md*}.md.gpg"
|
|
|
|
# Check if output file already exists
|
|
if [[ -f "$output_file" ]]; then
|
|
if [[ "$(yesno "$(gettext "Encrypted file already exists. Overwrite?")")" != "Yes" ]]; then
|
|
return
|
|
fi
|
|
fi
|
|
|
|
# Encrypt the given file and if successful, remove the original.
|
|
local gpg_error
|
|
if gpg_error=$(gpg -o "$output_file" -c "$1" 2>&1); then
|
|
rm -f "$1"
|
|
infobox "$(gettext "Note encrypted as: ") \"$output_file\""
|
|
modified="encrypted note added"
|
|
return
|
|
fi
|
|
|
|
# Provide specific error message
|
|
infobox "$(gettext "Encryption failed: ") $gpg_error"
|
|
}
|
|
|
|
list_notes() {
|
|
# Return a numbered list of unencrypted notes.
|
|
mapfile -t notes < <(find "$xdgPath/notestorm/notes" -type f -iname '*.md')
|
|
for i in "${!notes[@]}" ; do
|
|
echo "$i: ${notes[i]##*/}"
|
|
done
|
|
}
|
|
|
|
initialize_git() {
|
|
local message
|
|
if [[ -d "$xdgPath/notestorm/notes/.git" ]]; then
|
|
message="$(gettext "A git configuration already exists.")"
|
|
infobox "$message"
|
|
exit 1
|
|
fi
|
|
message="$(gettext "Please enter the url to your git repository for notes.")"
|
|
local gitURL="$(inputbox "$message")"
|
|
|
|
# Enhanced URL validation
|
|
if [[ ${#gitURL} -lt 3 ]]; then
|
|
message="$(gettext "Invalid URL detected, exiting.")"
|
|
infobox "$message"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for valid git URL patterns and sanitize
|
|
if [[ ! "$gitURL" =~ ^(https?://|git@|ssh://|file://|/) ]]; then
|
|
message="$(gettext "Invalid git URL format. Must start with https://, git@, ssh://, file://, or / for local paths.")"
|
|
infobox "$message"
|
|
exit 1
|
|
fi
|
|
|
|
# Prevent command injection in URL
|
|
if [[ "$gitURL" == *";"* ]] || [[ "$gitURL" == *'`'* ]] || [[ "$gitURL" == *'$'* ]] || [[ "$gitURL" == *'('* ]] || [[ "$gitURL" == *')'* ]]; then
|
|
message="$(gettext "Invalid characters detected in URL.")"
|
|
infobox "$message"
|
|
exit 1
|
|
fi
|
|
{ git -C "$xdgPath/notestorm/notes" init
|
|
git -C "$xdgPath/notestorm/notes" remote add origin "$gitURL"
|
|
git -C "$xdgPath/notestorm/notes" add -A
|
|
git -C "$xdgPath/notestorm/notes" commit -m "Initial commit added by notestorm"
|
|
git -C "$xdgPath/notestorm/notes" push -u origin master ; } | dialog --progressbox "$(gettext "Setting up git...")" -1 -1
|
|
exit 0
|
|
}
|
|
|
|
original_note() {
|
|
# If no notes are present this will try to copy the README.md file into the notes directory so the menu won't fail.
|
|
mapfile -t notes < <(find "$xdgPath/notestorm/notes" -type f -iname '*.md' -o -iname '*.gpg')
|
|
if [[ "${#notes[@]}" -gt 0 ]]; then
|
|
return
|
|
fi
|
|
if [[ -f "README.md" ]]; then
|
|
cp "README.md" "${xdgPath}/notestorm/notes/README.md"
|
|
elif [[ -f "/usr/share/doc/notestorm/README.md" ]]; then
|
|
cp "/usr/share/doc/notestorm/README.md" "${xdgPath}/notestorm/notes/README.md"
|
|
else
|
|
echo "The README file is missing. Pleae visit the notestorm git page for usage information." > "${xdgPath}/notestorm/notes/README.md"
|
|
fi
|
|
}
|
|
|
|
backup_notes() {
|
|
# Create a zip backup of all notes with optional password
|
|
# Usage: backup_notes [password]
|
|
local password="$1"
|
|
local backup_file="$HOME/Documents/notestorm-$(date +%Y-%m-%d).zip"
|
|
|
|
|
|
# Check if zip command exists
|
|
if ! command -v zip &> /dev/null; then
|
|
infobox "$(gettext "Error: zip command not found. Please install zip package.")"
|
|
return 1
|
|
fi
|
|
|
|
# Create Documents directory if it doesn't exist
|
|
mkdir -p "$HOME/Documents"
|
|
|
|
# Check if backup file already exists
|
|
if [[ -f "$backup_file" ]]; then
|
|
if [[ "$(yesno "$(gettext "Backup file already exists. Overwrite?")")" != "Yes" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Create backup with or without password
|
|
local zip_cmd="zip -r"
|
|
if [[ -n "$password" ]]; then
|
|
zip_cmd="$zip_cmd -P \"$password\""
|
|
fi
|
|
|
|
# Change to parent directory to avoid including full path in zip
|
|
local current_dir="$(pwd)"
|
|
cd "$xdgPath"
|
|
|
|
if [[ -n "$password" ]]; then
|
|
zip -r -P "$password" "$backup_file" "notestorm/notes" 2>/dev/null
|
|
else
|
|
zip -r "$backup_file" "notestorm/notes" 2>/dev/null
|
|
fi
|
|
|
|
local zip_result=$?
|
|
cd "$current_dir"
|
|
|
|
if [[ $zip_result -eq 0 ]]; then
|
|
infobox "$(gettext "Backup created successfully:") $backup_file"
|
|
return 0
|
|
else
|
|
infobox "$(gettext "Backup failed.")"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
restore_notes() {
|
|
# Restore notes from a zip backup
|
|
# Usage: restore_notes [zip_file]
|
|
local zip_file="$1"
|
|
|
|
# Check if unzip command exists
|
|
if ! command -v unzip &> /dev/null; then
|
|
infobox "$(gettext "Error: unzip command not found. Please install unzip package.")"
|
|
return 1
|
|
fi
|
|
|
|
# If no file specified, let user choose
|
|
if [[ -z "$zip_file" ]]; then
|
|
zip_file="$(inputbox "$(gettext "Enter path to backup zip file:")" "$HOME/Documents/")"
|
|
if [[ -z "$zip_file" ]]; then
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
# Check if zip file exists
|
|
if [[ ! -f "$zip_file" ]]; then
|
|
infobox "$(gettext "Error: Backup file not found:") $zip_file"
|
|
return 1
|
|
fi
|
|
|
|
# Warn about overwriting existing notes
|
|
if [[ -d "$xdgPath/notestorm/notes" ]] && [[ -n "$(ls -A "$xdgPath/notestorm/notes" 2>/dev/null)" ]]; then
|
|
if [[ "$(yesno "$(gettext "This will overwrite existing notes. Continue?")")" != "Yes" ]]; then
|
|
return 0
|
|
fi
|
|
|
|
# Backup existing notes first
|
|
local backup_existing="$xdgPath/notestorm/notes-backup-$(date +%Y-%m-%d-%H%M%S)"
|
|
mv "$xdgPath/notestorm/notes" "$backup_existing"
|
|
infobox "$(gettext "Existing notes backed up to:") $backup_existing"
|
|
fi
|
|
|
|
# Create notes directory
|
|
mkdir -p "$xdgPath/notestorm"
|
|
|
|
# Extract the backup
|
|
if unzip -o "$zip_file" -d "$xdgPath" 2>/dev/null; then
|
|
infobox "$(gettext "Notes restored successfully from:") $zip_file"
|
|
return 0
|
|
else
|
|
infobox "$(gettext "Restore failed. Check if the file is a valid zip or if password is required.")"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
claude_note() {
|
|
# Create a note using Claude Code with the provided prompt
|
|
# Usage: claude_note "prompt text"
|
|
local prompt="$1"
|
|
|
|
# Check if prompt is provided
|
|
if [[ -z "$prompt" ]]; then
|
|
gettext "Error: No prompt provided."
|
|
echo
|
|
exit 1
|
|
fi
|
|
|
|
# Check if claude command is available
|
|
if ! command -v claude &> /dev/null; then
|
|
echo "$(gettext "Error: claude command not found. Please install Claude Code.")"
|
|
echo
|
|
echo "$(gettext "Installation instructions:")"
|
|
echo "$(gettext "- Arch Linux: Install from AUR with 'yay -S claude-code' or 'paru -S claude-code'")"
|
|
echo "$(gettext "- Debian/Ubuntu: Install Node.js then run 'npm install -g @anthropic-ai/claude-code'")"
|
|
echo "$(gettext "- Generic: See https://docs.anthropic.com/en/docs/claude-code/quickstart")"
|
|
echo
|
|
exit 1
|
|
fi
|
|
|
|
# Generate note name based on timestamp
|
|
local noteName=$(date '+%s')
|
|
while [ -f "${xdgPath}/notestorm/notes/${noteName}.md" ]; do
|
|
((noteName++))
|
|
done
|
|
|
|
# Enhance the prompt to ensure proper markdown formatting and direct content
|
|
local enhanced_prompt="Create a markdown-formatted note with the following content: ${prompt}
|
|
|
|
Please format your response as a proper markdown document that can be saved directly as a .md file. Start with a clear title using # header syntax. Provide only the note content itself, not meta-commentary about creating the note. The note should be similar in style to a CLI reference with clear sections and practical information."
|
|
|
|
# Call claude and capture output
|
|
local claude_output
|
|
if ! claude_output=$(claude -p "$enhanced_prompt" 2>&1); then
|
|
gettext "Error: Failed to get response from Claude Code: "
|
|
echo "$claude_output"
|
|
exit 1
|
|
fi
|
|
|
|
# Write the output to the note file
|
|
echo "$claude_output" > "${xdgPath}/notestorm/notes/${noteName}.md"
|
|
|
|
# Try to extract a meaningful name from the first line
|
|
local first_line="$(head -n 1 "${xdgPath}/notestorm/notes/${noteName}.md" | head -c 100)"
|
|
# Remove markdown headers and clean up the name
|
|
first_line="${first_line#\# }"
|
|
first_line="${first_line#\#\# }"
|
|
first_line="${first_line#\#\#\# }"
|
|
first_line="${first_line//[[:space:]]/_}"
|
|
first_line="${first_line//[^a-zA-Z0-9._-]/_}"
|
|
|
|
# Rename if we got a meaningful name and it doesn't conflict
|
|
if [[ -n "$first_line" && "$first_line" != "_" && ! -e "${xdgPath}/notestorm/notes/${first_line}.md" ]]; then
|
|
mv "${xdgPath}/notestorm/notes/${noteName}.md" "${xdgPath}/notestorm/notes/${first_line}.md"
|
|
noteName="${first_line}"
|
|
fi
|
|
|
|
modified="note added"
|
|
gettext "Claude note created: "
|
|
echo "${noteName}.md"
|
|
}
|
|
|
|
|
|
# Configuration section
|
|
# Available arguments in both long and short versions stored in associative array.
|
|
declare -A argList=(
|
|
[g]="git"
|
|
[l]="list"
|
|
[n]="new"
|
|
[r]="restore"
|
|
[z]="zip"
|
|
[C]="claude")
|
|
# Make the args a continuous string.
|
|
# Build short options manually to handle optional arguments
|
|
short="glnr:z:C:"
|
|
long="git,list,new,restore:,zip:,claude:"
|
|
editor="${EDITOR:-nano}"
|
|
messageTimeout=${messageTimeout:-1}
|
|
# Set pager - using arrays to prevent command injection
|
|
if command -v w3m &> /dev/null ; then
|
|
pager=("w3m" "-T" "text/html")
|
|
elif command -v elinks &> /dev/null ; then
|
|
pager=("elinks" "-force-html")
|
|
elif command -v lynx &> /dev/null ; then
|
|
pager=("lynx" "-force_html")
|
|
else
|
|
# For PAGER, we need to handle it carefully since it could be a complex command
|
|
if [[ -n "${PAGER}" ]]; then
|
|
# Split PAGER on whitespace - this is a compromise for compatibility
|
|
read -ra pager <<< "${PAGER}"
|
|
else
|
|
pager=("more")
|
|
fi
|
|
fi
|
|
xdgPath="${XDG_CONFIG_HOME:-$HOME/.config}"
|
|
# set up the notes directory
|
|
mkdir -p "${xdgPath}/notestorm/notes"
|
|
original_note
|
|
# Keep track of the number of arguments passed to the program.
|
|
argNum=$#
|
|
# Settings to improve accessibility of dialog.
|
|
export DIALOGOPTS='--insecure --no-lines --visit-items'
|
|
# Dialog custom configuration file
|
|
export DIALOGRC="${xdgPath}/notestorm/dialogrc"
|
|
# Generate dialogrc if it does not exist.
|
|
if ! [ -e "$DIALOGRC" ]; then
|
|
dialog --create-rc "$DIALOGRC"
|
|
# Add keybindings to dialogrc
|
|
echo >> "$DIALOGRC"
|
|
echo "# Edit keybinding" >> "$DIALOGRC"
|
|
echo "bindkey menubox ^E EXTRA" >> "$DIALOGRC"
|
|
fi
|
|
|
|
|
|
# Code section
|
|
|
|
# Check dependencies before proceeding
|
|
check_dependencies
|
|
|
|
# If the only arg is a number, display that note, then exit.
|
|
if [[ "$*" =~ [0-9]+ ]]; then
|
|
display_note $*
|
|
fi
|
|
|
|
# Parse non-numeric command line args.
|
|
if ! options=$(getopt -o "$short" -l "$long" -n "notestorm" -- "$@"); then
|
|
gettext -e "Usage: notestorm launch interactive session.\n"
|
|
gettext -e -- "-C or --claude \"prompt\" create a note using Claude Code with the provided prompt.\n"
|
|
gettext -e -- "-g or --git set up backups to git. Requires existing git repository with branch name \"master\".\n"
|
|
gettext -e -- "-n or --new add a new note without opening an interactive session.\n"
|
|
gettext -e -- "-r or --restore [file] restore notes from a zip backup file.\n"
|
|
gettext -e -- "-z or --zip password create a zip backup of all notes with password. Use empty string \"\" for no password.\n"
|
|
echo
|
|
gettext -e "You can use markdown syntax in notes.\n"
|
|
gettext -e "Notes are named numerically. they can be renamed and will still show up so long as they end with a .md extension.\n"
|
|
gettext -e "To get a numbered list, instead of the interactive menu, use the -l option.\n"
|
|
gettext -e "To show a note without using the interactive interface and pager, give the notes number as the only argument.\n"
|
|
gettext -e "Zip backups are saved to ~/Documents/notestorm-YYYY-MM-DD.zip\n"
|
|
gettext "Notes are saved in "
|
|
echo "${xdgPath}/notestorm/notes"
|
|
exit 1
|
|
fi
|
|
|
|
eval set -- "$options"
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
"-g"|"--git") initialize_git;;
|
|
"-l"|"--list") list_notes;;
|
|
"-n"|"--new") add_note;;
|
|
"-r"|"--restore")
|
|
shift
|
|
restore_notes "$1";;
|
|
"-z"|"--zip")
|
|
shift
|
|
backup_notes "$1";;
|
|
"-C"|"--claude")
|
|
shift
|
|
claude_note "$1";;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
trap cleanup SIGINT SIGTERM EXIT
|
|
|
|
# If there were args, the program was controled by those and we do not need the menu.
|
|
if [ $argNum -gt 0 ]; then
|
|
exit 0
|
|
fi
|
|
|
|
while [ "$action" != "exit" ]; do
|
|
# Get a list of notes
|
|
mapfile -t notes < <(find "$xdgPath/notestorm/notes" -type f -iname '*.md' -o -iname '*.gpg')
|
|
# Generate the note menu:
|
|
unset noteMenu
|
|
declare -a noteMenu
|
|
for i in "${notes[@]}" ; do
|
|
noteMenu+=("$i")
|
|
if [[ "${i##*.}" == "gpg" ]]; then
|
|
noteMenu+=("$(gettext "[enc]") $(basename "${i%.md.gpg}")")
|
|
else
|
|
noteMenu+=("$(head -1 "${i}")")
|
|
fi
|
|
done
|
|
|
|
# Create menu of actions and notes.
|
|
ifs="$IFS"
|
|
IFS=$'\n'
|
|
action="$(menulist "${noteMenu[@]}")"
|
|
dialogCode=$?
|
|
IFS="$ifs"
|
|
|
|
if [ -z "$action" ]; then
|
|
action="exit"
|
|
fi
|
|
|
|
# Run selected action
|
|
case "${action}" in
|
|
"add_note") add_note;;
|
|
"exit") exit 0;;
|
|
*)
|
|
case $dialogCode in
|
|
0) display_note "${action}";;
|
|
2) more_menu "${action#HELP }";;
|
|
3) add_note;;
|
|
esac;;
|
|
esac
|
|
done
|
|
|
|
exit 0
|