// Copyright (C) 2026 Danilo M. // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License version 2 as // published by the Free Software Foundation. // // This program 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. import Quickshell.Io import QtQuick import "../.." import "Notes.js" as Notes // A drawer tile and page for bash-notes. The store is notes.sh's own, under // BASEDIR (default ~/.local/share/bash-notes): db.json is the index and // notes/ holds one file per note, named by its creation time in epoch // seconds. The page writes notes in exactly the shape notes.sh reads, so both // tools work on the same notes. // // A Module is a plain QtObject and has no default property, so the Process and // the FileView below are declared as typed properties, as UpdatesModule does // for its Process. Module { id: mod name: "notes" label: "Notes" alwaysActive: true // fa-sticky_note, present in Inconsolata Nerd Font. icon: "\uf249" // Resolved from notes.sh's own rc, so a changed BASEDIR moves the page // with it. Empty until the shell below returns. property string baseDir: "" property string dbPath: "" property string notesDir: "" property bool gitEnabled: false property string client: "" // The whole parsed db.json, so a write can replace .notes and carry // params and git through untouched. property var db: ({ notes: [] }) property bool ready: false property string lastError: "" readonly property var notes: Notes.sorted(mod.db.notes) readonly property int count: Array.isArray(mod.db.notes) ? mod.db.notes.length : 0 // One shell, sourcing the rc the way notes.sh sources it, so ${BASEDIR} // expands with ~ and notes.sh's own defaults apply when there is no rc. // Single-quoted JS strings: a template literal would interpolate ${...}. // Output is one field per line: base, db, notes dir, git on, client. property Process cfg: Process { running: true command: ["sh", "-c", '[ -r "$HOME/.config/bash-notes.rc" ] && . "$HOME/.config/bash-notes.rc"\n' + 'base="${BASEDIR:-$HOME/.local/share/bash-notes}"\n' + 'git=0; [ "${USEGIT:-false}" = "true" ] && [ -n "${GITREMOTE:-}" ] && git=1\n' + 'printf "%s\\n" "$base" "${DB:-$base/db.json}" "${NOTESDIR:-$base/notes}" "$git" "${GITCLIENT:-$(hostname)}"'] stdout: StdioCollector { onStreamFinished: mod.applyConfig(this.text) } } function applyConfig(text) { const l = String(text).split("\n"); mod.baseDir = l[0] || ""; mod.dbPath = l[1] || ""; mod.notesDir = l[2] || ""; mod.gitEnabled = (l[3] || "") === "1"; mod.client = l[4] || ""; } // The store, watched rather than read on open: the tile's count has to // move when a note is added from either side, the CLI included. A missing // file is an empty store, not an error worth logging. property FileView dbView: FileView { path: mod.dbPath atomicWrites: true watchChanges: true printErrors: false onFileChanged: mod.reparse() onLoaded: mod.reparse() onLoadFailed: mod.reparse() } // Nothing to read until the config has resolved, so the load failed on the // empty path does not mark the store ready. function reparse() { if (mod.dbPath === "") return; mod.db = Notes.parse(mod.dbView.text()); mod.ready = true; } tileContent: Component { Text { width: parent ? parent.width : implicitWidth elide: Text.ElideRight font { family: Theme.fontFamily; pixelSize: Theme.fontSize - 4 } color: Theme.subtext text: !mod.ready ? "…" : mod.count === 0 ? "empty" : mod.count === 1 ? "1 note" : mod.count + " notes" } } page: Component { Page { id: pg title: "Notes" // 57 is the page header plus its rule, the reading ClipboardModule // takes of the same number. NotesPage { width: parent.width height: pg.height - 57 owner: mod } } } }