├── docs ├── index.ts ├── docs │ ├── .vitepress │ │ ├── cache │ │ │ └── deps │ │ │ │ ├── package.json │ │ │ │ ├── vue.js.map │ │ │ │ ├── vitepress___@vueuse_core.js.map │ │ │ │ ├── _metadata.json │ │ │ │ ├── vue.js │ │ │ │ └── vitepress___@vueuse_core.js │ │ ├── theme │ │ │ └── index.ts │ │ └── config.ts │ ├── config │ │ ├── keybinds │ │ │ ├── index.md │ │ │ ├── global.md │ │ │ ├── fuzz.md │ │ │ ├── diary.md │ │ │ ├── daily.md │ │ │ └── home.md │ │ ├── styles │ │ │ ├── icons.md │ │ │ ├── index.md │ │ │ └── taskstyles.md │ │ ├── index.md │ │ └── general.md │ ├── guide │ │ ├── index.md │ │ ├── install.md │ │ └── contribute.md │ └── index.md ├── README.md ├── package.json ├── .gitignore ├── tsconfig.json └── vitepress.theme.css ├── .github └── CODEOWNERS ├── .gitignore ├── showcase.mp4 ├── internal ├── enums │ ├── splits.go │ ├── page.go │ ├── popuptype.go │ ├── taskPopup.go │ ├── tasktype.go │ └── task.go ├── styles │ ├── huhStyles.go │ ├── nodeStyles.go │ ├── borderStyle.go │ ├── cmdStyles.go │ ├── taskStyles.go │ └── text.go ├── fileTree │ ├── treeStructs.go │ ├── flatten.go │ └── fileTree.go ├── utils │ └── utils.go ├── keymap │ ├── converter.go │ ├── viewerMap.go │ ├── homeMap.go │ ├── explorerMap.go │ ├── fuzzyMap.go │ ├── diaryMap.go │ ├── taskpopup.go │ └── dailytasks.go ├── config │ ├── configModels.go │ ├── config.go │ ├── keybinds.go │ ├── default.go │ ├── styles.go │ └── glamour.go ├── models │ ├── fzf │ │ ├── fzfti.go │ │ ├── fzfvp.go │ │ └── fzf.go │ ├── menu │ │ ├── menu.go │ │ └── menulist.go │ ├── daily │ │ ├── handlers.go │ │ ├── taskDelegate.go │ │ ├── items.go │ │ └── daily.go │ ├── error │ │ └── error.go │ ├── taskPopup │ │ ├── delete.go │ │ ├── select.go │ │ ├── form.go │ │ └── taskPopup.go │ ├── filePopup │ │ ├── popup.go │ │ └── handlers.go │ ├── viewer │ │ └── viewer.go │ ├── homeModel │ │ └── homeModel.go │ ├── diary │ │ └── diary.go │ ├── fileExplorer │ │ └── fileExplorer.go │ └── rootModel.go ├── messages │ └── messages.go └── colors │ └── colors.go ├── main.go ├── cmd ├── tasks │ ├── tasks.go │ ├── delete.go │ ├── create.go │ ├── edit.go │ └── list.go ├── sync │ ├── sync.go │ ├── init.go │ ├── resync.go │ ├── push.go │ ├── save.go │ └── remote.go ├── notes │ ├── notes.go │ ├── delete.go │ ├── create.go │ ├── render.go │ ├── edit.go │ └── list.go ├── dump.go ├── init.go └── root.go ├── pkg ├── .SRCINFO └── PKGBUILD ├── LICENSE ├── .goreleaser.yaml ├── go.mod └── README.md /docs/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nucleofusion 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | debug.log 2 | Toney 3 | dist 4 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /showcase.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SourcewareLab/Toney/HEAD/showcase.mp4 -------------------------------------------------------------------------------- /internal/enums/splits.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type Splits int 4 | 5 | const ( 6 | File = iota 7 | FViewer 8 | ) 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/vue.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [], 4 | "sourcesContent": [], 5 | "mappings": "", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /internal/enums/page.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type Page int 4 | 5 | const ( 6 | MenuPage = iota 7 | HomePage 8 | DailyPage 9 | DiaryPage 10 | Quit 11 | ) 12 | -------------------------------------------------------------------------------- /internal/enums/popuptype.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type PopupType int 4 | 5 | const ( 6 | FileCreate = iota 7 | FileRename 8 | FileMove 9 | FileDelete 10 | ) 11 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "sources": [], 4 | "sourcesContent": [], 5 | "mappings": "", 6 | "names": [] 7 | } 8 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import '../../../vitepress.theme.css' 3 | 4 | export default { 5 | ...DefaultTheme, 6 | } 7 | 8 | -------------------------------------------------------------------------------- /internal/enums/taskPopup.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type TaskPopup int 4 | 5 | const ( 6 | CreateUnique = iota 7 | CreateRecurring 8 | Delete 9 | Edit 10 | ChangeStatus 11 | ClosePopup 12 | ) 13 | -------------------------------------------------------------------------------- /internal/styles/huhStyles.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import "github.com/charmbracelet/huh" 4 | 5 | func HuhTheme() *huh.Theme { // Do config/Docs for this 6 | th := huh.ThemeCatppuccin() 7 | return th 8 | } 9 | -------------------------------------------------------------------------------- /internal/fileTree/treeStructs.go: -------------------------------------------------------------------------------- 1 | package filetree 2 | 3 | type Node struct { 4 | Name string 5 | Depth int 6 | Parent *Node 7 | Children []*Node 8 | IsDirectory bool 9 | IsExpanded bool 10 | } 11 | -------------------------------------------------------------------------------- /internal/enums/tasktype.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type TaskType int 4 | 5 | var TaskTypeMap map[TaskType]string = map[TaskType]string{ 6 | RecurringTask: "Recurring", 7 | UniqueTask: "Unique", 8 | } 9 | 10 | const ( 11 | UniqueTask = iota 12 | RecurringTask 13 | ) 14 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # toney-docs 2 | 3 | To install dependencies: 4 | 5 | ```bash 6 | bun install 7 | ``` 8 | 9 | To run: 10 | 11 | ```bash 12 | bun run index.ts 13 | ``` 14 | 15 | This project was created using `bun init` in bun v1.2.18. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. 16 | -------------------------------------------------------------------------------- /internal/styles/nodeStyles.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/lipgloss" 6 | ) 7 | 8 | var CurrentNodeStyle = lipgloss.NewStyle().Background(colors.ColorPalette().MenuSelectedBg).Foreground(colors.ColorPalette().MenuSelectedText) 9 | -------------------------------------------------------------------------------- /cmd/tasks/tasks.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func TasksCmd() *cobra.Command { 6 | cmd := &cobra.Command{ 7 | Use: "tasks", 8 | Short: "Manage your tasks", 9 | } 10 | 11 | cmd.AddCommand( 12 | ListCmd(), 13 | CreatCmd(), 14 | DeleteCmd(), 15 | EditCmd(), 16 | ) 17 | 18 | return cmd 19 | } 20 | -------------------------------------------------------------------------------- /internal/enums/task.go: -------------------------------------------------------------------------------- 1 | package enums 2 | 3 | type TaskStatus int 4 | 5 | var TaskStatusMap map[TaskStatus]string = map[TaskStatus]string{ 6 | Pending: "Pending", 7 | Abandoned: "Abandoned", 8 | Complete: "Complete", 9 | Started: "Started", 10 | } 11 | 12 | const ( 13 | Pending = iota 14 | Started 15 | Abandoned 16 | Complete 17 | ) 18 | -------------------------------------------------------------------------------- /cmd/sync/sync.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func SyncCmd() *cobra.Command { 6 | cmd := &cobra.Command{ 7 | Use: "sync", 8 | Short: "sync your notes and tasks", 9 | } 10 | 11 | cmd.AddCommand( 12 | InitCmd(), 13 | SaveCmd(), 14 | RemoteCmd(), 15 | PushCmd(), 16 | ) 17 | 18 | return cmd 19 | } 20 | -------------------------------------------------------------------------------- /cmd/notes/notes.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import "github.com/spf13/cobra" 4 | 5 | func NotesCmd() *cobra.Command { 6 | cmd := &cobra.Command{ 7 | Use: "note", 8 | Short: "Manage your notes", 9 | } 10 | 11 | cmd.AddCommand( 12 | ListCmd(), 13 | DeleteCmd(), 14 | CreateCmd(), 15 | EditCmd(), 16 | RenderCmd(), 17 | ) 18 | 19 | return cmd 20 | } 21 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/messages" 5 | tea "github.com/charmbracelet/bubbletea" 6 | ) 7 | 8 | func ReturnError(locn, title string, err error) tea.Cmd { 9 | return func() tea.Msg { 10 | return messages.ErrorMsg{ 11 | Locn: locn, 12 | Title: title, 13 | Msg: err.Error(), 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internal/fileTree/flatten.go: -------------------------------------------------------------------------------- 1 | package filetree 2 | 3 | func FlattenVisibleTree(root *Node) []*Node { 4 | var result []*Node 5 | var walk func(n *Node, depth int) 6 | 7 | walk = func(n *Node, depth int) { 8 | result = append(result, n) 9 | if n.IsExpanded { 10 | for _, child := range n.Children { 11 | walk(child, depth+1) 12 | } 13 | } 14 | } 15 | walk(root, 0) 16 | return result 17 | } 18 | -------------------------------------------------------------------------------- /internal/styles/borderStyle.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/lipgloss" 6 | ) 7 | 8 | func BorderStyle() lipgloss.Style { 9 | return lipgloss.NewStyle(). 10 | Padding(1, 2). 11 | Align(lipgloss.Center, lipgloss.Center). 12 | BorderStyle(lipgloss.RoundedBorder()). 13 | BorderForeground(colors.ColorPalette().Border) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/.SRCINFO: -------------------------------------------------------------------------------- 1 | pkgbase = toney 2 | pkgdesc = Fast, lightweight, terminal-based note-taking app for the modern developer. 3 | pkgver = 2.0.0 4 | pkgrel = 1 5 | url = https://github.com/SourcewareLab/Toney 6 | arch = x86_64 7 | license = MIT 8 | makedepends = go 9 | source = toney-2.0.0.tar.gz::https://github.com/SourcewareLab/Toney/archive/refs/tags/v2.0.0.tar.gz 10 | sha256sums = SKIP 11 | 12 | pkgname = toney 13 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toney-docs", 3 | "module": "index.ts", 4 | "type": "module", 5 | "private": true, 6 | "devDependencies": { 7 | "@types/bun": "latest", 8 | "vitepress": "^1.6.3" 9 | }, 10 | "peerDependencies": { 11 | "typescript": "^5" 12 | }, 13 | "scripts": { 14 | "dev": "vitepress dev docs", 15 | "build": "vitepress build docs", 16 | "serve": "vitepress serve docs" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /internal/keymap/converter.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import "github.com/charmbracelet/bubbles/key" 4 | 5 | type DynamicMap struct { 6 | keys []key.Binding 7 | } 8 | 9 | func NewDynamic(keys []key.Binding) *DynamicMap { 10 | return &DynamicMap{ 11 | keys: keys, 12 | } 13 | } 14 | 15 | func (d DynamicMap) ShortHelp() []key.Binding { 16 | return d.keys 17 | } 18 | 19 | func (d DynamicMap) FullHelp() [][]key.Binding { 20 | return [][]key.Binding{d.keys} 21 | } 22 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/index.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | Keybinds in Toney follow the [bubbletea](https://charm.sh/bubbletea) nomenclature, that is, 4 | 5 | | Vim | Toney | 6 | |-------|-------| 7 | |\| "H" | 8 | |\| "ctrl+x" | 9 | |\| NA| 10 | |\| " "| 11 | |\| "up"| 12 | 13 | So always make sure the keybinds you input are supported by bubbletea. There is a way to have `alt` but it is not yet supported, it will be added in future versions, keep yourself updated! 14 | -------------------------------------------------------------------------------- /internal/config/configModels.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type Config struct { 4 | General GeneralConfig `mapstructure:"general"` 5 | Styles StylesConfig `mapstructure:"styles"` 6 | Keybinds KeybindsConfig `mapstructure:"keybinds"` 7 | } 8 | 9 | type GeneralConfig struct { 10 | Editor []string `mapstructure:"editor"` 11 | NotesDir string `mapstructure:"notes_dir"` 12 | StartScript []string `mapstructure:"start_script"` 13 | StopScript []string `mapstructure:"stop_script"` 14 | Script []string `mapstructure:"script"` 15 | } 16 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies (bun install) 2 | node_modules 3 | 4 | # output 5 | out 6 | dist 7 | *.tgz 8 | 9 | # code coverage 10 | coverage 11 | *.lcov 12 | 13 | # logs 14 | logs 15 | _.log 16 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 17 | 18 | # dotenv environment variable files 19 | .env 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .env.local 24 | 25 | # caches 26 | .eslintcache 27 | .cache 28 | *.tsbuildinfo 29 | 30 | # IntelliJ based IDEs 31 | .idea 32 | 33 | # Finder (MacOS) folder config 34 | .DS_Store 35 | -------------------------------------------------------------------------------- /pkg/PKGBUILD: -------------------------------------------------------------------------------- 1 | # aur/PKGBUILD 2 | pkgname=toney 3 | pkgver=2.0.0 4 | pkgrel=1 5 | pkgdesc="Fast, lightweight, terminal-based note-taking app for the modern developer." 6 | arch=('x86_64') 7 | url="https://github.com/SourcewareLab/Toney" 8 | license=('MIT') 9 | depends=() 10 | makedepends=('go') 11 | source=("$pkgname-$pkgver.tar.gz::https://github.com/SourcewareLab/Toney/archive/refs/tags/v$pkgver.tar.gz") 12 | sha256sums=('SKIP') 13 | 14 | build() { 15 | cd "$pkgname-$pkgver" 16 | go build -o toney 17 | } 18 | 19 | package() { 20 | cd "$pkgname-$pkgver" 21 | install -Dm755 "toney" "$pkgdir/usr/bin/toney" 22 | } 23 | 24 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/global.md: -------------------------------------------------------------------------------- 1 | # Global 2 | 3 | Denoted by the `[keybinds.global]` tag in TOML. 4 | 5 | ## Up 6 | 7 | can change the 'up' key, generally used for menu's etc using the `up` toml key. 8 | 9 | default value is: 10 | ```toml 11 | up = "up" 12 | ``` 13 | 14 | ## Down 15 | 16 | can change the 'down' keybind, generally used for menu's etc using the `down` toml key. 17 | 18 | default value is: 19 | ```toml 20 | down = "down" 21 | ``` 22 | 23 | ## Script 24 | 25 | can change the keybind for executing a command/script using the `script` toml key. The internal working, adds `bash -c` so you may omit it. 26 | 27 | default value is: 28 | ```toml 29 | script = [] 30 | ``` 31 | -------------------------------------------------------------------------------- /internal/models/fzf/fzfti.go: -------------------------------------------------------------------------------- 1 | package fzf 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SourcewareLab/Toney/internal/colors" 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | "github.com/charmbracelet/bubbles/textinput" 9 | "github.com/charmbracelet/lipgloss" 10 | ) 11 | 12 | func TextStyle() lipgloss.Style { 13 | return lipgloss.NewStyle().Foreground(colors.ColorPalette().Text) 14 | } 15 | 16 | func NewTI(w int) textinput.Model { 17 | ti := textinput.New() 18 | ti.Placeholder = fmt.Sprintf("Press '%s' To Search...", config.AppConfig.Keybinds.Fuzz.StartWriting) 19 | ti.Prompt = "Filter : " 20 | ti.Width = w - 12 21 | ti.TextStyle = TextStyle() 22 | ti.PromptStyle = TextStyle() 23 | 24 | return ti 25 | } 26 | -------------------------------------------------------------------------------- /internal/keymap/viewerMap.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/config" 5 | "github.com/charmbracelet/bubbles/key" 6 | ) 7 | 8 | type ViewerKeyMap struct { 9 | ScrollUp key.Binding 10 | ScrollDown key.Binding 11 | Grep key.Binding // TODO: Config and Docs 12 | } 13 | 14 | func NewViewerKeyMap() ViewerKeyMap { 15 | cfg := config.AppConfig.Keybinds.Home 16 | return ViewerKeyMap{ 17 | ScrollUp: key.NewBinding( 18 | key.WithKeys(cfg.ScrollUp), 19 | key.WithHelp("↑", "scroll up"), 20 | ), 21 | ScrollDown: key.NewBinding( 22 | key.WithKeys(cfg.ScrollDown), 23 | key.WithHelp("↓", "scroll down"), 24 | ), 25 | Grep: key.NewBinding( 26 | key.WithKeys("/"), 27 | key.WithHelp("/", "grep"), 28 | ), 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Environment setup & latest features 4 | "lib": ["ESNext"], 5 | "target": "ESNext", 6 | "module": "Preserve", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedIndexedAccess": true, 22 | "noImplicitOverride": true, 23 | 24 | // Some stricter flags (disabled by default) 25 | "noUnusedLocals": false, 26 | "noUnusedParameters": false, 27 | "noPropertyAccessFromIndexSignature": false 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /internal/styles/cmdStyles.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/lipgloss" 6 | "github.com/charmbracelet/lipgloss/table" 7 | ) 8 | 9 | func TableStyle(row, col int) lipgloss.Style { 10 | switch { 11 | case row == table.HeaderRow: 12 | return lipgloss.NewStyle().Align(lipgloss.Center). 13 | Bold(true).Italic(true).BorderForeground(colors.ColorPalette().Border). 14 | Foreground(lipgloss.Color("#74c7ec")).Background(lipgloss.Color("#313244")) 15 | default: 16 | return lipgloss.NewStyle().Foreground(colors.ColorPalette().Text).Padding(0, 1) 17 | } 18 | } 19 | 20 | func NewTable() *table.Table { 21 | t := table.New() 22 | t.Border(lipgloss.RoundedBorder()) 23 | t.BorderStyle(lipgloss.NewStyle().BorderForeground(colors.ColorPalette().ErrorText)) 24 | t.StyleFunc(TableStyle) 25 | return t 26 | } 27 | -------------------------------------------------------------------------------- /cmd/sync/init.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "github.com/SourcewareLab/Toney/internal/config" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func InitCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "init", 16 | Short: "initialize sync settings", 17 | RunE: func(cmd *cobra.Command, args []string) error { 18 | if err := config.SetConfig(); err != nil { 19 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 20 | } 21 | 22 | home, err := os.UserHomeDir() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 28 | 29 | command := exec.Command("git", "init") 30 | command.Dir = path 31 | 32 | err = command.Run() 33 | return err 34 | }, 35 | } 36 | 37 | return cmd 38 | } 39 | -------------------------------------------------------------------------------- /internal/keymap/homeMap.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/config" 5 | "github.com/charmbracelet/bubbles/key" 6 | ) 7 | 8 | type HomeKeyMap struct { 9 | FocusViewer key.Binding 10 | FocusExplorer key.Binding 11 | BackToMenu key.Binding 12 | Finder key.Binding 13 | } 14 | 15 | func NewHomeKeyMap() HomeKeyMap { 16 | cfg := config.AppConfig.Keybinds.Home 17 | return HomeKeyMap{ 18 | FocusExplorer: key.NewBinding( 19 | key.WithKeys(cfg.FocusExplorer), 20 | key.WithHelp(cfg.FocusExplorer, "file explorer"), 21 | ), 22 | FocusViewer: key.NewBinding( 23 | key.WithKeys(cfg.FocusViewer), 24 | key.WithHelp(cfg.FocusViewer, "viewer"), 25 | ), 26 | BackToMenu: key.NewBinding( 27 | key.WithKeys(cfg.BackToMenu), 28 | key.WithHelp(cfg.BackToMenu, "return to menu"), 29 | ), 30 | Finder: key.NewBinding( 31 | key.WithKeys("f"), 32 | key.WithHelp("f", "find"), 33 | ), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/fuzz.md: -------------------------------------------------------------------------------- 1 | # Fuzzy Finder Keybinds 2 | 3 | Denoted by the `[keybinds.fuzz]` tag in TOML. Usable while in the fuzzy finder menu. 4 | 5 | ## Start Writing 6 | 7 | can change the keybind for writing/searching using the `start_writing` toml key. 8 | 9 | default value is: 10 | ```toml 11 | start_writing = "/" 12 | ``` 13 | 14 | ## Enter 15 | 16 | can change the keybind for selecting the item using the `enter` toml key. 17 | 18 | default value is: 19 | ```toml 20 | enter = "enter" 21 | ``` 22 | 23 | ## Up 24 | 25 | can change the keybind for moving up using the `up` toml key. 26 | 27 | default value is: 28 | ```toml 29 | up = "up" 30 | ``` 31 | 32 | ## Down 33 | 34 | can change the keybind for moving down using the `down` toml key. 35 | 36 | default value is: 37 | ```toml 38 | down = "down" 39 | ``` 40 | 41 | ## Exit 42 | 43 | can change the keybind for exiting using the `exit` toml key. 44 | 45 | default value is: 46 | ```toml 47 | exit = "esc" 48 | ``` 49 | -------------------------------------------------------------------------------- /cmd/sync/resync.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "github.com/SourcewareLab/Toney/internal/config" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func ResyncCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "resync", 16 | Short: "syncs notes state", 17 | RunE: func(cmd *cobra.Command, args []string) error { 18 | if err := config.SetConfig(); err != nil { 19 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 20 | } 21 | 22 | home, err := os.UserHomeDir() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 28 | 29 | command := exec.Command("git", "pull", "--rebase") 30 | command.Dir = path 31 | 32 | err = command.Run() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return err 38 | }, 39 | } 40 | 41 | return cmd 42 | } 43 | -------------------------------------------------------------------------------- /cmd/dump.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/pelletier/go-toml/v2" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func DumpCmd() *cobra.Command { 14 | return &cobra.Command{ 15 | Use: "dump", 16 | Short: "Dump the default config to config file", 17 | Run: func(cmd *cobra.Command, args []string) { 18 | def := config.DefaultConfig() 19 | 20 | home, _ := os.UserHomeDir() 21 | f, err := os.Create(filepath.Join(home, ".config", "toney", "config.toml")) 22 | if err != nil { 23 | fmt.Println("Error Creating File: ", err.Error()) 24 | return 25 | } 26 | defer f.Close() 27 | 28 | encoder := toml.NewEncoder(f) 29 | err = encoder.Encode(def) 30 | if err != nil { 31 | fmt.Println("Failed to write to file: ", err.Error()) 32 | return 33 | } 34 | 35 | fmt.Println("Successfully Dumped file to ~/.config/toney/config.toml") 36 | }, 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cmd/sync/push.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "github.com/SourcewareLab/Toney/internal/config" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func PushCmd() *cobra.Command { 14 | cmd := &cobra.Command{ 15 | Use: "push", 16 | Short: "push to the remote url", 17 | RunE: func(cmd *cobra.Command, args []string) error { 18 | if err := config.SetConfig(); err != nil { 19 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 20 | } 21 | 22 | home, err := os.UserHomeDir() 23 | if err != nil { 24 | return err 25 | } 26 | 27 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 28 | 29 | command := exec.Command("git", "push", "-u", "origin", "HEAD") 30 | command.Dir = path 31 | 32 | err = command.Run() 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | }, 39 | } 40 | 41 | return cmd 42 | } 43 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/diary.md: -------------------------------------------------------------------------------- 1 | # Diary Keybinds 2 | 3 | Denoted by the `[keybinds.diary]` tag in TOML. 4 | 5 | ## Edit File 6 | 7 | can change the keybind for editing the file using the `edit` toml key. 8 | 9 | default value is: 10 | ```toml 11 | edit = "e" 12 | ``` 13 | 14 | ## Finder 15 | 16 | can change the keybind for opening the fuzzy finder using the `finder` toml key. 17 | 18 | default value is: 19 | ```toml 20 | finder = "f" 21 | ``` 22 | 23 | ## Scroll Up 24 | 25 | can change the keybind for scrolling up using the `scroll_up` toml key. 26 | 27 | default value is: 28 | ```toml 29 | scroll_up = "up" 30 | ``` 31 | 32 | ## Scroll Down 33 | 34 | can change the keybind for scrolling down using the `scroll_down` toml key. 35 | 36 | default value is: 37 | ```toml 38 | scroll_down = "down" 39 | ``` 40 | 41 | ## Return To Menu 42 | 43 | can change the keybind for returning to menu using the `return_to_menu` toml key. 44 | 45 | default value is: 46 | ```toml 47 | return_to_menu = "esc" 48 | ``` 49 | -------------------------------------------------------------------------------- /docs/docs/config/styles/icons.md: -------------------------------------------------------------------------------- 1 | # Icons 2 | 3 | Denoted by the `[styles.icons]` tag in TOML. Uses nerd fonts, so may require nerd fonts to be downloaded. 4 | 5 | ## Folder Icon 6 | 7 | can change the folder icon shown in the filetree using the `folder_icon` key. 8 | 9 | default value is: 10 | ```toml 11 | folder_icon = "󰷏" 12 | ``` 13 | 14 | ## File Icon 15 | 16 | can change the file icon shown in the filetree using the `file_icon` key. 17 | 18 | default value is: 19 | ```toml 20 | file_icon = "" 21 | ``` 22 | 23 | ## Task Icons 24 | 25 | used to change the Icons shown in daily tasks for each status. uses the `[styles.icons.task_icons]` 26 | 27 | ### Completed Task 28 | 29 | default value is: 30 | ```toml 31 | completed_icon = "✓" 32 | ``` 33 | 34 | ### Pending Task 35 | 36 | default value is: 37 | ```toml 38 | pending_icon = "~" 39 | ``` 40 | ### Started Task 41 | 42 | default value is: 43 | ```toml 44 | started_icon = "○" 45 | ``` 46 | 47 | ### Abandoned Task 48 | 49 | default value is: 50 | ```toml 51 | abandoned_icon = "×" 52 | ``` 53 | 54 | -------------------------------------------------------------------------------- /docs/docs/config/index.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | Toney supports a flexible configuration file for customizing themes, keybinds etc. We use a TOML format and the default config file is at: 4 | 5 | - Linux: 6 | ``` 7 | ~/.config/toney/config.toml 8 | ``` 9 | 10 | - Windows: 11 | ``` 12 | C:\Users\\AppData\Roaming\toney\config.yml 13 | ``` 14 | 15 | - Mac: 16 | ``` 17 | ~/Library/Application Support/toney/config.yml 18 | ``` 19 | 20 | ## File Format 21 | 22 | Toney uses TOML for its configuration file. Making it readable and easy to edit. 23 | 24 | An example minimal file with vim motions (hjkl motions, and 'a' for create etc) 25 | ```toml 26 | [keybinds.global] 27 | up = "k" 28 | down = "j" 29 | left = "h" 30 | right = "l" 31 | 32 | [keybinds.home] 33 | create = "a" 34 | scroll_up = "k" 35 | scroll_down = "k" 36 | 37 | [keybinds.daily] 38 | create = "a" 39 | ``` 40 | 41 | ## Key Sections 42 | 43 | the config file has 3 main sections: 44 | 45 | - **General**: for general stuff such as editor, notes_dir etc 46 | - **Styles**: to customize styles and colors 47 | - **Keybinds**: customize keybinds and styles 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nucleo Fusion 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /internal/models/fzf/fzfvp.go: -------------------------------------------------------------------------------- 1 | package fzf 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/bubbles/viewport" 6 | "github.com/charmbracelet/lipgloss" 7 | ) 8 | 9 | func ItemLineStyle(w int) lipgloss.Style { 10 | return lipgloss.NewStyle().Width(w).Foreground(colors.ColorPalette().Text) 11 | } 12 | 13 | func SelectedItemStyle(w int) lipgloss.Style { 14 | return ItemLineStyle(w).Background(colors.ColorPalette().MenuSelectedBg).Foreground(colors.ColorPalette().MenuSelectedText) 15 | } 16 | 17 | func VPStyle() lipgloss.Style { 18 | return lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().FocusedBorder). 19 | Foreground(colors.ColorPalette().Text) 20 | } 21 | 22 | func NewVP(w int, h int, items []string) viewport.Model { 23 | vp := viewport.New(w/3, h/2) 24 | vp.Style = VPStyle() 25 | 26 | text := "" 27 | for k, v := range items { 28 | if k == 0 { 29 | text += SelectedItemStyle(w/3).Render(v) + "\n" 30 | continue 31 | } 32 | 33 | text += ItemLineStyle(w/3).Render(v) + "\n" 34 | } 35 | 36 | vp.SetContent(text) 37 | 38 | return vp 39 | } 40 | -------------------------------------------------------------------------------- /cmd/sync/save.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/SourcewareLab/Toney/internal/config" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | func SaveCmd() *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "save", 17 | Short: "saves current notes state", 18 | RunE: func(cmd *cobra.Command, args []string) error { 19 | if err := config.SetConfig(); err != nil { 20 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 21 | } 22 | 23 | home, err := os.UserHomeDir() 24 | if err != nil { 25 | return err 26 | } 27 | 28 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 29 | 30 | command := exec.Command("git", "add", ".") 31 | command.Dir = path 32 | 33 | err = command.Run() 34 | if err != nil { 35 | return err 36 | } 37 | 38 | command = exec.Command("git", "commit", "-m", fmt.Sprintf("Saved state at %s", time.Now().Format("15:04:05 02 Jan"))) 39 | command.Dir = path 40 | 41 | err = command.Run() 42 | return err 43 | }, 44 | } 45 | 46 | return cmd 47 | } 48 | -------------------------------------------------------------------------------- /docs/docs/config/styles/index.md: -------------------------------------------------------------------------------- 1 | # General Styles 2 | 3 | Denoted by the `[styles]` tag in TOML. 4 | 5 | ## Text 6 | 7 | can change the text color using the `text` key. 8 | 9 | default value is: 10 | ```toml 11 | text = "#b4befe" 12 | ``` 13 | 14 | ## Background 15 | 16 | can change the text color using the `background` key. 17 | 18 | default value is: 19 | ```toml 20 | text = "#1e1e2e" 21 | ``` 22 | 23 | ## Border 24 | 25 | can change the border color using the `border` key. 26 | 27 | default value is: 28 | ```toml 29 | border = "#45475a" 30 | ``` 31 | 32 | ## Focused Border 33 | 34 | can change the border color when focused using the `focused_border` key. 35 | 36 | default value is: 37 | ```toml 38 | focused_border = "#b4befe" 39 | ``` 40 | ## Menu Selected Background 41 | 42 | can change the background color for selected item in all menus using the `menu_selected_bg` key. 43 | 44 | default value is: 45 | ```toml 46 | menu_selected_bg = "#b4befe" 47 | ``` 48 | 49 | ## Menu Selected Text 50 | 51 | can change the text color for selected item in all menus using the `menu_selected_text` key. 52 | 53 | default value is: 54 | ```toml 55 | menu_selected_text = "#1e1e2e" 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /internal/messages/messages.go: -------------------------------------------------------------------------------- 1 | package messages 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/enums" 5 | filetree "github.com/SourcewareLab/Toney/internal/fileTree" 6 | ) 7 | 8 | type ( 9 | ErrorMsg struct { 10 | Title string 11 | Msg string 12 | Locn string 13 | } 14 | 15 | CloseError struct{} 16 | 17 | FzfSelection struct { 18 | Selection string 19 | Exited bool 20 | } 21 | 22 | TaskPopupMessage struct { 23 | Type enums.TaskPopup 24 | IsDeleted bool 25 | Title string 26 | Desc string 27 | Status enums.TaskStatus 28 | } 29 | 30 | ChangePage struct { 31 | Page enums.Page 32 | } 33 | 34 | ShowLoader struct{} 35 | 36 | HideLoader struct{} 37 | 38 | EditorClose struct { 39 | Err error 40 | IsNative bool 41 | Value string 42 | } 43 | 44 | ShowPopupMessage struct { 45 | Type enums.PopupType 46 | Curr *filetree.Node 47 | } 48 | 49 | RefreshFileExplorerMsg struct{} 50 | 51 | NvimDoneMsg struct { 52 | Err string 53 | } 54 | 55 | ChangeFileMessage struct { 56 | Path string 57 | } 58 | 59 | RefreshView struct { 60 | Content string 61 | Path string 62 | } 63 | HidePopupMessage struct{} 64 | ) 65 | -------------------------------------------------------------------------------- /internal/models/menu/menu.go: -------------------------------------------------------------------------------- 1 | package menu 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/enums" 5 | "github.com/SourcewareLab/Toney/internal/styles" 6 | tea "github.com/charmbracelet/bubbletea" 7 | "github.com/charmbracelet/lipgloss" 8 | ) 9 | 10 | type Menu struct { 11 | Width int 12 | Height int 13 | Options map[enums.Page]string 14 | List *MenuList 15 | } 16 | 17 | func NewMenu(w int, h int) *Menu { 18 | opts := map[enums.Page]string{ 19 | enums.HomePage: "Home", 20 | enums.DailyPage: "Daily Tasks", 21 | enums.DiaryPage: "Diary", 22 | enums.Quit: "Quit", 23 | } 24 | 25 | list := NewMenuList(w/3, h/2-1, opts) 26 | return &Menu{ 27 | Width: w, 28 | Height: h, 29 | Options: opts, 30 | List: list, 31 | } 32 | } 33 | 34 | func (m *Menu) Init() tea.Cmd { 35 | return nil 36 | } 37 | 38 | func (m *Menu) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 39 | var cmd tea.Cmd 40 | 41 | _, cmd = m.List.Update(msg) 42 | 43 | return m, cmd 44 | } 45 | 46 | func (m *Menu) View() string { 47 | logo := styles.GetLogo(m.Width, m.Height/2) 48 | list := lipgloss.PlaceHorizontal(m.Width/3, lipgloss.Center, m.List.View()) 49 | 50 | return lipgloss.JoinVertical(lipgloss.Center, logo, list) 51 | } 52 | -------------------------------------------------------------------------------- /cmd/notes/delete.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | type DeleteOptions struct{} 13 | 14 | func DeleteCmd() *cobra.Command { 15 | cmd := &cobra.Command{ 16 | Use: "delete", 17 | Short: "Delete a note by relative path", 18 | Example: "toney note delete mydir/mynote.md", 19 | Args: cobra.ExactArgs(1), 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | if err := config.SetConfig(); err != nil { 22 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 23 | } 24 | 25 | if args[0] == "" { 26 | return fmt.Errorf("missing filepath parameter") 27 | } 28 | 29 | home, err := os.UserHomeDir() 30 | if err != nil { 31 | return fmt.Errorf("could not get user home directory: %v", err) 32 | } 33 | path := filepath.Join(home, config.AppConfig.General.NotesDir, args[0]) 34 | if err := os.Remove(path); err != nil { 35 | return fmt.Errorf("failed to delete note at %s :-\n%v", path, err) 36 | } 37 | 38 | fmt.Printf("Successfully deleted note %s", path) 39 | return nil 40 | }, 41 | } 42 | 43 | return cmd 44 | } 45 | -------------------------------------------------------------------------------- /internal/styles/taskStyles.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/lipgloss" 6 | ) 7 | 8 | type TaskStyle struct { 9 | Title lipgloss.Style 10 | Desc lipgloss.Style 11 | } 12 | 13 | func CompletedStyle() TaskStyle { 14 | return TaskStyle{ 15 | Title: lipgloss.NewStyle().Foreground(colors.ColorPalette().CompletedTask.TaskTitle), 16 | Desc: lipgloss.NewStyle().Foreground(colors.ColorPalette().CompletedTask.TaskDesc), 17 | } 18 | } 19 | 20 | func StartedStyle() TaskStyle { 21 | return TaskStyle{ 22 | Title: lipgloss.NewStyle().Foreground(colors.ColorPalette().StartedTask.TaskTitle), 23 | Desc: lipgloss.NewStyle().Foreground(colors.ColorPalette().StartedTask.TaskDesc), 24 | } 25 | } 26 | 27 | func AbandonedStyle() TaskStyle { 28 | return TaskStyle{ 29 | Title: lipgloss.NewStyle().Foreground(colors.ColorPalette().AbandonedTask.TaskTitle), 30 | Desc: lipgloss.NewStyle().Foreground(colors.ColorPalette().AbandonedTask.TaskDesc), 31 | } 32 | } 33 | 34 | func PendingStyle() TaskStyle { 35 | return TaskStyle{ 36 | Title: lipgloss.NewStyle().Foreground(colors.ColorPalette().PendingTask.TaskTitle), 37 | Desc: lipgloss.NewStyle().Foreground(colors.ColorPalette().PendingTask.TaskDesc), 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /internal/keymap/explorerMap.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/config" 5 | "github.com/charmbracelet/bubbles/key" 6 | ) 7 | 8 | type ExplorerKeyMap struct { 9 | CreateFile key.Binding 10 | MoveFile key.Binding 11 | RenameFile key.Binding 12 | DeleteFile key.Binding 13 | OpenForEdit key.Binding 14 | Up key.Binding 15 | Down key.Binding 16 | } 17 | 18 | func NewExplorerKeyMap() ExplorerKeyMap { 19 | cfg := config.AppConfig.Keybinds.Home 20 | 21 | return ExplorerKeyMap{ 22 | CreateFile: key.NewBinding( 23 | key.WithKeys(cfg.Create), 24 | key.WithHelp(cfg.Create, "create"), 25 | ), 26 | MoveFile: key.NewBinding( 27 | key.WithKeys(cfg.Move), 28 | key.WithHelp(cfg.Move, "move"), 29 | ), 30 | RenameFile: key.NewBinding( 31 | key.WithKeys(cfg.Rename), 32 | key.WithHelp(cfg.Rename, "rename"), 33 | ), 34 | DeleteFile: key.NewBinding( 35 | key.WithKeys(cfg.Delete), 36 | key.WithHelp(cfg.Delete, "delete"), 37 | ), 38 | OpenForEdit: key.NewBinding( 39 | key.WithKeys(cfg.Edit), 40 | key.WithHelp(cfg.Edit, "edit"), 41 | ), 42 | Up: key.NewBinding( 43 | key.WithKeys(cfg.ScrollUp), 44 | key.WithHelp("↑", "up"), 45 | ), 46 | Down: key.NewBinding( 47 | key.WithKeys(cfg.ScrollDown), 48 | key.WithHelp("↓", "down"), 49 | ), 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /internal/keymap/fuzzyMap.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/charmbracelet/bubbles/key" 8 | ) 9 | 10 | type FuzzyMap struct { 11 | StartWriting key.Binding 12 | Up key.Binding 13 | Down key.Binding 14 | Enter key.Binding 15 | Exit key.Binding 16 | } 17 | 18 | func NewFuzzyMap() FuzzyMap { 19 | cfg := config.AppConfig.Keybinds.Fuzz 20 | return FuzzyMap{ 21 | StartWriting: key.NewBinding( 22 | key.WithKeys(cfg.StartWriting), 23 | key.WithHelp(cfg.StartWriting, "start writing"), 24 | ), 25 | Up: key.NewBinding( 26 | key.WithKeys(cfg.Up), 27 | key.WithHelp(cfg.Up, "up"), 28 | ), 29 | Down: key.NewBinding( 30 | key.WithKeys(cfg.Down), 31 | key.WithHelp(cfg.Down, "down"), 32 | ), 33 | Enter: key.NewBinding( 34 | key.WithKeys(cfg.Enter), 35 | key.WithHelp(cfg.Enter, "enter"), 36 | ), 37 | Exit: key.NewBinding( 38 | key.WithKeys(cfg.Exit), 39 | key.WithHelp(cfg.Exit, "exit"), 40 | ), 41 | } 42 | } 43 | 44 | func (m FuzzyMap) Bindings() []key.Binding { 45 | var bindings []key.Binding 46 | 47 | v := reflect.ValueOf(m) 48 | for i := 0; i < v.NumField(); i++ { 49 | field := v.Field(i) 50 | if binding, ok := field.Interface().(key.Binding); ok { 51 | bindings = append(bindings, binding) 52 | } 53 | } 54 | 55 | return bindings 56 | } 57 | -------------------------------------------------------------------------------- /docs/docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | **Toney** is a fast, lightweight, terminal-based note-taking app for the modern developer. 4 | Built with [Bubbletea](https://charm.sh/), Toney brings a sleek TUI interface with markdown rendering, file navigation, and native Neovim editing – all in your terminal. 5 | 6 | ## What _is_ Toney? 7 | 8 | Toney is a lightweight TUI that lets you: 9 | 10 | - Take quick notes without leaving the terminal 11 | - Organize and edit notes in Markdown 12 | - Track your Daily Tasks 13 | - Customize the app for _you_ with a simple TOMl file 14 | 15 | No GUI bloat, just a minimalist note-taking app - fast and most importantly, _yours_. 16 | 17 | ## Key Features 18 | 19 | - **Terminal-first**: Made for the beginners and vim-guru's in mind. We abhor the mouse. 20 | - **Markdown-based**: Store notes in plain md file, with beautiful styling. Which you can customize, _obviously_. 21 | - **Daily Tasks**: Create and keep a record of your daily tasks, level up your productivity. 22 | - **Configurable**: Configure everything from styles to keybinds, I mean, who doesn't love that? 23 | 24 | ## Next Steps 25 | 26 | → Jump to the [Installation](/guide/install) section to start taking notes today. 27 | → Or peek into the [Configuration](/config/) page to make Toney your own. 28 | 29 |
30 | 31 | _Toney is built by [NucleoFusion](https://github.com/NucleoFusion), with love for the terminal._ 32 | -------------------------------------------------------------------------------- /docs/docs/guide/install.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 |
4 | 5 | **Make sure to run `Toney init` after installation** 6 | 7 | ## Linux 8 | 9 | ### Arch Linux (AUR) 10 | 11 | ``` 12 | yay -S toney 13 | ``` 14 | > _maintained by [NucleoFusion](https://github.com/NucleoFusion)_ 15 | 16 | ### Debian / Ubuntu (.deb) 17 | 18 | Install the .deb file [_here_](https://github.com/SourcewareLab/Toney/releases/tag/v2.0.0). 19 | 20 | ``` 21 | sudo apt install ./path/to/debfile 22 | ``` 23 | > _maintained by [NucleoFusion](https://github.com/NucleoFusion)_ 24 | 25 | ### Fedora / RHEL (.dnf) 26 | 27 | Install the .dnf file [_here_](https://github.com/SourcewareLab/Toney/releases/tag/v2.0.0). 28 | 29 | ``` 30 | sudo dnf install ./path/to/debfile 31 | ``` 32 | > _maintained by [NucleoFusion](https://github.com/NucleoFusion)_ 33 | 34 | ## Windows 35 | 36 | Install the .zip file [_here_](https://github.com/SourcewareLab/Toney/releases/tag/v2.0.0). 37 | 38 | 39 | ## MacOS 40 | 41 | Install the .tar.gz file [_here_](https://github.com/SourcewareLab/Toney/releases/tag/v2.0.0). 42 | 43 | 44 | ## From Source 45 | 46 | ### With Git Clone 47 | 48 | ``` 49 | curl -sSL https://raw.githubusercontent.com/NucleoFusion/toney/main/install.sh | bash 50 | cd Toney 51 | go build 52 | ./Toney 53 | ``` 54 | 55 | **Prerequisites**: 56 | 57 | - curl 58 | - go 59 | 60 | ### With Go Install 61 | 62 | ``` 63 | go install github.com/SourcewareLab/Toney@latest 64 | ``` 65 | -------------------------------------------------------------------------------- /internal/keymap/diaryMap.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/charmbracelet/bubbles/key" 8 | ) 9 | 10 | type DiaryMap struct { 11 | Edit key.Binding 12 | ScrollUp key.Binding 13 | ScrollDown key.Binding 14 | OpenFinder key.Binding 15 | BackToMenu key.Binding 16 | } 17 | 18 | func NewDiaryMap() DiaryMap { 19 | cfg := config.AppConfig.Keybinds.Diary 20 | return DiaryMap{ 21 | Edit: key.NewBinding( 22 | key.WithKeys(cfg.Edit), 23 | key.WithHelp(cfg.Edit, "edit"), 24 | ), 25 | OpenFinder: key.NewBinding( 26 | key.WithKeys(cfg.Finder), 27 | key.WithHelp(cfg.Finder, "find"), 28 | ), 29 | ScrollUp: key.NewBinding( 30 | key.WithKeys(cfg.ScrollUp), 31 | key.WithHelp(cfg.ScrollUp, "scroll up"), 32 | ), 33 | ScrollDown: key.NewBinding( 34 | key.WithKeys(cfg.ScrollDown), 35 | key.WithHelp(cfg.ScrollDown, "scroll down"), 36 | ), 37 | BackToMenu: key.NewBinding( 38 | key.WithKeys(cfg.BackToMenu), 39 | key.WithHelp(cfg.BackToMenu, "return to menu"), 40 | ), 41 | } 42 | } 43 | 44 | func (m DiaryMap) Bindings() []key.Binding { 45 | var bindings []key.Binding 46 | 47 | v := reflect.ValueOf(m) 48 | for i := 0; i < v.NumField(); i++ { 49 | field := v.Field(i) 50 | if binding, ok := field.Interface().(key.Binding); ok { 51 | bindings = append(bindings, binding) 52 | } 53 | } 54 | 55 | return bindings 56 | } 57 | -------------------------------------------------------------------------------- /docs/docs/config/general.md: -------------------------------------------------------------------------------- 1 | # General Config 2 | 3 | Denoted by the `[general]` tag in TOML. 4 | 5 | ## Notes Directory 6 | 7 | can change the directory where all the notes are stored using the `notes_dir` key. 8 | 9 | default value is: 10 | ```toml 11 | notes_dir = ".toney" 12 | ``` 13 | 14 | ## Editor 15 | 16 | can change the editor for editing notes using the `editor` key, it takes an array. **Make sure to write the command for the editor, not the name**. 17 | 18 | For users with _helix_, use `editor=["alacritty", "-e" , "bash", "-c", "hx"]` or change it according to your term and shell. Helix requires a TTY to launch hence this is required. 19 | 20 | default value is: 21 | ```toml 22 | editor = ["nvim"] 23 | ``` 24 | 25 | ## Start Script 26 | 27 | runs a script/bash command on startup using the `start_script` key, it takes an array. The internal working, adds `bash -c` so you may omit it. 28 | 29 | default value is: 30 | ```toml 31 | start_script = [] 32 | ``` 33 | 34 | ## Stop Script 35 | 36 | runs a script/bash command on quit/exit using the `stop_script` key, it takes an array. The internal working, adds `bash -c` so you may omit it. 37 | 38 | default value is: 39 | ```toml 40 | stop_script = [] 41 | ``` 42 | 43 | ## Script 44 | 45 | runs a script/bash command on keybind using the `script` key, it takes an array. The internal working, adds `bash -c` so you may omit it. 46 | 47 | default value is: 48 | ```toml 49 | script = [] 50 | ``` 51 | -------------------------------------------------------------------------------- /docs/docs/guide/contribute.md: -------------------------------------------------------------------------------- 1 | # Contribution 2 | 3 | Toney is built with Go using [bubbletea](https://github.com/charmbracelet/bubbletea), [lipgloss](https://github.com/charmbracelet/lipgloss) and other supporting libraries. 4 | 5 | ## Project Structure 6 | 7 | ``` 8 | ├── cmd // CLI 9 | └── internal 10 | ├── colors // Styles Config 11 | ├── config // Config 12 | ├── enums // All enums 13 | ├── fileTree // Filtree - manages tree creation for directory 14 | ├── keymap // Keymap 15 | ├── messages // Messages - has all bubbletea message declarations 16 | ├── models // Models - Has all bubbletea models 17 | └── styles // Styles - has all lipgloss styles declarations 18 | ``` 19 | 20 | ## How to Contribute 21 | 22 | 1. **Fork the Repository from Github** 23 | 24 | 2. **Clone the Repository** 25 | ``` 26 | git clone https://github.com//Toney 27 | cd Toney 28 | ``` 29 | 30 | 3. **Install dependencies** 31 | ``` 32 | go get 33 | ``` 34 | 35 | 4. **Build and Run** 36 | ``` 37 | go build 38 | ./Toney 39 | ``` 40 | 41 | ## What can you work on? 42 | 43 | Keep a look at the issues section, there you would find Bugs, Feature Request etc. 44 | 45 | **Please** drop a message in the issue and get it approved before you start working, we can discuss potential ways to structure a solution. 46 | 47 | ## License 48 | 49 | By contributing, you agree to license your work under the MIT License used by this project. 50 | -------------------------------------------------------------------------------- /cmd/sync/remote.go: -------------------------------------------------------------------------------- 1 | package synccmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | 9 | "github.com/SourcewareLab/Toney/internal/config" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | func RemoteCmd() *cobra.Command { 14 | var IsSetting bool 15 | cmd := &cobra.Command{ 16 | Use: "remote", 17 | Short: "set the remote url", 18 | Args: cobra.ExactArgs(1), 19 | RunE: func(cmd *cobra.Command, args []string) error { 20 | if err := config.SetConfig(); err != nil { 21 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 22 | } 23 | 24 | if args[0] == "" { 25 | return fmt.Errorf("Empty remote url found") 26 | } 27 | 28 | remote := args[0] 29 | 30 | home, err := os.UserHomeDir() 31 | if err != nil { 32 | return err 33 | } 34 | 35 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 36 | 37 | if IsSetting { 38 | command := exec.Command("git", "remote", "set-url", "origin", remote) 39 | command.Dir = path 40 | 41 | err = command.Run() 42 | if err != nil { 43 | return err 44 | } 45 | } else { 46 | command := exec.Command("git", "remote", "add", "origin", remote) 47 | command.Dir = path 48 | 49 | err = command.Run() 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | 55 | return nil 56 | }, 57 | } 58 | 59 | cmd.Flags().BoolVarP(&IsSetting, "set", "s", false, "set one you are changing the remote url") 60 | 61 | return cmd 62 | } 63 | -------------------------------------------------------------------------------- /internal/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "runtime" 8 | 9 | "github.com/spf13/viper" 10 | ) 11 | 12 | var AppConfig Config 13 | 14 | func SetConfig() error { 15 | cfg := getCfgDir() 16 | 17 | configDir := filepath.Join(cfg, "toney") 18 | configFile := filepath.Join(configDir, "config.toml") 19 | 20 | // Create config directory if it doesn't exist 21 | if err := os.MkdirAll(configDir, 0755); err != nil { 22 | return fmt.Errorf("failed to create config directory: %w", err) 23 | } 24 | 25 | // Check if config file exists, if not create it with defaults 26 | if _, err := os.Stat(configFile); os.IsNotExist(err) { 27 | AppConfig = DefaultConfig() 28 | viper.SetConfigFile(configFile) 29 | if err := viper.WriteConfig(); err != nil { 30 | return fmt.Errorf("failed to write default config: %w", err) 31 | } 32 | return nil 33 | } 34 | 35 | viper.SetConfigFile(configFile) 36 | if err := viper.ReadInConfig(); err != nil { 37 | return fmt.Errorf("failed to read config: %w", err) 38 | } 39 | 40 | AppConfig = DefaultConfig() 41 | if err := viper.Unmarshal(&AppConfig); err != nil { 42 | return fmt.Errorf("failed to parse config: %w", err) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func getCfgDir() string { 49 | switch runtime.GOOS { 50 | case "linux", "darwin": 51 | home, _ := os.UserHomeDir() 52 | return filepath.Join(home, ".config", "toney") 53 | case "windows": 54 | home, _ := os.UserHomeDir() 55 | return filepath.Join(home, ".toney") 56 | default: 57 | cfg, _ := os.UserConfigDir() 58 | return cfg 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /cmd/init.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | func InitCmd() *cobra.Command { 13 | return &cobra.Command{ 14 | Use: "init", 15 | Short: "Initialize Toney configuration", 16 | Run: func(cmd *cobra.Command, args []string) { 17 | if err := config.SetConfig(); err != nil { 18 | home, _ := os.UserHomeDir() 19 | 20 | err = os.MkdirAll(home+"/.config/toney", 0o755) 21 | if err != nil { 22 | fmt.Println("Could not create config directory", err.Error()) 23 | return 24 | } 25 | 26 | _, err = os.Create(home + "/.config/toney/config.toml") 27 | if err != nil { 28 | fmt.Println("Could not create config file", err.Error()) 29 | return 30 | } 31 | } 32 | 33 | fmt.Println("Initializing Toney...") 34 | home, err := os.UserHomeDir() 35 | if err != nil { 36 | fmt.Println("Could not find $HOME directory", err.Error()) 37 | return 38 | } 39 | 40 | dailyDir := filepath.Join(home, config.AppConfig.General.NotesDir, ".daily") 41 | 42 | err = os.MkdirAll(dailyDir, 0o755) 43 | if err != nil { 44 | fmt.Println("Could not create .toney directory", err.Error()) 45 | return 46 | } 47 | 48 | diaryDir := filepath.Join(home, config.AppConfig.General.NotesDir, ".diary") 49 | 50 | err = os.MkdirAll(diaryDir, 0o755) 51 | if err != nil { 52 | fmt.Println("Could not create .diary directory", err.Error()) 53 | return 54 | } 55 | 56 | fmt.Println("Toney setup was succesfull!") 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cmd/notes/create.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | type CreateOptions struct { 13 | Ext string 14 | Title string 15 | } 16 | 17 | func CreateCmd() *cobra.Command { 18 | opts := &CreateOptions{} 19 | cmd := &cobra.Command{ 20 | Use: "create", 21 | Short: "create a note by relative path", 22 | Example: "toney note create mydir/mynote -e txt -t 'My Note'", 23 | Args: cobra.ExactArgs(1), 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | if err := config.SetConfig(); err != nil { 26 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 27 | } 28 | 29 | if args[0] == "" { 30 | return fmt.Errorf("missing title argument") 31 | } 32 | 33 | filename := fmt.Sprintf("%s.%s", args[0], opts.Ext) 34 | 35 | home, err := os.UserHomeDir() 36 | if err != nil { 37 | return fmt.Errorf("could not get user home directory: %v", err) 38 | } 39 | 40 | path := filepath.Join(home, config.AppConfig.General.NotesDir, filename) 41 | 42 | f, err := os.Create(path) 43 | if err != nil { 44 | return fmt.Errorf("could not create note at %s :-\n%v", path, err) 45 | } 46 | 47 | if opts.Title != "" { 48 | fmt.Fprintf(f, "# %s\n", opts.Title) 49 | } 50 | 51 | fmt.Printf("Successfully created note %s\n", path) 52 | return nil 53 | }, 54 | } 55 | 56 | cmd.Flags().StringVarP(&opts.Ext, "ext", "e", "md", "file extension to use") 57 | cmd.Flags().StringVarP(&opts.Title, "title", "t", "", "title for the note") 58 | 59 | return cmd 60 | } 61 | -------------------------------------------------------------------------------- /cmd/tasks/delete.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | "github.com/SourcewareLab/Toney/internal/models/daily" 9 | "github.com/SourcewareLab/Toney/internal/styles" 10 | "github.com/charmbracelet/huh" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type DeleteOptions struct { 15 | ID int 16 | Confirm bool 17 | } 18 | 19 | func DeleteCmd() *cobra.Command { 20 | opts := &DeleteOptions{} 21 | cmd := &cobra.Command{ 22 | Use: "delete", 23 | Short: "delete a task", 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | if err := config.SetConfig(); err != nil { 26 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 27 | } 28 | 29 | tasks := daily.GetItems() 30 | 31 | options := make([]huh.Option[int], 0) 32 | for _, v := range tasks { 33 | options = append(options, huh.NewOption[int](v.Title(), v.ID)) 34 | } 35 | 36 | form := huh.NewForm( 37 | huh.NewGroup( 38 | huh.NewSelect[int](). 39 | Title("Choose task to delete"). 40 | Value(&opts.ID). 41 | Options( 42 | options..., 43 | ), 44 | ), 45 | huh.NewGroup( 46 | huh.NewConfirm(). 47 | Title(fmt.Sprintf("Delete Task: `%s` ?", tasks[opts.ID].Title())). 48 | Value(&opts.Confirm), 49 | ), 50 | ).WithTheme(styles.HuhTheme()) 51 | 52 | form.Run() 53 | 54 | if !opts.Confirm { 55 | return nil 56 | } 57 | 58 | fmt.Printf("Deleted Task: `%s`\n", tasks[opts.ID].Title()) 59 | tasks = slices.Delete(tasks, opts.ID-1, opts.ID) 60 | 61 | daily.WriteItems(tasks) 62 | 63 | return nil 64 | }, 65 | } 66 | 67 | return cmd 68 | } 69 | -------------------------------------------------------------------------------- /internal/models/daily/handlers.go: -------------------------------------------------------------------------------- 1 | package daily 2 | 3 | import ( 4 | "slices" 5 | 6 | "github.com/SourcewareLab/Toney/internal/enums" 7 | "github.com/SourcewareLab/Toney/internal/messages" 8 | ) 9 | 10 | func (m Daily) CreateTask(msg messages.TaskPopupMessage, isUnique bool) { 11 | task := Task{ 12 | TaskTitle: msg.Title, 13 | TaskDesc: msg.Desc, 14 | Status: msg.Status, 15 | } 16 | 17 | if isUnique { 18 | task.TaskType = enums.UniqueTask 19 | } else { 20 | task.TaskType = enums.RecurringTask 21 | } 22 | 23 | m.Tasks = append(m.Tasks, task) 24 | 25 | WriteItems(m.Tasks) 26 | } 27 | 28 | func (m Daily) DeleteTask(msg messages.TaskPopupMessage) { 29 | if !msg.IsDeleted { 30 | return 31 | } 32 | 33 | item := m.List.SelectedItem() 34 | 35 | task, ok := item.(Task) 36 | if !ok { // Making sure that item is of type Task 37 | return 38 | } 39 | 40 | m.Tasks = slices.Delete(m.Tasks, task.ID, task.ID+1) 41 | 42 | WriteItems(m.Tasks) 43 | } 44 | 45 | func (m Daily) StatusChangeTask(msg messages.TaskPopupMessage) { 46 | item := m.List.SelectedItem() 47 | 48 | task, ok := item.(Task) 49 | if !ok { // Making sure that item is of type Task 50 | return 51 | } 52 | 53 | task.Status = msg.Status 54 | 55 | m.Tasks[task.ID] = task 56 | 57 | WriteItems(m.Tasks) 58 | } 59 | 60 | func (m Daily) EditTask(msg messages.TaskPopupMessage) { 61 | task := Task{ 62 | TaskTitle: msg.Title, 63 | TaskDesc: msg.Desc, 64 | Status: msg.Status, 65 | } 66 | 67 | item := m.List.SelectedItem() 68 | 69 | oldTask, ok := item.(Task) 70 | if !ok { // Making sure that item is of type Task 71 | return 72 | } 73 | 74 | task.Status = oldTask.Status 75 | 76 | m.Tasks[oldTask.ID] = task 77 | 78 | WriteItems(m.Tasks) 79 | } 80 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/daily.md: -------------------------------------------------------------------------------- 1 | # Daily Keybinds 2 | 3 | Denoted by the `[keybinds.daily]` tag in TOML. 4 | 5 | ## Create Task 6 | 7 | can change the keybind for creating a task using the `create` toml key. 8 | 9 | default value is: 10 | ```toml 11 | create = "c" 12 | ``` 13 | 14 | ## Edit Task 15 | 16 | can change the keybind for editing a task using the `edit` toml key. 17 | 18 | default value is: 19 | ```toml 20 | edit = "e" 21 | ``` 22 | 23 | ## Change Status 24 | 25 | can change the keybind for changing task status using the `status_change` toml key. 26 | 27 | default value is: 28 | ```toml 29 | status_change = "s" 30 | ``` 31 | 32 | ## Delete Task 33 | 34 | can change the keybind for deleting a task using the `delete` toml key. 35 | 36 | default value is: 37 | ```toml 38 | delete = "d" 39 | ``` 40 | 41 | ## Exit Popup 42 | 43 | can change the keybind for closing the popup using the `exit_popup` toml key. 44 | 45 | default value is: 46 | ```toml 47 | exit_popup = "esc" 48 | ``` 49 | 50 | ## Submit Form 51 | 52 | can change the keybind for submitting the form using the `enter` toml key. 53 | 54 | default value is: 55 | ```toml 56 | enter = "enter" 57 | ``` 58 | 59 | ## Form Up 60 | 61 | can change the keybind for moving one input field up in the form using the `form_up` toml key. 62 | 63 | default value is: 64 | ```toml 65 | form_up = "ctrl+up" 66 | ``` 67 | 68 | ## Form Down 69 | 70 | can change the keybind for moving one input field down in the form using the `form_down` toml key. 71 | 72 | default value is: 73 | ```toml 74 | form_down = "ctrl+down" 75 | ``` 76 | 77 | ## Return to Menu 78 | 79 | can change the keybind for returning to the main menu using the `return_to_menu` toml key. 80 | 81 | default value is: 82 | ```toml 83 | return_to_menu = "esc" 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /internal/models/error/error.go: -------------------------------------------------------------------------------- 1 | package errorpopup 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/SourcewareLab/Toney/internal/colors" 8 | tea "github.com/charmbracelet/bubbletea" 9 | "github.com/charmbracelet/lipgloss" 10 | ) 11 | 12 | type ErrorPopup struct { 13 | Width int 14 | Height int 15 | Message string 16 | Title string 17 | Location string 18 | } 19 | 20 | func NewErrorPopup(w, h int, msg, title, location string) *ErrorPopup { 21 | return &ErrorPopup{ 22 | Width: w, 23 | Height: h, 24 | Message: WrapAndLimit(msg, 20, 3), 25 | Title: title, 26 | Location: location, 27 | } 28 | } 29 | 30 | func (s *ErrorPopup) Init() tea.Cmd { return nil } 31 | 32 | func (s *ErrorPopup) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 33 | return s, nil 34 | } 35 | 36 | func (s *ErrorPopup) View() string { 37 | style := lipgloss.NewStyle() 38 | 39 | text := fmt.Sprintf("%s\n\n%s", style.Foreground(colors.ColorPalette().ErrorText).Render(s.Title+" | "+s.Location), 40 | 41 | style.Foreground(colors.ColorPalette().Text).Render(s.Message)) 42 | 43 | return lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().FocusedBorder). 44 | Background(colors.ColorPalette().ErrorBg).Padding(1, 3).Render(text) 45 | } 46 | 47 | // Wraps the text to the given length and also limits no. of lines, adds a "..." line if exceeding. 48 | func WrapAndLimit(s string, maxLen, maxLines int) string { 49 | var lines []string 50 | 51 | for i := 0; i < len(s); i += maxLen { 52 | end := i + maxLen 53 | if end > len(s) { 54 | end = len(s) 55 | } 56 | lines = append(lines, s[i:end]) 57 | } 58 | 59 | if len(lines) > maxLines { 60 | lines = append(lines[:maxLines], "...") 61 | } 62 | 63 | return strings.Join(lines, "\n") 64 | } 65 | -------------------------------------------------------------------------------- /internal/keymap/taskpopup.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/charmbracelet/bubbles/key" 8 | ) 9 | 10 | type TaskPopupMap struct { 11 | Enter key.Binding 12 | Exit key.Binding 13 | } 14 | 15 | func NewTaskPopupMap() TaskPopupMap { 16 | cfg := config.AppConfig.Keybinds.Daily 17 | return TaskPopupMap{ 18 | Enter: key.NewBinding( 19 | key.WithKeys(cfg.Enter), 20 | key.WithHelp(cfg.Enter, "Enter"), 21 | ), 22 | Exit: key.NewBinding( 23 | key.WithKeys(cfg.ExitPopup), 24 | key.WithHelp(cfg.ExitPopup, "Exit"), 25 | ), 26 | } 27 | } 28 | 29 | func (m TaskPopupMap) Bindings() []key.Binding { 30 | var bindings []key.Binding 31 | 32 | v := reflect.ValueOf(m) 33 | for i := 0; i < v.NumField(); i++ { 34 | field := v.Field(i) 35 | if binding, ok := field.Interface().(key.Binding); ok { 36 | bindings = append(bindings, binding) 37 | } 38 | } 39 | 40 | return bindings 41 | } 42 | 43 | type TaskFormMap struct { 44 | MoveDown key.Binding 45 | MoveUp key.Binding 46 | } 47 | 48 | func NewTaskFormMap() TaskFormMap { 49 | cfg := config.AppConfig.Keybinds.Daily 50 | return TaskFormMap{ 51 | MoveUp: key.NewBinding( 52 | key.WithKeys(cfg.FormUp), 53 | key.WithHelp(cfg.FormUp, "Move Up"), 54 | ), 55 | MoveDown: key.NewBinding( 56 | key.WithKeys(cfg.FormDown), 57 | key.WithHelp(cfg.FormDown, "Move Down"), 58 | ), 59 | } 60 | } 61 | 62 | func (m TaskFormMap) Bindings() []key.Binding { 63 | var bindings []key.Binding 64 | 65 | v := reflect.ValueOf(m) 66 | for i := 0; i < v.NumField(); i++ { 67 | field := v.Field(i) 68 | if binding, ok := field.Interface().(key.Binding); ok { 69 | bindings = append(bindings, binding) 70 | } 71 | } 72 | 73 | return bindings 74 | } 75 | -------------------------------------------------------------------------------- /cmd/notes/render.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/charmbracelet/glamour" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | type RenderOptions struct { 14 | Raw bool 15 | } 16 | 17 | func RenderCmd() *cobra.Command { 18 | opts := &RenderOptions{} 19 | cmd := &cobra.Command{ 20 | Use: "render", 21 | Short: "render a note by relative path", 22 | Example: "toney note render mydir/mynote --raw", 23 | Args: cobra.ExactArgs(1), 24 | RunE: func(cmd *cobra.Command, args []string) error { 25 | if err := config.SetConfig(); err != nil { 26 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 27 | } 28 | 29 | if args[0] == "" { 30 | return fmt.Errorf("missing title argument") 31 | } 32 | 33 | home, err := os.UserHomeDir() 34 | if err != nil { 35 | return fmt.Errorf("could not get user home directory: %v", err) 36 | } 37 | 38 | path := filepath.Join(home, config.AppConfig.General.NotesDir, args[0]) 39 | 40 | content, err := os.ReadFile(path) 41 | if err != nil { 42 | return fmt.Errorf("could not create note at %s :-\n%v", path, err) 43 | } 44 | 45 | if opts.Raw { 46 | fmt.Print(string(content)) 47 | } else { 48 | r, err := glamour.NewTermRenderer(glamour.WithStyles(config.ToGlamourStyle(config.AppConfig.Styles.Renderer))) 49 | if err != nil { 50 | return fmt.Errorf("could not create renderer from config :-\n%v", err) 51 | } 52 | 53 | fmt.Print(r.Render(string(content))) 54 | } 55 | 56 | return nil 57 | }, 58 | } 59 | 60 | cmd.Flags().BoolVarP(&opts.Raw, "raw", "r", false, "view raw file without styles") 61 | 62 | return cmd 63 | } 64 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/_metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "hash": "4a089679", 3 | "configHash": "1f790002", 4 | "lockfileHash": "018b6896", 5 | "browserHash": "a8cc9f57", 6 | "optimized": { 7 | "vue": { 8 | "src": "../../../../node_modules/vue/dist/vue.runtime.esm-bundler.js", 9 | "file": "vue.js", 10 | "fileHash": "6eae2039", 11 | "needsInterop": false 12 | }, 13 | "vitepress > @vue/devtools-api": { 14 | "src": "../../../../node_modules/@vue/devtools-api/dist/index.js", 15 | "file": "vitepress___@vue_devtools-api.js", 16 | "fileHash": "b1fe2616", 17 | "needsInterop": false 18 | }, 19 | "vitepress > @vueuse/core": { 20 | "src": "../../../../node_modules/@vueuse/core/index.mjs", 21 | "file": "vitepress___@vueuse_core.js", 22 | "fileHash": "601f23e2", 23 | "needsInterop": false 24 | }, 25 | "vitepress > @vueuse/integrations/useFocusTrap": { 26 | "src": "../../../../node_modules/@vueuse/integrations/useFocusTrap.mjs", 27 | "file": "vitepress___@vueuse_integrations_useFocusTrap.js", 28 | "fileHash": "2d69a55e", 29 | "needsInterop": false 30 | }, 31 | "vitepress > mark.js/src/vanilla.js": { 32 | "src": "../../../../node_modules/mark.js/src/vanilla.js", 33 | "file": "vitepress___mark__js_src_vanilla__js.js", 34 | "fileHash": "de3e8b33", 35 | "needsInterop": false 36 | }, 37 | "vitepress > minisearch": { 38 | "src": "../../../../node_modules/minisearch/dist/es/index.js", 39 | "file": "vitepress___minisearch.js", 40 | "fileHash": "f98b5f61", 41 | "needsInterop": false 42 | } 43 | }, 44 | "chunks": { 45 | "chunk-3GYA4YLH": { 46 | "file": "chunk-3GYA4YLH.js" 47 | }, 48 | "chunk-DDXJJ377": { 49 | "file": "chunk-DDXJJ377.js" 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /docs/docs/config/styles/taskstyles.md: -------------------------------------------------------------------------------- 1 | # Task Styles 2 | 3 | Denoted by the `[styles.task_styles]` tag in TOML. 4 | 5 | ## Focused Bar 6 | 7 | can change the color of the focus bar when focused in daily tasks page using the `focused_bar` key. 8 | 9 | default value is: 10 | ```toml 11 | focused_bar = "#b4befe" 12 | ``` 13 | 14 | ## Unfocused Bar 15 | 16 | can change the color of the focus bar in daily tasks page using the `unfocused_bar` key. 17 | 18 | default value is: 19 | ```toml 20 | unfocused_bar = "#45475a" 21 | ``` 22 | 23 | ## Completed Task 24 | 25 | can change the text and description font color using the `[styles.task_styles.completed_style]` key. 26 | 27 | ### Title 28 | 29 | default value is: 30 | ```toml 31 | title = "#a6e3a1" 32 | ``` 33 | 34 | ### Description 35 | 36 | default value is: 37 | ```toml 38 | desc = "#5a7a57" 39 | ``` 40 | 41 | ## Pending Task 42 | 43 | can change the text and description font color using the `[styles.task_styles.pending_style]` key. 44 | 45 | ### Title 46 | 47 | default value is: 48 | ```toml 49 | title = "#6c7086" 50 | ``` 51 | 52 | ### Description 53 | 54 | default value is: 55 | ```toml 56 | desc = "#313244" 57 | ``` 58 | 59 | ## Started Task 60 | 61 | can change the text and description font color using the `[styles.task_styles.started_style]` key. 62 | 63 | ### Title 64 | 65 | default value is: 66 | ```toml 67 | title = "#f9e2af" 68 | ``` 69 | 70 | ### Description 71 | 72 | default value is: 73 | ```toml 74 | desc = "#a38e65" 75 | ``` 76 | 77 | ## Abandoned Task 78 | 79 | can change the text and description font color using the `[styles.task_styles.abandoned_style]` key. 80 | 81 | ### Title 82 | 83 | default value is: 84 | ```toml 85 | title = "#f38ba8" 86 | ``` 87 | 88 | ### Description 89 | 90 | default value is: 91 | ```toml 92 | desc = "#894454" 93 | ``` 94 | 95 | -------------------------------------------------------------------------------- /internal/models/taskPopup/delete.go: -------------------------------------------------------------------------------- 1 | package taskpopup 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SourcewareLab/Toney/internal/colors" 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | tea "github.com/charmbracelet/bubbletea" 9 | "github.com/charmbracelet/lipgloss" 10 | ) 11 | 12 | type DeleteForm struct { 13 | Width int 14 | Height int 15 | isDeleting bool 16 | } 17 | 18 | func NewDeleteForm(w int, h int) *DeleteForm { 19 | return &DeleteForm{ 20 | Width: w, 21 | Height: h, 22 | isDeleting: true, 23 | } 24 | } 25 | 26 | func (m DeleteForm) Init() tea.Cmd { 27 | return nil 28 | } 29 | 30 | func (m *DeleteForm) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 31 | switch msg := msg.(type) { 32 | case tea.KeyMsg: 33 | switch msg.String() { 34 | case config.AppConfig.Keybinds.Global.Down: 35 | if m.isDeleting { 36 | m.isDeleting = !m.isDeleting 37 | } 38 | return m, nil 39 | case config.AppConfig.Keybinds.Global.Up: 40 | if !m.isDeleting { 41 | m.isDeleting = !m.isDeleting 42 | } 43 | return m, nil 44 | } 45 | } 46 | 47 | return m, nil 48 | } 49 | 50 | func (m *DeleteForm) View() string { 51 | return lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().Border).Render(m.GetText()) 52 | } 53 | 54 | func (m DeleteForm) GetText() string { 55 | yes := "Yes" 56 | no := "No" 57 | style := lipgloss.NewStyle().Width(m.Width).Foreground(colors.ColorPalette().Text) 58 | 59 | if m.isDeleting { 60 | yes = style.Background(colors.ColorPalette().MenuSelectedBg). 61 | Foreground(colors.ColorPalette().MenuSelectedText). 62 | Render(yes) 63 | no = style.Render(no) 64 | } else { 65 | no = style.Background(colors.ColorPalette().MenuSelectedBg). 66 | Foreground(colors.ColorPalette().MenuSelectedText). 67 | Render(no) 68 | yes = style.Render(yes) 69 | } 70 | 71 | return fmt.Sprintf("%s\n%s", yes, no) 72 | } 73 | -------------------------------------------------------------------------------- /internal/keymap/dailytasks.go: -------------------------------------------------------------------------------- 1 | package keymap 2 | 3 | import ( 4 | "reflect" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/charmbracelet/bubbles/key" 8 | ) 9 | 10 | type DailyTaskMap struct { 11 | CreateUnique key.Binding 12 | CreateRecurring key.Binding 13 | EditTask key.Binding 14 | ChangeStatus key.Binding 15 | DeleteTask key.Binding 16 | BackToMenu key.Binding 17 | TabRight key.Binding 18 | TabLeft key.Binding 19 | } 20 | 21 | func NewDailyTaskMap() DailyTaskMap { 22 | cfg := config.AppConfig.Keybinds.Daily 23 | return DailyTaskMap{ 24 | CreateUnique: key.NewBinding( 25 | key.WithKeys(cfg.Create), 26 | key.WithHelp(cfg.Create, "create"), 27 | ), 28 | CreateRecurring: key.NewBinding( 29 | key.WithKeys(cfg.CreateRecurring), 30 | key.WithHelp(cfg.CreateRecurring, "new recurring"), 31 | ), 32 | EditTask: key.NewBinding( 33 | key.WithKeys(cfg.Edit), 34 | key.WithHelp(cfg.Edit, "edit"), 35 | ), 36 | ChangeStatus: key.NewBinding( 37 | key.WithKeys(cfg.StatusChange), 38 | key.WithHelp(cfg.StatusChange, "status change"), 39 | ), 40 | DeleteTask: key.NewBinding( 41 | key.WithKeys(cfg.Delete), 42 | key.WithHelp(cfg.Delete, "delete"), 43 | ), 44 | BackToMenu: key.NewBinding( 45 | key.WithKeys(cfg.BackToMenu), 46 | key.WithHelp(cfg.BackToMenu, "return to menu"), 47 | ), 48 | TabRight: key.NewBinding( 49 | key.WithKeys("right"), 50 | key.WithHelp("right", "right tab"), 51 | ), 52 | TabLeft: key.NewBinding( 53 | key.WithKeys("left"), 54 | key.WithHelp("left", "left tab"), 55 | ), 56 | } 57 | } 58 | 59 | func (m DailyTaskMap) Bindings() []key.Binding { 60 | var bindings []key.Binding 61 | 62 | v := reflect.ValueOf(m) 63 | for i := 0; i < v.NumField(); i++ { 64 | field := v.Field(i) 65 | if binding, ok := field.Interface().(key.Binding); ok { 66 | bindings = append(bindings, binding) 67 | } 68 | } 69 | 70 | return bindings 71 | } 72 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strings" 11 | 12 | "github.com/SourcewareLab/Toney/cmd/notes" 13 | synccmd "github.com/SourcewareLab/Toney/cmd/sync" 14 | "github.com/SourcewareLab/Toney/cmd/tasks" 15 | "github.com/SourcewareLab/Toney/internal/config" 16 | "github.com/SourcewareLab/Toney/internal/models" 17 | tea "github.com/charmbracelet/bubbletea" 18 | "github.com/charmbracelet/fang" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | func RootCmd() *cobra.Command { 23 | cmd := &cobra.Command{ 24 | Use: "toney", 25 | Short: "Toney is a TUI for developers", 26 | Long: `Toney is a powerful terminal UI for managing your workflows and repositories.`, 27 | Run: func(cmd *cobra.Command, args []string) { 28 | if err := config.SetConfig(); err != nil { 29 | log.Printf("Config error: %v\n", err) 30 | log.Println("Try running 'toney init' to initialize configuration") 31 | return 32 | } 33 | 34 | if len(config.AppConfig.General.StartScript) > 0 { 35 | script := strings.Join(config.AppConfig.General.StartScript, " ") 36 | var command *exec.Cmd 37 | 38 | switch runtime.GOOS { 39 | case "windows": 40 | command = exec.Command("cmd", "/C", script) 41 | default: 42 | command = exec.Command("sh", "-c", script) 43 | } 44 | 45 | command.Stdout = os.Stdout 46 | command.Stderr = os.Stderr 47 | 48 | if err := command.Run(); err != nil { 49 | log.Printf("Script execution error: %v\n", err) 50 | } 51 | } 52 | 53 | p := tea.NewProgram(models.NewRoot(), tea.WithAltScreen()) 54 | if _, err := p.Run(); err != nil { 55 | fmt.Printf("Toney error: %v\n", err) 56 | } 57 | }, 58 | } 59 | 60 | cmd.AddCommand( 61 | InitCmd(), 62 | DumpCmd(), 63 | notes.NotesCmd(), 64 | tasks.TasksCmd(), 65 | synccmd.SyncCmd(), 66 | ) 67 | 68 | return cmd 69 | } 70 | 71 | func Execute() { 72 | fang.Execute(context.Background(), RootCmd()) 73 | } 74 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | project_name: toney 4 | release: 5 | github: 6 | owner: SourcewareLab 7 | name: toney 8 | 9 | builds: 10 | - id: toney 11 | main: . 12 | goos: 13 | - linux 14 | - windows 15 | - darwin 16 | - freebsd 17 | - netbsd 18 | - openbsd 19 | goarch: 20 | - amd64 21 | - arm64 22 | ldflags: 23 | - -s -w 24 | binary: toney 25 | 26 | archives: 27 | - id: archive 28 | name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}" 29 | format_overrides: 30 | - goos: windows 31 | formats: ['zip'] 32 | 33 | homebrew_casks: 34 | - name: toney 35 | binary: toney 36 | description: "Toney - a fast, lightweight terminal-based note-taking app" 37 | homepage: "https://sourcewarelab.github.io/Toney/" 38 | url: 39 | template: "https://github.com/SourcewareLab/Toney/releases/download/v{{ .Version }}/toney_darwin_amd64.tar.gz" 40 | uninstall: 41 | delete: 42 | - "/usr/local/bin/toney" 43 | repository: 44 | owner: SourcewareLab 45 | name: homebrew-tap 46 | token: "{{ .Env.GITHUB_TOKEN }}" 47 | commit_author: 48 | name: NucleoFusion 49 | email: lakshit.singh.mail@gmail.com 50 | 51 | nfpms: 52 | - id: deb 53 | formats: 54 | - deb 55 | section: utils 56 | priority: optional 57 | maintainer: "NucleoFusion " 58 | description: "Toney - a fast, lightweight terminal-based note-taking app" 59 | license: MIT 60 | vendor: NucleoFusion 61 | homepage: https://sourcewarelab.github.io/Toney/ 62 | 63 | - id: rpm 64 | formats: 65 | - rpm 66 | section: utils 67 | priority: optional 68 | maintainer: "NucleoFusion " 69 | description: "Toney - a fast, lightweight terminal-based note-taking app" 70 | license: MIT 71 | vendor: NucleoFusion 72 | homepage: https://sourcewarelab.github.io/Toney/ 73 | 74 | checksum: 75 | name_template: 'checksums.txt' 76 | 77 | 78 | -------------------------------------------------------------------------------- /cmd/notes/edit.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/SourcewareLab/Toney/internal/config" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type EditOptions struct { 15 | Editor string 16 | } 17 | 18 | func EditCmd() *cobra.Command { 19 | opts := &EditOptions{} 20 | cmd := &cobra.Command{ 21 | Use: "edit", 22 | Short: "edit a note by relative path", 23 | Example: "toney note edit -e 'alacritty -e bash -c hx'", 24 | Args: cobra.ExactArgs(1), 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | if err := config.SetConfig(); err != nil { 27 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 28 | } 29 | 30 | if args[0] == "" { 31 | return fmt.Errorf("missing filepath argument") 32 | } 33 | 34 | home, err := os.UserHomeDir() 35 | if err != nil { 36 | return fmt.Errorf("could not get user home directory: %v", err) 37 | } 38 | 39 | path := filepath.Join(home, config.AppConfig.General.NotesDir, args[0]) 40 | 41 | editorcmd := "" 42 | editorargs := make([]string, 0) 43 | if opts.Editor == "" { 44 | cfg := config.AppConfig.General.Editor 45 | if len(cfg) < 1 { 46 | return fmt.Errorf("editor command in config is empty") 47 | } 48 | 49 | editorcmd = cfg[0] 50 | 51 | if len(cfg) >= 2 { 52 | editorargs = cfg[1:] 53 | } 54 | } else { 55 | cmdargs := strings.Split(opts.Editor, " ") 56 | 57 | editorcmd = cmdargs[0] 58 | if len(cmdargs) >= 2 { 59 | editorargs = cmdargs[1:] 60 | } 61 | } 62 | 63 | editorargs = append(editorargs, path) 64 | 65 | execcmd := exec.Command(editorcmd, editorargs...) 66 | 67 | execcmd.Stdin = os.Stdin 68 | execcmd.Stdout = os.Stdout 69 | execcmd.Stderr = os.Stderr 70 | 71 | if err := execcmd.Run(); err != nil { 72 | os.Exit(1) 73 | } 74 | 75 | return nil 76 | }, 77 | } 78 | 79 | cmd.Flags().StringVarP(&opts.Editor, "editor", "e", "", "editor to open note, will be split ' ' and then computed") 80 | 81 | return cmd 82 | } 83 | -------------------------------------------------------------------------------- /docs/vitepress.theme.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Backgrounds */ 3 | --vp-c-bg: #1e1e2e; /* base */ 4 | --vp-c-bg-alt: #181825; /* mantle */ 5 | --vp-c-bg-elv: #1e1e2e; /* base */ 6 | --vp-c-bg-soft: #11111b; /* crust */ 7 | 8 | /* Borders */ 9 | --vp-c-border: #313244; /* surface0 */ 10 | --vp-c-divider: #45475a; /* surface1 */ 11 | --vp-c-gutter: #585b70; /* surface2 */ 12 | 13 | /* Text */ 14 | --vp-c-text-1: #cdd6f4; /* text */ 15 | --vp-c-text-2: #bac2de; /* subtext1 */ 16 | --vp-c-text-3: #a6adc8; /* subtext0 */ 17 | 18 | /* Brand / Accent */ 19 | --vp-c-brand-1: #89dceb; 20 | --vp-c-brand-2: #74c7ec; 21 | --vp-c-brand-3: #89b4fa; 22 | 23 | /* Semantic Colors */ 24 | --vp-c-green-1: #a6e3a1; 25 | --vp-c-green-2: #94e2d5; 26 | --vp-c-green-3: #74c7ec; 27 | 28 | --vp-c-purple-1: #cba6f7; 29 | --vp-c-purple-2: #b4befe; 30 | --vp-c-purple-3: #a6adc8; 31 | 32 | --vp-c-yellow-1: #f9e2af; 33 | --vp-c-yellow-2: #f5e0dc; 34 | --vp-c-yellow-3: #fab387; 35 | 36 | --vp-c-red-1: #f38ba8; 37 | --vp-c-red-2: #eba0ac; 38 | --vp-c-red-3: #f38ba8; 39 | 40 | --vp-c-gray-1: #313244; /* surface0 */ 41 | --vp-c-gray-2: #45475a; /* surface1 */ 42 | --vp-c-gray-3: #585b70; /* surface2 */ 43 | --vp-c-gray-soft: rgba(205, 214, 244, 0.1); /* faded text */ 44 | 45 | /* Sponsor */ 46 | --vp-c-sponsor: #f5c2e7; /* pink */ 47 | } 48 | 49 | .dark { 50 | --vp-c-bg: #1e1e2e; 51 | --vp-c-bg-alt: #181825; 52 | --vp-c-bg-elv: #1e1e2e; 53 | --vp-c-bg-soft: #11111b; 54 | 55 | --vp-c-border: #313244; 56 | --vp-c-divider: #45475a; 57 | --vp-c-gutter: #585b70; 58 | 59 | --vp-c-text-1: #cdd6f4; 60 | --vp-c-text-2: #bac2de; 61 | --vp-c-text-3: #a6adc8; 62 | 63 | --vp-c-brand-1: #89dceb; 64 | --vp-c-brand-2: #74c7ec; 65 | --vp-c-brand-3: #89b4fa; 66 | } 67 | 68 | .VPNavBar .VPNavBarTitle { 69 | font-size: 1.5em; 70 | font-weight: bold; 71 | background: linear-gradient(120deg, var(--vp-c-brand-2), var(--vp-c-brand-1)); 72 | -webkit-background-clip: text; 73 | -webkit-text-fill-color: transparent; 74 | background-clip: text; 75 | color: transparent; 76 | } 77 | 78 | /* .VPSwitchAppearance { */ 79 | /* display: none !important; */ 80 | /* } */ 81 | -------------------------------------------------------------------------------- /internal/colors/colors.go: -------------------------------------------------------------------------------- 1 | package colors 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/config" 5 | "github.com/charmbracelet/lipgloss" 6 | ) 7 | 8 | type Colors struct { 9 | Text lipgloss.Color 10 | Background lipgloss.Color 11 | Border lipgloss.Color 12 | FocusedBorder lipgloss.Color 13 | MenuSelectedBg lipgloss.Color 14 | MenuSelectedText lipgloss.Color 15 | TaskFocusedBar lipgloss.Color 16 | TaskUnfocusedBar lipgloss.Color 17 | ErrorBg lipgloss.Color 18 | ErrorText lipgloss.Color 19 | CompletedTask TaskColors 20 | AbandonedTask TaskColors 21 | PendingTask TaskColors 22 | StartedTask TaskColors 23 | } 24 | 25 | type TaskColors struct { 26 | TaskTitle lipgloss.Color 27 | TaskDesc lipgloss.Color 28 | } 29 | 30 | func ColorPalette() Colors { 31 | cfg := config.AppConfig.Styles 32 | return Colors{ 33 | Text: lipgloss.Color(cfg.Text), 34 | Background: lipgloss.Color(cfg.Background), 35 | Border: lipgloss.Color(cfg.Border), 36 | FocusedBorder: lipgloss.Color(cfg.FocusedBorder), 37 | TaskFocusedBar: lipgloss.Color(cfg.TaskStyles.FocusedBar), 38 | TaskUnfocusedBar: lipgloss.Color(cfg.TaskStyles.UnfocusedBar), 39 | MenuSelectedBg: lipgloss.Color(cfg.MenuSelectedBg), 40 | MenuSelectedText: lipgloss.Color(cfg.MenuSelectedText), 41 | ErrorBg: lipgloss.Color(cfg.ErrorBg), 42 | ErrorText: lipgloss.Color(cfg.ErrorText), 43 | CompletedTask: TaskColors{ 44 | TaskTitle: lipgloss.Color(cfg.TaskStyles.CompletedStyle.Title), 45 | TaskDesc: lipgloss.Color(cfg.TaskStyles.CompletedStyle.Desc), 46 | }, 47 | AbandonedTask: TaskColors{ 48 | TaskTitle: lipgloss.Color(cfg.TaskStyles.AbandonedStyle.Title), 49 | TaskDesc: lipgloss.Color(cfg.TaskStyles.AbandonedStyle.Desc), 50 | }, 51 | PendingTask: TaskColors{ 52 | TaskTitle: lipgloss.Color(cfg.TaskStyles.PendingStyle.Title), 53 | TaskDesc: lipgloss.Color(cfg.TaskStyles.PendingStyle.Desc), 54 | }, 55 | StartedTask: TaskColors{ 56 | TaskTitle: lipgloss.Color(cfg.TaskStyles.StartedStyle.Title), 57 | TaskDesc: lipgloss.Color(cfg.TaskStyles.StartedStyle.Desc), 58 | }, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /internal/models/taskPopup/select.go: -------------------------------------------------------------------------------- 1 | package taskpopup 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/SourcewareLab/Toney/internal/colors" 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | "github.com/SourcewareLab/Toney/internal/enums" 9 | tea "github.com/charmbracelet/bubbletea" 10 | "github.com/charmbracelet/lipgloss" 11 | ) 12 | 13 | type SelectStatus struct { 14 | Width int 15 | Height int 16 | Opts []enums.TaskStatus 17 | TitleMap map[enums.TaskStatus]string 18 | Selected int 19 | } 20 | 21 | func NewSelect(w int, h int) *SelectStatus { 22 | return &SelectStatus{ 23 | Width: w, 24 | Height: h, 25 | Opts: []enums.TaskStatus{enums.Pending, enums.Started, enums.Abandoned, enums.Complete}, 26 | TitleMap: map[enums.TaskStatus]string{ 27 | enums.Started: "Started", 28 | enums.Pending: "Pending", 29 | enums.Complete: "Complete", 30 | enums.Abandoned: "Abandoned", 31 | }, 32 | } 33 | } 34 | 35 | func (m SelectStatus) Init() tea.Cmd { 36 | return nil 37 | } 38 | 39 | func (m *SelectStatus) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 40 | switch msg := msg.(type) { 41 | case tea.KeyMsg: 42 | switch msg.String() { 43 | case config.AppConfig.Keybinds.Global.Down: 44 | if m.Selected < len(m.Opts)-1 { 45 | m.Selected += 1 46 | } 47 | return m, nil 48 | case config.AppConfig.Keybinds.Global.Up: 49 | if m.Selected > 0 { 50 | m.Selected -= 1 51 | } 52 | return m, nil 53 | } 54 | } 55 | 56 | return m, nil 57 | } 58 | 59 | func (m *SelectStatus) View() string { 60 | return lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().Border).Render(m.GetText()) 61 | } 62 | 63 | func (m SelectStatus) GetText() string { 64 | text := "" 65 | style := lipgloss.NewStyle().Width(m.Width).Padding(0, 2).Foreground(colors.ColorPalette().Text) 66 | 67 | for idx, val := range m.Opts { 68 | line := m.Opts[val] 69 | if m.Selected == idx { 70 | text += style.Background(colors.ColorPalette().MenuSelectedBg). 71 | Foreground(colors.ColorPalette().MenuSelectedText). 72 | Render(m.TitleMap[line]) + "\n" 73 | 74 | continue 75 | } 76 | 77 | text += style.Render(m.TitleMap[line]) + "\n" 78 | } 79 | 80 | return strings.TrimSuffix(text, "\n") 81 | } 82 | -------------------------------------------------------------------------------- /docs/docs/config/keybinds/home.md: -------------------------------------------------------------------------------- 1 | # Home Keybinds 2 | 3 | Denoted by the `[keybinds.home]` tag in TOML. 4 | 5 | 6 | ## Focus Viewer 7 | 8 | You can change the keybind for focusing the file viewer using the `focus_viewer` TOML key. 9 | 10 | Default value: 11 | ```toml 12 | focus_viewer = "V" 13 | ``` 14 | 15 | ## Focus Filetree 16 | 17 | You can change the keybind for focusing the file tree using the `focus_explorer` TOML key. 18 | 19 | Default value: 20 | ```toml 21 | focus_explorer = "F" 22 | ``` 23 | 24 | ## Finder 25 | 26 | You can change the keybind for opening the fuzzy finder using the `finder` TOML key. 27 | 28 | Default value: 29 | ```toml 30 | finder = "f" 31 | ``` 32 | 33 | ## Create File/Folder 34 | 35 | You can change the keybind for creating a new file or folder using the `create` TOML key. 36 | 37 | Default value: 38 | ```toml 39 | create = "c" 40 | ``` 41 | 42 | ## Rename File/Folder 43 | 44 | You can change the keybind for renaming a file or folder using the `rename` TOML key. 45 | 46 | Default value: 47 | ```toml 48 | rename = "r" 49 | ``` 50 | 51 | ## Scroll Up 52 | 53 | You can change the keybind for scrolling up using the `scroll_up` TOML key. 54 | 55 | Default value: 56 | ```toml 57 | scroll_up = "up" 58 | ``` 59 | 60 | ## Scroll Down 61 | 62 | You can change the keybind for scrolling down using the `scroll_down` TOML key. 63 | 64 | Default value: 65 | ```toml 66 | scroll_down = "down" 67 | ``` 68 | 69 | ## Move File/Folder 70 | 71 | You can change the keybind for moving a file or folder using the `move` TOML key. 72 | 73 | Default value: 74 | ```toml 75 | move = "m" 76 | ``` 77 | 78 | ## Delete File/Folder 79 | 80 | You can change the keybind for deleting a file or folder using the `delete` TOML key. 81 | 82 | Default value: 83 | ```toml 84 | delete = "d" 85 | ``` 86 | 87 | ## Edit File 88 | 89 | You can change the keybind for editing a file using the `edit` TOML key. 90 | 91 | Default value: 92 | ```toml 93 | edit = "enter" 94 | ``` 95 | 96 | ## Back to Menu 97 | 98 | You can change the keybind for returning to the main menu using the `return_to_menu` TOML key. 99 | 100 | Default value: 101 | ```toml 102 | return_to_menu = "esc" 103 | ``` 104 | -------------------------------------------------------------------------------- /internal/config/keybinds.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type KeybindsConfig struct { 4 | Global GlobalKeybinds `mapstructure:"global"` 5 | Home HomeKeybinds `mapstructure:"home"` 6 | Daily DailyKeybinds `mapstructure:"daily"` 7 | Fuzz FuzzyKeybinds `mapstructure:"fuzzy"` 8 | Diary DiaryKeybinds `mapstructure:"diary"` 9 | } 10 | 11 | type FuzzyKeybinds struct { 12 | StartWriting string `mapstructure:"start_writing"` 13 | Up string `mapstructure:"up"` 14 | Down string `mapstructure:"down"` 15 | Enter string `mapstructure:"enter"` 16 | Exit string `mapstructure:"exit"` 17 | } 18 | 19 | type DiaryKeybinds struct { 20 | Edit string `mapstructure:"edit"` 21 | ScrollUp string `mapstructure:"scroll_up"` 22 | ScrollDown string `mapstructure:"scroll_down"` 23 | Finder string `mapstructure:"finder"` 24 | BackToMenu string `mapstructure:"return_to_menu"` 25 | } 26 | 27 | type GlobalKeybinds struct { 28 | Up string `mapstructure:"up"` 29 | Down string `mapstructure:"down"` 30 | Script string `mapstructure:"script"` 31 | } 32 | 33 | type DailyKeybinds struct { 34 | Create string `mapstructure:"create"` 35 | CreateRecurring string `mapstructure:"create_recurring"` 36 | Delete string `mapstructure:"delete"` 37 | StatusChange string `mapstructure:"status_change"` 38 | Edit string `mapstructure:"edit"` 39 | BackToMenu string `mapstructure:"return_to_menu"` 40 | FormUp string `mapstructure:"form_up"` 41 | FormDown string `mapstructure:"form_down"` 42 | ExitPopup string `mapstructure:"exit_popup"` 43 | Enter string `mapstructure:"enter"` 44 | } 45 | 46 | type HomeKeybinds struct { 47 | BackToMenu string `mapstructure:"return_to_menu"` 48 | FocusViewer string `mapstructure:"focus_viewer"` 49 | FocusExplorer string `mapstructure:"focus_explorer"` 50 | Create string `mapstructure:"create"` 51 | Rename string `mapstructure:"rename"` 52 | Move string `mapstructure:"move"` 53 | Delete string `mapstructure:"delete"` 54 | Edit string `mapstructure:"edit"` 55 | ScrollUp string `mapstructure:"scroll_up"` 56 | ScrollDown string `mapstructure:"scroll_down"` 57 | Finder string `mapstructure:"finder"` 58 | } 59 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | export default defineConfig({ 4 | title: 'Toney', 5 | description: 'A Note-taking app for the terminal!', 6 | lang: 'en-US', 7 | head: [ 8 | ['link', { rel: 'icon', href: '/favicon.ico' }], 9 | ['meta', { name: 'theme-color', content: '#0f172a' }] 10 | ], 11 | appearance: 'force-dark', 12 | base: '/Toney/', 13 | themeConfig: { 14 | outline: 'deep', 15 | footer: { 16 | message: "Released under the MIT License", 17 | copyright: "Created and managed by NucleoFusion", 18 | }, 19 | nav: [ 20 | { text: 'Guide', link: '/guide/' }, 21 | { text: 'Config', link: '/config/' }, 22 | { text: 'Install', link: '/guide/install/' }, 23 | ], 24 | socialLinks: [ 25 | { icon: 'github', link: 'https://github.com/SourcewareLab/Toney' }, 26 | { icon: 'discord', link: 'https://discord.gg/X69MUr2DKm' } 27 | ], 28 | sidebar: { 29 | '/guide/': [ 30 | { text: 'Introduction', link: '/guide/' }, 31 | { text: 'Installation', link: '/guide/install' }, 32 | { text: 'Contribution', link: '/guide/contribute' } 33 | ], 34 | '/config/': [ 35 | { text: 'Introduction', link: '/config/' }, 36 | { text: 'General', link: '/config/general' }, 37 | { 38 | text: 'Styles', items: [ 39 | { text: 'General', link: '/config/styles/' }, 40 | { text: 'Icons', link: '/config/styles/icons' }, 41 | { text: 'Task Styles', link: '/config/styles/taskstyles' }, 42 | { text: 'Renderer', link: 'config/styles/renderer' } 43 | ] 44 | }, 45 | { 46 | text: 'Keybinds', items: [ 47 | { text: 'General', link: '/config/keybinds/' }, 48 | { text: 'Global', link: '/config/keybinds/global' }, 49 | { text: 'Home', link: '/config/keybinds/home' }, 50 | { text: 'Daily', link: '/config/keybinds/daily' }, 51 | { text: 'Diary', link: '/config/keybinds/diary' }, 52 | { text: 'Fuzzy Finder', link: '/config/keybinds/fuzz' }, 53 | ] 54 | } 55 | ] 56 | }, 57 | lastUpdated: { 58 | text: "Last updated", 59 | }, 60 | search: { 61 | provider: "local", 62 | }, 63 | } 64 | }) 65 | -------------------------------------------------------------------------------- /internal/models/menu/menulist.go: -------------------------------------------------------------------------------- 1 | package menu 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/SourcewareLab/Toney/internal/colors" 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | "github.com/SourcewareLab/Toney/internal/enums" 9 | "github.com/SourcewareLab/Toney/internal/messages" 10 | tea "github.com/charmbracelet/bubbletea" 11 | "github.com/charmbracelet/lipgloss" 12 | ) 13 | 14 | type MenuList struct { 15 | Width int 16 | Height int 17 | Options map[enums.Page]string 18 | Selections []enums.Page 19 | Selected int 20 | } 21 | 22 | func NewMenuList(w int, h int, opts map[enums.Page]string) *MenuList { 23 | selections := []enums.Page{enums.HomePage, enums.DailyPage, enums.DiaryPage, enums.Quit} 24 | 25 | return &MenuList{ 26 | Width: w, 27 | Height: h, 28 | Options: opts, 29 | Selections: selections, 30 | Selected: 0, 31 | } 32 | } 33 | 34 | func (m *MenuList) Init() tea.Cmd { 35 | return nil 36 | } 37 | 38 | func (m *MenuList) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 39 | switch msg := msg.(type) { 40 | case tea.KeyMsg: 41 | switch msg.String() { 42 | case config.AppConfig.Keybinds.Global.Down: 43 | if m.Selected < len(m.Selections)-1 { 44 | m.Selected += 1 45 | } 46 | return m, nil 47 | case config.AppConfig.Keybinds.Global.Up: 48 | if m.Selected > 0 { 49 | m.Selected -= 1 50 | } 51 | return m, nil 52 | case "enter": 53 | return m, func() tea.Msg { 54 | return messages.ChangePage{ 55 | Page: m.Selections[m.Selected], 56 | } 57 | } 58 | } 59 | } 60 | 61 | return m, nil 62 | } 63 | 64 | func (m *MenuList) View() string { 65 | return lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().Border).Render(m.GetText()) 66 | } 67 | 68 | func (m *MenuList) GetText() string { 69 | text := "" 70 | style := lipgloss.NewStyle().Width(m.Width).Padding(0, 2).Foreground(colors.ColorPalette().Text) 71 | 72 | for idx, val := range m.Selections { 73 | line := m.Options[val] 74 | if m.Selected == idx { 75 | text += style.Background(colors.ColorPalette().MenuSelectedBg). 76 | Foreground(colors.ColorPalette().MenuSelectedText). 77 | Render(line) + "\n" 78 | 79 | continue 80 | } 81 | 82 | text += style.Render(line) + "\n" 83 | } 84 | 85 | return strings.TrimSuffix(text, "\n") 86 | } 87 | -------------------------------------------------------------------------------- /internal/styles/text.go: -------------------------------------------------------------------------------- 1 | package styles 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/charmbracelet/lipgloss" 6 | ) 7 | 8 | const LogoText = ` 9 | ______ 10 | /_ __/___ ____ ___ __ __ 11 | / / / __ \/ __ \/ _ \/ / / / 12 | / / / /_/ / / / / __/ /_/ / 13 | /_/ \____/_/ /_/\___/\__, / 14 | /____/ 15 | ` 16 | 17 | func GetLogo(w int, h int) string { 18 | return lipgloss.NewStyle().Width(w).Height(h).Foreground(colors.ColorPalette().Text).Align(lipgloss.Center, lipgloss.Center).Render(LogoText) 19 | } 20 | 21 | const TasksText = ` 22 | ____ _ __ ______ __ 23 | / __ \ ____ _ (_)/ /__ __ /_ __/____ _ _____ / /__ _____ 24 | / / / // __ '// // // / / / / / / __ '// ___// //_// ___/ 25 | / /_/ // /_/ // // // /_/ / / / / /_/ /(__ )/ ,< (__ ) 26 | /_____/ \__,_//_//_/ \__, / /_/ \__,_//____//_/|_|/____/ 27 | /____/ ` 28 | 29 | func GetDailyText(w int, h int) string { 30 | return lipgloss.NewStyle().Width(w).Height(h).Foreground(colors.ColorPalette().Text). 31 | PaddingTop(2).Align(lipgloss.Center, lipgloss.Top).Render(TasksText) 32 | } 33 | 34 | const AddTasks = ` 35 | ___ __ __ ______ __ 36 | / | ____/ /____/ / /_ __/____ _ _____ / /__ 37 | / /| | / __ // __ / / / / __ '// ___// //_/ 38 | / ___ |/ /_/ // /_/ / / / / /_/ /(__ )/ ,< 39 | /_/ |_|\__,_/ \__,_/ /_/ \__,_//____//_/|_| 40 | 41 | ` 42 | 43 | func GetAddTasks(w int, h int) string { 44 | return lipgloss.NewStyle().Width(w).Height(h).Foreground(colors.ColorPalette().Text). 45 | PaddingTop(2).Align(lipgloss.Center, lipgloss.Top).Render(AddTasks) 46 | } 47 | 48 | const SelectStatus = ` 49 | _____ __ __ _____ __ __ 50 | / ___/ ___ / /___ _____ / /_ / ___/ / /_ ____ _ / /_ __ __ _____ 51 | \__ \ / _ \ / // _ \ / ___// __/ \__ \ / __// __ '// __// / / // ___/ 52 | ___/ // __// // __// /__ / /_ ___/ // /_ / /_/ // /_ / /_/ /(__ ) 53 | /____/ \___//_/ \___/ \___/ \__/ /____/ \__/ \__,_/ \__/ \__,_//____/ 54 | 55 | ` 56 | 57 | func GetSelectStatus(w int, h int) string { 58 | return lipgloss.NewStyle().Width(w).Height(h).Foreground(colors.ColorPalette().Text). 59 | PaddingTop(2).Align(lipgloss.Center, lipgloss.Top).Render(SelectStatus) 60 | } 61 | -------------------------------------------------------------------------------- /cmd/tasks/create.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/SourcewareLab/Toney/internal/enums" 8 | "github.com/SourcewareLab/Toney/internal/models/daily" 9 | "github.com/SourcewareLab/Toney/internal/styles" 10 | "github.com/charmbracelet/huh" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type CreateOptions struct { 15 | Title string 16 | Description string 17 | Status enums.TaskStatus 18 | Type enums.TaskType 19 | } 20 | 21 | func CreatCmd() *cobra.Command { 22 | opts := &CreateOptions{} 23 | cmd := &cobra.Command{ 24 | Use: "create", 25 | Short: "create a task", 26 | RunE: func(cmd *cobra.Command, args []string) error { 27 | if err := config.SetConfig(); err != nil { 28 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 29 | } 30 | 31 | grps := make([]*huh.Group, 0) 32 | 33 | if opts.Title == "" { 34 | grps = append(grps, huh.NewGroup(huh.NewInput(). 35 | Title("Title"). 36 | Value(&opts.Title))) 37 | } 38 | 39 | if opts.Description == "" { 40 | grps = append(grps, huh.NewGroup(huh.NewText(). 41 | Title("Description"). 42 | Value(&opts.Description))) 43 | } 44 | 45 | grps = append(grps, huh.NewGroup(huh.NewSelect[enums.TaskStatus](). 46 | Title("Status"). 47 | Options( 48 | huh.NewOption[enums.TaskStatus]("Pending", enums.Pending), 49 | huh.NewOption[enums.TaskStatus]("Started", enums.Started), 50 | huh.NewOption[enums.TaskStatus]("Abandoned", enums.Abandoned), 51 | huh.NewOption[enums.TaskStatus]("Complete", enums.Complete), 52 | ). 53 | Value(&opts.Status)), 54 | huh.NewGroup( 55 | huh.NewSelect[enums.TaskType](). 56 | Title("Type"). 57 | Options( 58 | huh.NewOption[enums.TaskType]("Unique", enums.UniqueTask), 59 | huh.NewOption[enums.TaskType]("Recurring", enums.RecurringTask), 60 | ). 61 | Value(&opts.Type))) 62 | 63 | form := huh.NewForm(grps...).WithTheme(styles.HuhTheme()) 64 | form.Run() 65 | 66 | tasks := daily.GetItems() 67 | tasks = append(tasks, daily.Task{ 68 | TaskTitle: opts.Title, 69 | TaskType: opts.Type, 70 | TaskDesc: opts.Description, 71 | Status: opts.Status, 72 | }) 73 | 74 | daily.WriteItems(tasks) 75 | 76 | return nil 77 | }, 78 | } 79 | 80 | cmd.Flags().StringVarP(&opts.Title, "title", "t", "", "title for the task") 81 | cmd.Flags().StringVarP(&opts.Description, "desc", "d", "", "description for the task") 82 | 83 | return cmd 84 | } 85 | -------------------------------------------------------------------------------- /internal/models/daily/taskDelegate.go: -------------------------------------------------------------------------------- 1 | package daily 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/SourcewareLab/Toney/internal/colors" 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/SourcewareLab/Toney/internal/enums" 10 | "github.com/SourcewareLab/Toney/internal/styles" 11 | "github.com/charmbracelet/bubbles/list" 12 | tea "github.com/charmbracelet/bubbletea" 13 | "github.com/charmbracelet/lipgloss" 14 | ) 15 | 16 | type TaskDelegate struct{} 17 | 18 | func (d TaskDelegate) Height() int { return 2 } 19 | func (d TaskDelegate) Spacing() int { return 1 } 20 | func (d TaskDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } 21 | 22 | func (d TaskDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) { 23 | if len(m.Items()) == 0 { 24 | return 25 | } 26 | 27 | t, ok := item.(Task) 28 | if !ok { 29 | return 30 | } 31 | 32 | text := "" 33 | cfg := config.AppConfig.Styles.Icons.TaskIcons 34 | 35 | switch t.Status { 36 | case enums.Complete: 37 | text += fmt.Sprintf("%s\n%s", 38 | styles.CompletedStyle().Title.Render(fmt.Sprintf("%s | %s", cfg.CompletedIcon, Shorten(t.Title(), 35))), 39 | styles.CompletedStyle().Desc.Render(t.Description()), 40 | ) 41 | case enums.Pending: 42 | text += fmt.Sprintf("%s\n%s", 43 | styles.PendingStyle().Title.Render(fmt.Sprintf("%s | %s", cfg.PendingIcon, Shorten(t.Title(), 35))), 44 | styles.PendingStyle().Desc.Render(t.Description()), 45 | ) 46 | case enums.Started: 47 | text += fmt.Sprintf("%s\n%s", 48 | styles.StartedStyle().Title.Render(fmt.Sprintf("%s | %s", cfg.StartedIcon, Shorten(t.Title(), 35))), 49 | styles.StartedStyle().Desc.Render(t.Description()), 50 | ) 51 | case enums.Abandoned: 52 | text += fmt.Sprintf("%s\n%s", 53 | styles.AbandonedStyle().Title.Render(fmt.Sprintf("%s | %s", cfg.AbandonedIcon, Shorten(t.Title(), 35))), 54 | styles.AbandonedStyle().Desc.Render(t.Description()), 55 | ) 56 | } 57 | 58 | border := lipgloss.NewStyle().Border(lipgloss.NormalBorder()). 59 | PaddingLeft(2). 60 | BorderLeft(true).BorderBottom(false).BorderRight(false).BorderTop(false) 61 | 62 | if index == m.Index() { 63 | text = border.BorderForeground(colors.ColorPalette().TaskFocusedBar).Render(text) 64 | } else { 65 | text = border.BorderForeground(colors.ColorPalette().TaskUnfocusedBar).Render(text) 66 | } 67 | 68 | io.WriteString(w, text) 69 | } 70 | 71 | func Shorten(s string, maxLen int) string { 72 | if len(s) <= maxLen { 73 | return lipgloss.NewStyle().Width(maxLen).Render(s) 74 | } 75 | if maxLen <= 1 { 76 | return s[:maxLen] 77 | } 78 | return s[:maxLen-1] + "…" 79 | } 80 | -------------------------------------------------------------------------------- /internal/models/daily/items.go: -------------------------------------------------------------------------------- 1 | package daily 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "github.com/SourcewareLab/Toney/internal/config" 10 | "github.com/SourcewareLab/Toney/internal/enums" 11 | "github.com/charmbracelet/bubbles/list" 12 | "github.com/jszwec/csvutil" 13 | ) 14 | 15 | type Task struct { 16 | TaskTitle string `csv:"title"` 17 | TaskDesc string `csv:"desc"` 18 | Status enums.TaskStatus `csv:"status"` 19 | // We will not store these data in the file 20 | ID int `csv:"-"` // Point to index in the respective type array 21 | TaskType enums.TaskType `csv:"-"` 22 | } 23 | 24 | func (m Task) Title() string { return m.TaskTitle } 25 | func (m Task) Description() string { return m.TaskDesc } 26 | func (m Task) FilterValue() string { return m.TaskTitle } 27 | 28 | func TaskToItems(tasks []Task) []list.Item { 29 | list := make([]list.Item, 0) 30 | for i, v := range tasks { 31 | v.ID = i + 1 32 | v.TaskType = enums.RecurringTask 33 | list = append(list, v) 34 | } 35 | return list 36 | } 37 | 38 | func GetItems() []Task { 39 | path := GetPath() 40 | 41 | _, err := os.Stat(path) 42 | if os.IsNotExist(err) { 43 | f, err2 := os.Create(path) 44 | if err2 != nil { 45 | fmt.Println(err2.Error()) 46 | } 47 | 48 | content, err2 := os.ReadFile(GetYesterdayPath()) 49 | if err2 != nil { 50 | fmt.Println(err2.Error()) 51 | } 52 | 53 | tasks := make([]Task, 0) 54 | csvutil.Unmarshal(content, &tasks) 55 | 56 | for k := range tasks { 57 | tasks[k].ID = k + 1 58 | tasks[k].Status = enums.Pending 59 | } 60 | 61 | data, err2 := csvutil.Marshal(tasks) 62 | if err2 != nil { 63 | fmt.Println(err2.Error()) 64 | } 65 | 66 | f.Write(data) 67 | } else if err != nil { 68 | fmt.Println("Error: ", err.Error()) 69 | } 70 | 71 | content, _ := os.ReadFile(path) 72 | 73 | tasks := make([]Task, 0) 74 | err = csvutil.Unmarshal(content, &tasks) 75 | if err != nil { 76 | fmt.Println(err.Error()) 77 | } 78 | 79 | return tasks 80 | } 81 | 82 | func WriteItems(tasks []Task) { 83 | path := GetPath() 84 | 85 | data, _ := csvutil.Marshal(tasks) 86 | 87 | os.WriteFile(path, data, 0o644) 88 | } 89 | 90 | func GetPath() string { 91 | home, _ := os.UserHomeDir() 92 | date := time.Now().Format("2006-01-02") 93 | 94 | return filepath.Join(home, config.AppConfig.General.NotesDir, ".daily", date) 95 | } 96 | 97 | func GetYesterdayPath() string { 98 | home, _ := os.UserHomeDir() 99 | yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") 100 | return filepath.Join(home, config.AppConfig.General.NotesDir, ".daily", yesterday) 101 | } 102 | -------------------------------------------------------------------------------- /internal/models/taskPopup/form.go: -------------------------------------------------------------------------------- 1 | package taskpopup 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/SourcewareLab/Toney/internal/keymap" 6 | "github.com/SourcewareLab/Toney/internal/styles" 7 | "github.com/charmbracelet/bubbles/key" 8 | "github.com/charmbracelet/bubbles/textarea" 9 | "github.com/charmbracelet/bubbles/textinput" 10 | tea "github.com/charmbracelet/bubbletea" 11 | "github.com/charmbracelet/lipgloss" 12 | ) 13 | 14 | type Form struct { 15 | Width int 16 | Height int 17 | Keymap keymap.TaskFormMap 18 | TitleInput textinput.Model 19 | DescInput textarea.Model 20 | } 21 | 22 | func NewForm(w int, h int) *Form { 23 | tinput := textinput.New() 24 | tinput.Prompt = "" 25 | tinput.Placeholder = "Title" 26 | tinput.Focus() 27 | tinput.Width = w / 4 28 | 29 | ta := textarea.New() 30 | ta.Placeholder = "Enter Description..." 31 | ta.SetWidth(w / 4) 32 | 33 | return &Form{ 34 | Width: w, 35 | Height: h, 36 | Keymap: keymap.NewTaskFormMap(), 37 | TitleInput: tinput, 38 | DescInput: ta, 39 | } 40 | } 41 | 42 | func (m Form) Init() tea.Cmd { 43 | return nil 44 | } 45 | 46 | func (m *Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 47 | switch msg := msg.(type) { 48 | case tea.KeyMsg: 49 | switch { 50 | case key.Matches(msg, m.Keymap.MoveDown): 51 | if m.TitleInput.Focused() { 52 | m.TitleInput.Blur() 53 | m.DescInput.Focus() 54 | } 55 | return m, nil 56 | case key.Matches(msg, m.Keymap.MoveUp): 57 | if m.DescInput.Focused() { 58 | m.DescInput.Blur() 59 | m.TitleInput.Focus() 60 | } 61 | return m, nil 62 | } 63 | } 64 | var cmd tea.Cmd 65 | 66 | if m.TitleInput.Focused() { 67 | m.TitleInput, cmd = m.TitleInput.Update(msg) 68 | } else if m.DescInput.Focused() { 69 | m.DescInput, cmd = m.DescInput.Update(msg) 70 | } 71 | 72 | return m, cmd 73 | } 74 | 75 | func (m *Form) View() string { 76 | formView := lipgloss.Place(m.Width, m.Height*2/3, lipgloss.Center, lipgloss.Top, 77 | lipgloss.JoinVertical(lipgloss.Center, m.InputView(), m.AreaView())) 78 | 79 | return lipgloss.Place(m.Width, m.Height, lipgloss.Center, lipgloss.Center, 80 | lipgloss.JoinVertical(lipgloss.Center, styles.GetAddTasks(m.Width, m.Height/3), formView)) 81 | } 82 | 83 | func (m *Form) InputView() string { 84 | return lipgloss.NewStyle(). 85 | BorderStyle(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().Border). 86 | Foreground(colors.ColorPalette().Text).Padding(0, 1). 87 | Render("Title:" + "\n" + m.TitleInput.View()) 88 | } 89 | 90 | func (m *Form) AreaView() string { 91 | return lipgloss.NewStyle(). 92 | BorderStyle(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().Border). 93 | Foreground(colors.ColorPalette().Text).Padding(0, 1).PaddingLeft(2). 94 | Render(m.DescInput.View()) 95 | } 96 | -------------------------------------------------------------------------------- /internal/models/filePopup/popup.go: -------------------------------------------------------------------------------- 1 | package filepopup 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SourcewareLab/Toney/internal/enums" 7 | filetree "github.com/SourcewareLab/Toney/internal/fileTree" 8 | "github.com/SourcewareLab/Toney/internal/messages" 9 | "github.com/SourcewareLab/Toney/internal/styles" 10 | 11 | "github.com/SourcewareLab/Toney/internal/colors" 12 | "github.com/charmbracelet/bubbles/textinput" 13 | tea "github.com/charmbracelet/bubbletea" 14 | "github.com/charmbracelet/lipgloss" 15 | ) 16 | 17 | func PopupStyle() lipgloss.Style { 18 | return styles.BorderStyle().Align(lipgloss.Left, lipgloss.Top).BorderForeground(colors.ColorPalette().FocusedBorder) 19 | } 20 | 21 | func HeaderStyle() lipgloss.Style { 22 | return lipgloss.NewStyle().Background(colors.ColorPalette().MenuSelectedBg).Foreground(colors.ColorPalette().MenuSelectedText) 23 | } 24 | 25 | type FilePopup struct { 26 | Height int 27 | Width int 28 | Type enums.PopupType 29 | TextInput textinput.Model 30 | Node *filetree.Node 31 | } 32 | 33 | func NewPopup(typ enums.PopupType, node *filetree.Node) *FilePopup { 34 | ti := textinput.New() 35 | ti.TextStyle = lipgloss.NewStyle().Foreground(colors.ColorPalette().Text) 36 | ti.PromptStyle = lipgloss.NewStyle().Foreground(colors.ColorPalette().Text) 37 | ti.Focus() 38 | 39 | return &FilePopup{ 40 | Type: typ, 41 | TextInput: ti, 42 | Node: node, 43 | } 44 | } 45 | 46 | func (m FilePopup) Init() tea.Cmd { 47 | return nil 48 | } 49 | 50 | func (m *FilePopup) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 51 | switch msg := msg.(type) { 52 | // case PopupMessage: 53 | // return m, func() tea.Msg { 54 | // return msg 55 | // } 56 | 57 | case tea.KeyMsg: 58 | switch msg.String() { 59 | case "esc": 60 | return m, func() tea.Msg { 61 | return messages.HidePopupMessage{} 62 | } 63 | case "enter": 64 | return HandleEnter(m) 65 | } 66 | case tea.WindowSizeMsg: 67 | m.Height = msg.Height 68 | m.Width = msg.Width 69 | } 70 | 71 | var cmd tea.Cmd 72 | m.TextInput, cmd = m.TextInput.Update(msg) 73 | return m, cmd 74 | } 75 | 76 | func (m *FilePopup) View() string { 77 | w := m.Width / 3 78 | h := m.Height / 3 79 | 80 | return PopupStyle().Width(w).Height(h).Render(GetText(m.Type, m.TextInput)) 81 | } 82 | 83 | func GetText(typ enums.PopupType, ti textinput.Model) string { 84 | header := "" 85 | switch typ { 86 | case enums.FileCreate: 87 | header = "Create a File (names ending with '/' will create directory):- " 88 | case enums.FileDelete: 89 | header = "Delete file (y/n)?" 90 | case enums.FileRename: 91 | header = "Enter new name for file:- " 92 | case enums.FileMove: 93 | header = "Enter new location (relative) for file:- " 94 | default: 95 | header = "TBD" 96 | } 97 | 98 | return fmt.Sprintf("%s\n\n%s", HeaderStyle().Render(header), ti.View()) 99 | } 100 | -------------------------------------------------------------------------------- /internal/models/filePopup/handlers.go: -------------------------------------------------------------------------------- 1 | package filepopup 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/SourcewareLab/Toney/internal/enums" 9 | filetree "github.com/SourcewareLab/Toney/internal/fileTree" 10 | "github.com/SourcewareLab/Toney/internal/messages" 11 | 12 | tea "github.com/charmbracelet/bubbletea" 13 | ) 14 | 15 | func HandleEnter(m *FilePopup) (tea.Model, tea.Cmd) { 16 | path := GetPath(m.Node) 17 | 18 | switch m.Type { 19 | case enums.FileCreate: 20 | Create(path, m.TextInput.Value(), m.Node) 21 | case enums.FileDelete: 22 | Delete(path[0:len(path)-1], m.TextInput.Value()) 23 | case enums.FileRename: 24 | Rename(path[0:len(path)-1], m.TextInput.Value()) 25 | case enums.FileMove: 26 | Move(path[0:len(path)-1], m.TextInput.Value()) 27 | default: 28 | fmt.Println("default") 29 | } 30 | 31 | return m, tea.Batch(func() tea.Msg { 32 | return messages.HidePopupMessage{} 33 | }, 34 | func() tea.Msg { 35 | return messages.RefreshFileExplorerMsg{} 36 | }) 37 | } 38 | 39 | func Move(path string, value string) { 40 | err := os.Rename(path, value) 41 | if err != nil { 42 | fmt.Println(err.Error()) 43 | os.Exit(1) 44 | } 45 | } 46 | 47 | func Rename(path string, value string) { 48 | newpathArr := strings.Split(path, "/") 49 | newpath := strings.Join(newpathArr[0:len(newpathArr)-1], "/") + "/" + value 50 | err := os.Rename(path, newpath) 51 | if err != nil { 52 | fmt.Println(err.Error()) 53 | os.Exit(1) 54 | } 55 | } 56 | 57 | func Delete(path string, value string) { 58 | if value != "y" { 59 | return 60 | } 61 | 62 | err := os.Remove(path) 63 | if err != nil { 64 | fmt.Println(err.Error()) 65 | os.Exit(1) 66 | } 67 | } 68 | 69 | func Create(path string, value string, n *filetree.Node) { 70 | if !n.IsDirectory { 71 | pathArr := strings.Split(path, "/") 72 | pathArr = pathArr[0 : len(pathArr)-2] 73 | path = strings.Join(pathArr, "/") + "/" 74 | } 75 | 76 | if strings.HasSuffix(value, "/") { 77 | err := os.MkdirAll(path+value, 0o755) 78 | if err != nil { 79 | fmt.Println(err.Error()) 80 | // os.Exit(1) 81 | } 82 | } else { 83 | _, err := os.Create(path + value) 84 | if err != nil { 85 | fmt.Println(err.Error()) 86 | os.Exit(1) 87 | } 88 | } 89 | } 90 | 91 | func MapExpanded(new *filetree.Node, old *filetree.Node) { 92 | if old.IsExpanded { 93 | new.IsExpanded = true 94 | } 95 | 96 | if len(old.Children) != len(new.Children) { 97 | return 98 | } 99 | 100 | for idx, val := range old.Children { 101 | if val.Name != new.Children[idx].Name { 102 | continue 103 | } 104 | 105 | if !val.IsDirectory || !new.Children[idx].IsDirectory { 106 | continue 107 | } 108 | 109 | MapExpanded(new.Children[idx], val) 110 | } 111 | } 112 | 113 | func GetPath(n *filetree.Node) string { 114 | c := n 115 | 116 | home, _ := os.UserHomeDir() 117 | 118 | path := "" 119 | 120 | for { 121 | if c == nil { 122 | return home + "/" + path 123 | } 124 | 125 | path = c.Name + "/" + path 126 | c = c.Parent 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /internal/fileTree/fileTree.go: -------------------------------------------------------------------------------- 1 | package filetree 2 | 3 | import ( 4 | "io/fs" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | 9 | "github.com/SourcewareLab/Toney/internal/colors" 10 | "github.com/SourcewareLab/Toney/internal/config" 11 | "github.com/charmbracelet/lipgloss" 12 | ) 13 | 14 | func CreateTree() (*Node, error) { 15 | home, _ := os.UserHomeDir() 16 | 17 | root, err := buildTree(nil, filepath.Join(home, config.AppConfig.General.NotesDir), 0) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | root.IsExpanded = true 23 | 24 | return root, nil 25 | } 26 | 27 | func buildTree(parent *Node, path string, depth int) (*Node, error) { 28 | info, err := os.Stat(path) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | node := Node{ 34 | Name: info.Name(), 35 | Parent: parent, 36 | Depth: depth, 37 | IsDirectory: info.IsDir(), 38 | IsExpanded: false, 39 | } 40 | 41 | if node.IsDirectory { 42 | files, _ := os.ReadDir(path) 43 | for _, file := range files { 44 | childPath := filepath.Join(path, file.Name()) 45 | child, _ := buildTree(&node, childPath, depth+1) 46 | node.Children = append(node.Children, child) 47 | } 48 | } 49 | 50 | return &node, nil 51 | } 52 | 53 | func BuildNodeTree(n *Node, prefix string, isLast bool, curr *Node) string { 54 | var sb strings.Builder 55 | 56 | branch := "├─ " 57 | if isLast { 58 | branch = "└─ " 59 | } 60 | 61 | icon := config.AppConfig.Styles.Icons.FileIcon 62 | if n.IsDirectory { 63 | icon = config.AppConfig.Styles.Icons.FolderIcon 64 | } 65 | 66 | newPrefix := prefix 67 | 68 | // Build the line for this node 69 | line := "" 70 | if n.Parent == nil { 71 | line = icon + " " + n.Name 72 | } else { 73 | line = prefix + branch + icon + " " + n.Name 74 | } 75 | 76 | // line += "\n\n\n\n" 77 | 78 | if n == curr { 79 | line = lipgloss.NewStyle().Background(colors.ColorPalette().MenuSelectedBg). 80 | Foreground(colors.ColorPalette().MenuSelectedText).Render(line) 81 | } else { 82 | line = lipgloss.NewStyle().Foreground(colors.ColorPalette().Text).Render(line) 83 | } 84 | 85 | sb.WriteString(line + "\n") 86 | 87 | // Update prefix for children 88 | if n.Parent != nil { 89 | if isLast { 90 | newPrefix += " " 91 | } else { 92 | newPrefix += "│ " 93 | } 94 | } 95 | 96 | if n.IsExpanded { 97 | for i, child := range n.Children { 98 | sb.WriteString(BuildNodeTree(child, newPrefix, i == len(n.Children)-1, curr)) 99 | } 100 | } 101 | 102 | return sb.String() 103 | } 104 | 105 | func ListFilesRel(baseDir string) ([]string, error) { 106 | var relPaths []string 107 | 108 | err := filepath.WalkDir(baseDir, func(path string, d fs.DirEntry, err error) error { 109 | if err != nil { 110 | return err 111 | } 112 | if d.IsDir() { 113 | return nil 114 | } 115 | rel, err := filepath.Rel(baseDir, path) 116 | if err != nil { 117 | return err 118 | } 119 | relPaths = append(relPaths, rel) 120 | return nil 121 | }) 122 | if err != nil { 123 | return nil, err 124 | } 125 | return relPaths, nil 126 | } 127 | -------------------------------------------------------------------------------- /cmd/notes/list.go: -------------------------------------------------------------------------------- 1 | package notes 2 | 3 | import ( 4 | "fmt" 5 | "io/fs" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/SourcewareLab/Toney/internal/config" 12 | "github.com/SourcewareLab/Toney/internal/styles" 13 | "github.com/lithammer/fuzzysearch/fuzzy" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | type ListOptions struct { 18 | Search string 19 | All bool 20 | Path string 21 | } 22 | 23 | func ListCmd() *cobra.Command { 24 | opts := &ListOptions{} 25 | cmd := &cobra.Command{ 26 | Use: "list", 27 | Short: "List all notes in the notes directory", 28 | RunE: func(cmd *cobra.Command, args []string) error { 29 | if err := config.SetConfig(); err != nil { 30 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 31 | } 32 | 33 | home, _ := os.UserHomeDir() 34 | rootpath := filepath.Join(home, config.AppConfig.General.NotesDir) 35 | entries := map[string]os.DirEntry{} 36 | 37 | if opts.Path != "" { 38 | rootpath = filepath.Join(rootpath, opts.Path) 39 | } 40 | 41 | filepath.WalkDir(rootpath, func(path string, d fs.DirEntry, err error) error { 42 | if err != nil { 43 | if opts.Path == "" { 44 | log.Fatalf("failed to read notes directory (%s) : %v", path, err) 45 | } else { 46 | log.Fatalf("failed to read directory given with -p/--path flag (%s) : %v", path, err) 47 | } 48 | } 49 | 50 | if d.IsDir() { 51 | if d.Name() == config.AppConfig.General.NotesDir { 52 | return nil 53 | } else if strings.HasPrefix(d.Name(), ".") && !opts.All { 54 | return fs.SkipDir 55 | } 56 | } 57 | 58 | if !d.IsDir() { 59 | entries[path] = d 60 | } 61 | 62 | return nil 63 | }) 64 | 65 | filteredEntries := entries 66 | // Fuzzy Filtering 67 | if opts.Search != "" { 68 | filteredEntries = map[string]os.DirEntry{} // Resetting filteredEntries 69 | 70 | paths := make([]string, 0, len(entries)) 71 | for k := range entries { 72 | paths = append(paths, k) 73 | } 74 | 75 | res := fuzzy.Find(opts.Search, paths) 76 | 77 | for _, v := range res { 78 | filteredEntries[v] = entries[v] 79 | } 80 | } 81 | 82 | content := FormatEntries(filteredEntries, rootpath) 83 | 84 | fmt.Println(content) 85 | return nil 86 | }, 87 | } 88 | 89 | cmd.Flags().StringVarP(&opts.Search, "search", "s", "", "fuzzy find a filename that matches the given input") 90 | cmd.Flags().BoolVarP(&opts.All, "all", "a", false, "include hidden files in list") 91 | cmd.Flags().StringVarP(&opts.Path, "path", "p", "", "list files in given path, path is relative to the `notes_dir`") 92 | 93 | return cmd 94 | } 95 | 96 | func FormatEntries(entries map[string]os.DirEntry, root string) string { 97 | t := styles.NewTable() 98 | t.Headers("Filename", "Updated", "Size") 99 | 100 | for k, v := range entries { 101 | info, _ := v.Info() 102 | p, _ := filepath.Rel(root, k) 103 | t.Row(p, info.ModTime().Format("02 Jan"), fmt.Sprintf("%dKb", info.Size())) 104 | } 105 | 106 | return t.Render() 107 | } 108 | -------------------------------------------------------------------------------- /docs/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | pageClass: home-page 4 | 5 | hero: 6 | name: Toney 7 | text: A Terminal Based Note Taking App 8 | actions: 9 | - theme: brand 10 | text: Get Started 11 | link: /docs 12 | - theme: alt 13 | text: Documentation 14 | link: /config/ 15 | 16 | features: 17 | - title: Terminal-First Note Taking! 18 | details: 19 | Take notes directly in your terminal. Designed for speed and minimal distraction, so you stay in flow while you work. 20 | - title: Markdown, but Better! 21 | details: 22 | Write in plain Markdown, render beautifully. Lists, Checkboxes, all supported out of the box. 23 | - title: Plan Your Day! 24 | details: 25 | Built-in daily task manager with productivity-focused design — all inside the shell. 26 | - title: Single Config, Endless Possibility 27 | details: 28 | Centralized TOML config file controls everything — styling, shortcuts, layout, and more. 29 | --- 30 | 31 | 104 | -------------------------------------------------------------------------------- /cmd/tasks/edit.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/SourcewareLab/Toney/internal/config" 7 | "github.com/SourcewareLab/Toney/internal/enums" 8 | "github.com/SourcewareLab/Toney/internal/models/daily" 9 | "github.com/SourcewareLab/Toney/internal/styles" 10 | "github.com/charmbracelet/huh" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | type EditOptions struct { 15 | ID int 16 | Title string 17 | Description string 18 | Status enums.TaskStatus 19 | Type enums.TaskType 20 | } 21 | 22 | func EditCmd() *cobra.Command { 23 | opts := &EditOptions{} 24 | cmd := &cobra.Command{ 25 | Use: "edit", 26 | Short: "edit a task", 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | if err := config.SetConfig(); err != nil { 29 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 30 | } 31 | 32 | tasks := daily.GetItems() 33 | 34 | options := make([]huh.Option[int], 0) 35 | for k, v := range tasks { 36 | options = append(options, huh.NewOption(v.Title(), k)) 37 | } 38 | 39 | selectform := huh.NewForm( 40 | huh.NewGroup( 41 | huh.NewSelect[int](). 42 | Title("Select a task to edit"). 43 | Value(&opts.ID). 44 | Options( 45 | options..., 46 | ), 47 | ), 48 | ).WithTheme(styles.HuhTheme()) 49 | 50 | selectform.Run() 51 | 52 | opts.Title = tasks[opts.ID].TaskTitle 53 | opts.Description = tasks[opts.ID].TaskDesc 54 | opts.Type = tasks[opts.ID].TaskType 55 | opts.Status = tasks[opts.ID].Status 56 | 57 | statusOpts := make([]huh.Option[enums.TaskStatus], 0) 58 | for i := range 4 { 59 | opt := huh.NewOption(enums.TaskStatusMap[enums.TaskStatus(i)], enums.TaskStatus(i)).Selected(false) 60 | 61 | if enums.TaskStatus(i) == opts.Status { 62 | opt.Selected(true) 63 | } 64 | 65 | statusOpts = append(statusOpts, opt) 66 | } 67 | 68 | typeOpts := make([]huh.Option[enums.TaskType], 0) 69 | for i := range 2 { 70 | opt := huh.NewOption(enums.TaskTypeMap[enums.TaskType(i)], enums.TaskType(i)).Selected(false) 71 | 72 | if enums.TaskType(i) == opts.Type { 73 | opt.Selected(true) 74 | } 75 | 76 | typeOpts = append(typeOpts, opt) 77 | } 78 | 79 | taskform := huh.NewForm( 80 | huh.NewGroup( 81 | huh.NewInput(). 82 | Title("Title"). 83 | Value(&opts.Title), 84 | ), 85 | huh.NewGroup( 86 | huh.NewText(). 87 | Title("Description"). 88 | Value(&opts.Description), 89 | ), 90 | huh.NewGroup(huh.NewSelect[enums.TaskStatus](). 91 | Title("Status"). 92 | Options( 93 | statusOpts..., 94 | ). 95 | Value(&opts.Status)), 96 | huh.NewGroup( 97 | huh.NewSelect[enums.TaskType](). 98 | Title("Type"). 99 | Options( 100 | typeOpts..., 101 | ). 102 | Value(&opts.Type)), 103 | ).WithTheme(styles.HuhTheme()) 104 | 105 | taskform.Run() 106 | 107 | task := daily.Task{ 108 | TaskType: opts.Type, 109 | TaskTitle: opts.Title, 110 | TaskDesc: opts.Description, 111 | Status: opts.Status, 112 | } 113 | 114 | tasks[opts.ID] = task 115 | 116 | daily.WriteItems(tasks) 117 | 118 | return nil 119 | }, 120 | } 121 | 122 | return cmd 123 | } 124 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/SourcewareLab/Toney 2 | 3 | go 1.24.2 4 | 5 | require ( 6 | github.com/charmbracelet/bubbles v0.21.0 7 | github.com/charmbracelet/bubbletea v1.3.6 8 | github.com/charmbracelet/glamour v0.10.0 9 | github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 10 | github.com/spf13/viper v1.20.1 11 | ) 12 | 13 | require ( 14 | github.com/BurntSushi/toml v1.5.0 // indirect 15 | github.com/alecthomas/chroma/v2 v2.14.0 // indirect 16 | github.com/atotto/clipboard v0.1.4 // indirect 17 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 18 | github.com/aymerick/douceur v0.2.0 // indirect 19 | github.com/catppuccin/go v0.3.0 // indirect 20 | github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3 // indirect 21 | github.com/charmbracelet/colorprofile v0.3.1 // indirect 22 | github.com/charmbracelet/fang v0.4.0 // indirect 23 | github.com/charmbracelet/huh v0.7.0 // indirect 24 | github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3 // indirect 25 | github.com/charmbracelet/x/ansi v0.9.3 // indirect 26 | github.com/charmbracelet/x/cellbuf v0.0.14-0.20250501183327-ad3bc78c6a81 // indirect 27 | github.com/charmbracelet/x/exp/charmtone v0.0.0-20250603201427-c31516f43444 // indirect 28 | github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect 29 | github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect 30 | github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197 // indirect 31 | github.com/charmbracelet/x/term v0.2.1 // indirect 32 | github.com/charmbracelet/x/windows v0.2.1 // indirect 33 | github.com/dlclark/regexp2 v1.11.0 // indirect 34 | github.com/dustin/go-humanize v1.0.1 // indirect 35 | github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 36 | github.com/fsnotify/fsnotify v1.8.0 // indirect 37 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 38 | github.com/gorilla/css v1.0.1 // indirect 39 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 40 | github.com/jszwec/csvutil v1.10.0 // indirect 41 | github.com/lithammer/fuzzysearch v1.1.8 // indirect 42 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/mattn/go-localereader v0.0.1 // indirect 45 | github.com/mattn/go-runewidth v0.0.16 // indirect 46 | github.com/microcosm-cc/bluemonday v1.0.27 // indirect 47 | github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect 48 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect 49 | github.com/muesli/cancelreader v0.2.2 // indirect 50 | github.com/muesli/mango v0.1.0 // indirect 51 | github.com/muesli/mango-cobra v1.2.0 // indirect 52 | github.com/muesli/mango-pflag v0.1.0 // indirect 53 | github.com/muesli/reflow v0.3.0 // indirect 54 | github.com/muesli/roff v0.1.0 // indirect 55 | github.com/muesli/termenv v0.16.0 // indirect 56 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect 57 | github.com/rivo/uniseg v0.4.7 // indirect 58 | github.com/rmhubbert/bubbletea-overlay v0.4.0 // indirect 59 | github.com/sagikazarmark/locafero v0.7.0 // indirect 60 | github.com/sahilm/fuzzy v0.1.1 // indirect 61 | github.com/sourcegraph/conc v0.3.0 // indirect 62 | github.com/spf13/afero v1.12.0 // indirect 63 | github.com/spf13/cast v1.7.1 // indirect 64 | github.com/spf13/cobra v1.9.1 // indirect 65 | github.com/spf13/pflag v1.0.6 // indirect 66 | github.com/subosito/gotenv v1.6.0 // indirect 67 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 68 | github.com/yuin/goldmark v1.7.12 // indirect 69 | github.com/yuin/goldmark-emoji v1.0.5 // indirect 70 | go.uber.org/atomic v1.9.0 // indirect 71 | go.uber.org/multierr v1.9.0 // indirect 72 | golang.org/x/net v0.33.0 // indirect 73 | golang.org/x/sync v0.16.0 // indirect 74 | golang.org/x/sys v0.34.0 // indirect 75 | golang.org/x/term v0.31.0 // indirect 76 | golang.org/x/text v0.27.0 // indirect 77 | gopkg.in/yaml.v3 v3.0.1 // indirect 78 | ) 79 | -------------------------------------------------------------------------------- /internal/models/fzf/fzf.go: -------------------------------------------------------------------------------- 1 | package fzf 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/SourcewareLab/Toney/internal/colors" 7 | "github.com/SourcewareLab/Toney/internal/keymap" 8 | "github.com/SourcewareLab/Toney/internal/messages" 9 | "github.com/charmbracelet/bubbles/help" 10 | "github.com/charmbracelet/bubbles/key" 11 | "github.com/charmbracelet/bubbles/textinput" 12 | "github.com/charmbracelet/bubbles/viewport" 13 | tea "github.com/charmbracelet/bubbletea" 14 | "github.com/charmbracelet/lipgloss" 15 | "github.com/lithammer/fuzzysearch/fuzzy" 16 | ) 17 | 18 | type FuzzyFinder struct { 19 | Width int 20 | Height int 21 | Keymap keymap.FuzzyMap 22 | Help help.Model 23 | Vp viewport.Model 24 | Ti textinput.Model 25 | Items []string 26 | Filtered []string 27 | SelectedIndex int 28 | } 29 | 30 | func NewFzf(items []string, w int, h int) FuzzyFinder { 31 | return FuzzyFinder{ 32 | Width: w, 33 | Height: h, 34 | Help: help.New(), 35 | Keymap: keymap.NewFuzzyMap(), 36 | Items: items, 37 | Filtered: items, 38 | Vp: NewVP(w, h, items), 39 | Ti: NewTI(w / 3), 40 | SelectedIndex: 0, 41 | } 42 | } 43 | 44 | func (m *FuzzyFinder) Init() tea.Cmd { 45 | return nil 46 | } 47 | 48 | func (m *FuzzyFinder) Update(msg tea.Msg) (FuzzyFinder, tea.Cmd) { 49 | switch msg := msg.(type) { 50 | case tea.KeyMsg: 51 | switch { 52 | case key.Matches(msg, m.Keymap.StartWriting): 53 | m.Ti.Placeholder = "Search for Diary Entries..." 54 | m.Ti.Focus() 55 | case key.Matches(msg, m.Keymap.Down): 56 | if len(m.Filtered)-1 > m.SelectedIndex { 57 | m.SelectedIndex += 1 58 | } 59 | if m.SelectedIndex > m.Vp.Height+m.Vp.YOffset-3 { // -2 for border and something else, idk breaks otherwise 60 | m.Vp.YOffset += 1 61 | } 62 | m.UpdateVP() 63 | return *m, nil 64 | case key.Matches(msg, m.Keymap.Up): 65 | if 0 < m.SelectedIndex { 66 | m.SelectedIndex -= 1 67 | } 68 | if m.SelectedIndex < m.Vp.YOffset { 69 | m.Vp.YOffset -= 1 70 | } 71 | m.UpdateVP() 72 | return *m, nil 73 | case key.Matches(msg, m.Keymap.Enter): 74 | return *m, func() tea.Msg { 75 | return messages.FzfSelection{ 76 | Selection: m.Filtered[m.SelectedIndex], 77 | Exited: false, 78 | } 79 | } 80 | case key.Matches(msg, m.Keymap.Exit): 81 | return *m, func() tea.Msg { 82 | return messages.FzfSelection{ 83 | Selection: "", 84 | Exited: true, 85 | } 86 | } 87 | default: 88 | if m.Ti.Focused() { 89 | var cmd tea.Cmd 90 | m.Ti, cmd = m.Ti.Update(msg) 91 | m.Filter(m.Ti.Value()) 92 | m.UpdateVP() 93 | return *m, cmd 94 | } 95 | } 96 | } 97 | 98 | return *m, nil 99 | } 100 | 101 | func (m *FuzzyFinder) View() string { 102 | pg := lipgloss.Place(m.Width, m.Height-1, lipgloss.Center, lipgloss.Center, 103 | lipgloss.JoinVertical(lipgloss.Center, 104 | lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(colors.ColorPalette().FocusedBorder).Render(m.Ti.View()), 105 | m.Vp.View())) 106 | hlp := lipgloss.NewStyle().PaddingLeft(2).Render(m.Help.View(keymap.NewDynamic(m.Keymap.Bindings()))) 107 | return lipgloss.JoinVertical(lipgloss.Left, pg, hlp) 108 | } 109 | 110 | func (m *FuzzyFinder) UpdateVP() { 111 | text := "" 112 | for k, v := range m.Filtered { 113 | if k == m.SelectedIndex { 114 | text += SelectedItemStyle(m.Width/3).Render(v) + "\n" 115 | continue 116 | } 117 | 118 | text += ItemLineStyle(m.Width/3).Render(v) + "\n" 119 | } 120 | 121 | m.Vp.SetContent(text) 122 | } 123 | 124 | func (m *FuzzyFinder) Filter(val string) { 125 | ranked := fuzzy.RankFindFold(val, m.Items) 126 | sort.Sort(ranked) // Sort by ascending distance 127 | result := make([]string, len(ranked)) 128 | for i, r := range ranked { 129 | result[i] = r.Target 130 | } 131 | 132 | m.Filtered = result 133 | 134 | if len(m.Filtered) <= m.SelectedIndex { // So that index doesnt go out of bounds 135 | m.SelectedIndex = len(m.Filtered) - 1 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /cmd/tasks/list.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/SourcewareLab/Toney/internal/config" 11 | "github.com/SourcewareLab/Toney/internal/enums" 12 | "github.com/SourcewareLab/Toney/internal/models/daily" 13 | "github.com/SourcewareLab/Toney/internal/styles" 14 | "github.com/charmbracelet/bubbles/list" 15 | tea "github.com/charmbracelet/bubbletea" 16 | "github.com/charmbracelet/lipgloss" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | type ListOptions struct { 21 | Raw bool 22 | Verbose bool 23 | } 24 | 25 | func ListCmd() *cobra.Command { 26 | opts := &ListOptions{} 27 | cmd := &cobra.Command{ 28 | Use: "list", 29 | Short: "List all current tasks", 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | if err := config.SetConfig(); err != nil { 32 | return fmt.Errorf("failed to load config: %v\n\n%s", err, "Try running the `toney init` command and try again.") 33 | } 34 | 35 | dl := CmdDelegate{Verbose: opts.Verbose} 36 | ht := dl.Height() + dl.Spacing() 37 | tasks := daily.TaskToItems(daily.GetItems()) 38 | 39 | lst := list.New(tasks, dl, 1000, len(tasks)*ht) 40 | lst.SetShowTitle(false) 41 | lst.SetShowHelp(false) 42 | lst.SetShowFilter(false) 43 | lst.SetShowStatusBar(false) 44 | lst.SetShowPagination(false) 45 | 46 | if opts.Raw { 47 | path := daily.GetPath() 48 | content, _ := os.ReadFile(path) 49 | fmt.Println(string(content)) 50 | return nil 51 | } 52 | fmt.Printf("\n\n%s\n\n", lst.View()) 53 | return nil 54 | }, 55 | } 56 | 57 | cmd.Flags().BoolVarP(&opts.Raw, "raw", "r", false, "get raw output of the daily tasks file") 58 | cmd.Flags().BoolVarP(&opts.Verbose, "verbose", "v", false, "verbose rendering of tasks, shows description as well") 59 | 60 | return cmd 61 | } 62 | 63 | type CmdDelegate struct { 64 | Verbose bool 65 | } 66 | 67 | func (d CmdDelegate) Height() int { 68 | if d.Verbose { 69 | return 2 70 | } 71 | 72 | return 1 73 | } 74 | func (d CmdDelegate) Spacing() int { return 1 } 75 | func (d CmdDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } 76 | 77 | func (d CmdDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) { 78 | if len(m.Items()) == 0 { 79 | return 80 | } 81 | 82 | t, ok := item.(daily.Task) 83 | if !ok { 84 | return 85 | } 86 | 87 | text := "" 88 | cfg := config.AppConfig.Styles.Icons.TaskIcons 89 | 90 | icon := "" 91 | switch t.Status { 92 | case enums.Complete: 93 | icon = cfg.CompletedIcon 94 | case enums.Pending: 95 | icon = cfg.PendingIcon 96 | case enums.Started: 97 | icon = cfg.StartedIcon 98 | case enums.Abandoned: 99 | icon = cfg.AbandonedIcon 100 | } 101 | 102 | taskText := fmt.Sprintf("%s | %s", icon, Shorten(t.Title(), 35)) 103 | 104 | switch t.Status { 105 | case enums.Complete: 106 | taskText = styles.CompletedStyle().Title.Render(taskText) 107 | case enums.Pending: 108 | taskText = styles.PendingStyle().Title.Render(taskText) 109 | case enums.Started: 110 | taskText = styles.StartedStyle().Title.Render(taskText) 111 | case enums.Abandoned: 112 | taskText = styles.AbandonedStyle().Title.Render(taskText) 113 | } 114 | 115 | if d.Verbose { 116 | descText := fmt.Sprintf("\n%s %s", strings.Repeat(" ", len(strconv.Itoa(t.ID))), t.Description()) // All the spacing to align correctly with title 117 | 118 | switch t.Status { 119 | case enums.Complete: 120 | descText = styles.CompletedStyle().Desc.Render(descText) 121 | case enums.Pending: 122 | descText = styles.PendingStyle().Desc.Render(descText) 123 | case enums.Started: 124 | descText = styles.StartedStyle().Desc.Render(descText) 125 | case enums.Abandoned: 126 | descText = styles.AbandonedStyle().Desc.Render(descText) 127 | } 128 | 129 | taskText += descText 130 | } 131 | 132 | text += taskText 133 | 134 | text = lipgloss.NewStyle().MarginLeft(3).Render(fmt.Sprintf("%d. %s", t.ID, text)) 135 | 136 | io.WriteString(w, text) 137 | } 138 | 139 | func Shorten(s string, maxLen int) string { 140 | if len(s) <= maxLen { 141 | return lipgloss.NewStyle().Width(maxLen).Render(s) 142 | } 143 | if maxLen <= 1 { 144 | return s[:maxLen] 145 | } 146 | return s[:maxLen-1] + "…" 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toney 2 | 3 | **Toney** is a fast, lightweight, terminal-based note-taking app for the modern developer. Built with [Bubbletea](https://github.com/charmbracelet/bubbletea), Toney brings a sleek TUI interface with markdown rendering, file navigation, and native Neovim editing – all in your terminal. 4 | 5 | Refer to the docs [here](https://sourcewarelab.github.io/Toney/). To view all the details! 6 | 7 | 8 | 9 | 10 | 11 | https://github.com/user-attachments/assets/71d4555f-8ea1-4a5c-b1df-c67acbd9248a 12 | 13 | 14 | 15 | 16 | 17 | --- 18 | 19 | ## ✨ Features 20 | 21 | - ⚡ **Fast** – Minimal memory usage and snappy performance. 22 | - 📝 **Markdown Renderer** – Styled previews via [`glamour`](https://github.com/charmbracelet/glamour). 23 | - 🧠 **Neovim Integration** – Edit your notes using your favorite editor (`nvim`). 24 | - 📂 **File Management** – Easily navigate, open, and manage markdown files. 25 | - ✅ **Daily Tasks** – Keep a record of your daily tasks in the `Daily Tasks` section. 26 | - 📓 **Diary Entries** - Organize and manage your thoughts with the `Diary` section. 27 | - ⚙️ **Extensive Config** - Configurate the app to look and work exactly as `you` want. 28 | - 🎨 **TUI Styling** – Clean, responsive interface using `lipgloss`. 29 | 30 | --- 31 | 32 | ## 🚀 Installation 33 | 34 | Refer to the docs [here](https://sourcewarelab.github.io/Toney/guide/install). To view all the installation options! 35 | 36 | Run this command to ensure Toney is setup perfectly. 37 | 38 | ``` 39 | toney init 40 | ``` 41 | 42 | 43 | ### ✅ Verify Installation 44 | 45 | Once installed, you can run: 46 | 47 | ``` 48 | toney init 49 | ``` 50 | 51 | --- 52 | 53 | ## 🔑 Keybinds 54 | 55 | Refer to the docs [here](https://sourcewarelab.github.io/Toney/config/keybinds/). To view all the Default Keybinds! 56 | 57 | Keybinds are also visible in the app itself thanks to the `bubbles` component library. 58 | 59 | --- 60 | 61 | ## 🗺 Roadmap 62 | 63 | ### v2.0.0 Goals 64 | 65 | - [ ] Overlay Error Popups 66 | - [ ] File Import/Export 67 | - [ ] Fuzzy Finder in notes 68 | - [ ] Recurring and Unique Daily tasks 69 | - [ ] Nvim TODO Integration 70 | - [ ] Github Issues Integration 71 | - [ ] Bug solve 72 | 73 | ### Long Term Goals 74 | 75 | - [ ] Cross-platform **mobile app** 76 | - [ ] **Server sync** with configuration & cloud storage 77 | 78 | --- 79 | 80 | ## 🛠️ Project Structure 81 | 82 | ``` 83 | ├── cmd // CLI 84 | └── internal 85 | ├── colors // Styles Config 86 | ├── config // Config 87 | ├── enums // All enums 88 | ├── fileTree // Filtree - manages tree creation for directory 89 | ├── keymap // Keymap 90 | ├── messages // Messages - has all bubbletea message declarations 91 | ├── models // Models - Has all bubbletea models 92 | └── styles // Styles - has all lipgloss styles declarations 93 | ``` 94 | 95 | --- 96 | 97 | ## 🤝 Contributing 98 | 99 | We welcome contributions! Toney follows Go and Bubbletea conventions. You can also view more details [here](https://sourcewarelab.github.io/Toney/guide/contribute). 100 | 101 | ### 🧾 Guidelines 102 | 103 | - Follow idiomatic Go formatting (`go fmt`, `go vet`, `golint`). 104 | - Use `Init`, `Update`, and `View` separation for all models. 105 | - Keep component responsibilities well-isolated. 106 | - All exported functions/types should be documented with Go-style comments. 107 | - Prefer `tea.Msg` messages over direct cross-component function calls. 108 | 109 | ### ✅ How to contribute 110 | 111 | 1. Fork the repo and create a feature branch: 112 | ```bash 113 | git checkout -b feature/my-feature 114 | ``` 115 | 116 | 2. Write your code and make sure it builds: 117 | ```bash 118 | go build ./... 119 | ``` 120 | 121 | 3. Format your code: 122 | ```bash 123 | go fmt ./... 124 | ``` 125 | 126 | 4. Commit and push: 127 | ```bash 128 | git commit -m "feat: add my awesome feature" 129 | git push origin feature/my-feature 130 | ``` 131 | 132 | 5. Submit a Pull Request 🎉 133 | 134 | Please open an issue or discussion for large changes before starting them. 135 | 136 | --- 137 | 138 | ## 📄 License 139 | 140 | MIT License. See [LICENSE](./LICENSE). 141 | 142 | --- 143 | 144 | ## 💡 Inspiration 145 | 146 | Toney is inspired by: 147 | - [Glow](https://github.com/charmbracelet/glow) – for markdown rendering 148 | - [Lazygit](https://github.com/jesseduffield/lazygit) – for terminal UI polish 149 | - [Charm ecosystem](https://github.com/charmbracelet) – for all things delightful in the terminal 150 | 151 | --- 152 | 153 | > Made with 💀 by [Nucleo](https://github.com/NucleoFusion) & [SourcewareLab](https://discord.gg/X69MUr2DKm) 154 | 155 | -------------------------------------------------------------------------------- /internal/models/taskPopup/taskPopup.go: -------------------------------------------------------------------------------- 1 | package taskpopup 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/enums" 5 | "github.com/SourcewareLab/Toney/internal/keymap" 6 | "github.com/SourcewareLab/Toney/internal/messages" 7 | "github.com/SourcewareLab/Toney/internal/styles" 8 | "github.com/charmbracelet/bubbles/help" 9 | "github.com/charmbracelet/bubbles/key" 10 | tea "github.com/charmbracelet/bubbletea" 11 | "github.com/charmbracelet/lipgloss" 12 | ) 13 | 14 | type TaskPopup struct { 15 | Width int 16 | Height int 17 | Type enums.TaskPopup 18 | Keymap keymap.TaskPopupMap 19 | Help help.Model 20 | Form *Form 21 | Select *SelectStatus 22 | DeleteForm *DeleteForm 23 | ShowSelect bool 24 | } 25 | 26 | func NewPopup(w int, h int, typ enums.TaskPopup) *TaskPopup { 27 | return &TaskPopup{ 28 | Width: w, 29 | Height: h, 30 | Type: typ, 31 | Keymap: keymap.NewTaskPopupMap(), 32 | Help: help.New(), 33 | Form: NewForm(w, h), 34 | Select: NewSelect(w/3, h), 35 | DeleteForm: NewDeleteForm(w/3, h), 36 | ShowSelect: false, 37 | } 38 | } 39 | 40 | func (m *TaskPopup) Init() tea.Cmd { 41 | return nil 42 | } 43 | 44 | func (m *TaskPopup) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 45 | switch msg := msg.(type) { 46 | case tea.KeyMsg: 47 | switch { 48 | case key.Matches(msg, m.Keymap.Enter): 49 | if m.Type == enums.CreateUnique && !m.ShowSelect { 50 | m.ShowSelect = true 51 | return m, nil 52 | } else if m.Type == enums.CreateRecurring && !m.ShowSelect { 53 | m.ShowSelect = true 54 | return m, nil 55 | } 56 | return m, func() tea.Msg { 57 | return messages.TaskPopupMessage{ // returning all values, daily.go will handle the logic 58 | Title: m.Form.TitleInput.Value(), 59 | Type: m.Type, 60 | Desc: m.Form.DescInput.Value(), 61 | Status: m.Select.Opts[m.Select.Selected], 62 | IsDeleted: m.DeleteForm.isDeleting, 63 | } 64 | } 65 | case key.Matches(msg, m.Keymap.Exit): 66 | return m, func() tea.Msg { 67 | return messages.TaskPopupMessage{Type: enums.ClosePopup} // closes the popup 68 | } 69 | } 70 | } 71 | 72 | switch m.Type { 73 | case enums.CreateRecurring: 74 | fallthrough 75 | case enums.CreateUnique: 76 | if m.ShowSelect { 77 | updated, _ := m.Select.Update(msg) 78 | if sel, ok := updated.(*SelectStatus); ok { // Type matching, cause I cant assign it straightaway 79 | m.Select = sel 80 | return m, nil 81 | } 82 | } 83 | updated, _ := m.Form.Update(msg) 84 | if form, ok := updated.(*Form); ok { // Type matching, cause I cant assign it straightaway 85 | m.Form = form 86 | return m, nil 87 | } 88 | case enums.ChangeStatus: 89 | updated, _ := m.Select.Update(msg) 90 | if sel, ok := updated.(*SelectStatus); ok { // Type matching, cause I cant assign it straightaway 91 | m.Select = sel 92 | return m, nil 93 | } 94 | case enums.Delete: 95 | updated, _ := m.DeleteForm.Update(msg) 96 | if del, ok := updated.(*DeleteForm); ok { // Type matching, cause I cant assign it straightaway 97 | m.DeleteForm = del 98 | return m, nil 99 | } 100 | case enums.Edit: 101 | updated, _ := m.Form.Update(msg) 102 | if form, ok := updated.(*Form); ok { // Type matching, cause I cant assign it straightaway 103 | m.Form = form 104 | return m, nil 105 | } 106 | } 107 | 108 | return m, nil 109 | } 110 | 111 | func (m *TaskPopup) View() string { 112 | view := "" 113 | 114 | switch m.Type { 115 | case enums.CreateRecurring: 116 | fallthrough 117 | case enums.CreateUnique: 118 | if m.ShowSelect { 119 | view = lipgloss.JoinVertical(lipgloss.Center, 120 | styles.GetSelectStatus(m.Width, m.Height/2), 121 | lipgloss.Place(m.Width, m.Height/2, lipgloss.Center, lipgloss.Top, m.Select.View())) 122 | } else { 123 | view = m.Form.View() 124 | } 125 | case enums.ChangeStatus: 126 | view = lipgloss.JoinVertical(lipgloss.Center, 127 | styles.GetSelectStatus(m.Width, m.Height/2), 128 | lipgloss.Place(m.Width, m.Height/2, lipgloss.Center, lipgloss.Top, m.Select.View())) 129 | case enums.Delete: 130 | view = lipgloss.JoinVertical(lipgloss.Center, 131 | lipgloss.Place(m.Width, m.Height/2, lipgloss.Center, lipgloss.Center, styles.GetSelectStatus(m.Width, m.Height/3)), 132 | lipgloss.Place(m.Width, m.Height/2, lipgloss.Center, lipgloss.Top, m.DeleteForm.View())) 133 | case enums.Edit: 134 | view = m.Form.View() 135 | } 136 | 137 | binds := m.Keymap.Bindings() 138 | if m.Type == enums.CreateUnique || m.Type == enums.Edit || m.Type == enums.CreateRecurring { 139 | binds = append(binds, m.Form.Keymap.Bindings()...) 140 | } 141 | 142 | return lipgloss.JoinVertical(lipgloss.Center, view, m.Help.View(keymap.NewDynamic(binds))) 143 | } 144 | -------------------------------------------------------------------------------- /internal/models/viewer/viewer.go: -------------------------------------------------------------------------------- 1 | package viewer 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | 8 | "github.com/SourcewareLab/Toney/internal/config" 9 | "github.com/SourcewareLab/Toney/internal/keymap" 10 | "github.com/SourcewareLab/Toney/internal/messages" 11 | "github.com/SourcewareLab/Toney/internal/models/fzf" 12 | 13 | "github.com/SourcewareLab/Toney/internal/colors" 14 | "github.com/charmbracelet/bubbles/key" 15 | "github.com/charmbracelet/bubbles/viewport" 16 | tea "github.com/charmbracelet/bubbletea" 17 | "github.com/charmbracelet/glamour" 18 | "github.com/charmbracelet/lipgloss" 19 | ) 20 | 21 | type Viewer struct { 22 | IsFocused bool 23 | Height int 24 | Width int 25 | Viewport viewport.Model 26 | Ready bool 27 | ShowFinder bool 28 | Path string 29 | Content []string 30 | Highlighted int 31 | isEditing bool 32 | Renderer *glamour.TermRenderer 33 | Keymap keymap.ViewerKeyMap 34 | Finder fzf.FuzzyFinder 35 | } 36 | 37 | func NewViewer(w int, h int) *Viewer { 38 | vp := viewport.New(w*3/4, h) 39 | vp.YOffset = 0 40 | vp.Style = lipgloss.NewStyle(). 41 | Align(lipgloss.Center, lipgloss.Center). 42 | BorderStyle(lipgloss.RoundedBorder()). 43 | MarginTop(1). 44 | Padding(1, 1). 45 | BorderForeground(colors.ColorPalette().Border) 46 | vp.SetContent( 47 | lipgloss.Place(w*3/4, h-2, lipgloss.Center, lipgloss.Center, 48 | lipgloss.NewStyle().Foreground(colors.ColorPalette().Text).Render("Select a file to view its contents"), 49 | )) 50 | 51 | r, _ := glamour.NewTermRenderer(glamour.WithStyles(config.ToGlamourStyle(config.AppConfig.Styles.Renderer)), 52 | glamour.WithWordWrap(w*3/4-2)) 53 | 54 | return &Viewer{ 55 | Viewport: vp, 56 | Height: h, 57 | Width: w, 58 | isEditing: false, 59 | Keymap: keymap.NewViewerKeyMap(), 60 | Renderer: r, 61 | } 62 | } 63 | 64 | func (m Viewer) Init() tea.Cmd { 65 | return nil 66 | } 67 | 68 | func (m *Viewer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 69 | switch msg := msg.(type) { 70 | case messages.EditorClose: 71 | m.Content = m.ReadFile() 72 | m.Viewport.SetContent(m.RenderMarkdown(m.Content, m.Width)) 73 | return m, nil 74 | case messages.ChangeFileMessage: 75 | m.Path = msg.Path 76 | m.Content = m.ReadFile() 77 | m.Viewport.SetContent(m.RenderMarkdown(m.Content, m.Width)) 78 | m.Viewport.YOffset = 0 79 | m.Highlighted = -1 80 | return m, nil 81 | case tea.WindowSizeMsg: 82 | m.Width = msg.Width 83 | m.Height = msg.Height 84 | 85 | m.Viewport.Height = msg.Height 86 | m.Viewport.Height = msg.Width * 3 / 4 87 | 88 | return m, nil 89 | case messages.FzfSelection: 90 | m.ShowFinder = false 91 | 92 | if !msg.Exited { 93 | x := -1 94 | for k, v := range m.Content { 95 | if v == msg.Selection { 96 | x = k 97 | break 98 | } 99 | } 100 | 101 | m.Highlighted = x 102 | } 103 | return m, nil 104 | case tea.KeyMsg: 105 | switch { 106 | case key.Matches(msg, m.Keymap.Grep): 107 | m.Finder = fzf.NewFzf(m.Content, m.Width*3/4, m.Height) 108 | m.ShowFinder = true 109 | return m, nil 110 | } 111 | } 112 | 113 | if m.ShowFinder { 114 | var cmd tea.Cmd 115 | m.Finder, cmd = m.Finder.Update(msg) 116 | return m, cmd 117 | } 118 | 119 | var ( 120 | cmd tea.Cmd 121 | cmds []tea.Cmd 122 | ) 123 | 124 | m.Viewport, cmd = m.Viewport.Update(msg) 125 | 126 | cmds = append(cmds, cmd) 127 | 128 | return m, tea.Batch(cmds...) 129 | } 130 | 131 | func (m Viewer) View() string { 132 | if m.ShowFinder { 133 | return m.Finder.View() 134 | } 135 | 136 | if m.IsFocused { 137 | m.Viewport.Style = m.Viewport.Style.BorderForeground(colors.ColorPalette().FocusedBorder) 138 | } else { 139 | m.Viewport.Style = m.Viewport.Style.BorderForeground(colors.ColorPalette().Border) 140 | } 141 | 142 | return m.Viewport.View() 143 | } 144 | 145 | func (m *Viewer) Header() string { 146 | return "" 147 | } 148 | 149 | func (m *Viewer) ReadFile() []string { // Change to editor type when config done 150 | path := strings.TrimSuffix(m.Path, "/") 151 | 152 | content, err := os.ReadFile(path) 153 | if err != nil { 154 | fmt.Println(err.Error()) 155 | content = ([]byte)(fmt.Sprintf("An error occured while reading the file:%s\n%s", m.Path, err.Error())) 156 | } 157 | 158 | return strings.Split(string(content), "\n") 159 | } 160 | 161 | func (m *Viewer) RenderMarkdown(md []string, width int) string { 162 | out, _ := m.Renderer.Render(strings.Join(md, "\n")) 163 | 164 | if m.Highlighted != -1 { 165 | s := strings.Split(out, "\n") 166 | s[m.Highlighted] = lipgloss.NewStyle().Background(colors.ColorPalette().MenuSelectedBg).Foreground(colors.ColorPalette().MenuSelectedText).Render(md[m.Highlighted]) 167 | out = strings.Join(s, "\n") 168 | } 169 | 170 | return out 171 | } 172 | -------------------------------------------------------------------------------- /internal/models/daily/daily.go: -------------------------------------------------------------------------------- 1 | package daily 2 | 3 | import ( 4 | "github.com/SourcewareLab/Toney/internal/colors" 5 | "github.com/SourcewareLab/Toney/internal/enums" 6 | "github.com/SourcewareLab/Toney/internal/keymap" 7 | "github.com/SourcewareLab/Toney/internal/messages" 8 | taskpopup "github.com/SourcewareLab/Toney/internal/models/taskPopup" 9 | "github.com/SourcewareLab/Toney/internal/styles" 10 | "github.com/charmbracelet/bubbles/help" 11 | "github.com/charmbracelet/bubbles/key" 12 | "github.com/charmbracelet/bubbles/list" 13 | tea "github.com/charmbracelet/bubbletea" 14 | "github.com/charmbracelet/lipgloss" 15 | ) 16 | 17 | type Daily struct { 18 | Width int 19 | Height int 20 | List list.Model 21 | Tasks []Task 22 | Popup *taskpopup.TaskPopup 23 | ShowPopup bool 24 | Keymap keymap.DailyTaskMap 25 | Help help.Model 26 | } 27 | 28 | func NewDaily(w int, h int) *Daily { 29 | tasks := GetItems() 30 | 31 | lst := list.New(TaskToItems(tasks), TaskDelegate{}, w/2, 2*h/3) 32 | km := list.DefaultKeyMap() 33 | km.Quit.Unbind() 34 | lst.KeyMap = km 35 | lst.SetShowHelp(false) 36 | lst.SetShowTitle(false) 37 | 38 | return &Daily{ 39 | Width: w, 40 | Height: h, 41 | List: lst, 42 | Tasks: tasks, 43 | Keymap: keymap.NewDailyTaskMap(), 44 | Help: help.New(), 45 | } 46 | } 47 | 48 | func (m *Daily) Init() tea.Cmd { 49 | return nil 50 | } 51 | 52 | func (m *Daily) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 53 | switch msg := msg.(type) { 54 | case messages.TaskPopupMessage: 55 | switch msg.Type { 56 | case enums.CreateRecurring: 57 | fallthrough 58 | case enums.CreateUnique: 59 | m.CreateTask(msg, msg.Type == enums.CreateUnique) 60 | case enums.Delete: 61 | m.DeleteTask(msg) 62 | case enums.ChangeStatus: 63 | m.StatusChangeTask(msg) 64 | case enums.Edit: 65 | m.EditTask(msg) 66 | } 67 | 68 | m.Refresh() 69 | m.ShowPopup = false 70 | return m, nil 71 | case tea.KeyMsg: 72 | if m.ShowPopup { 73 | updated, cmd := m.Popup.Update(msg) 74 | if popup, ok := updated.(*taskpopup.TaskPopup); ok { // Type matching, cause I cant assign it straightaway 75 | m.Popup = popup 76 | return m, cmd 77 | } 78 | } 79 | switch { 80 | case key.Matches(msg, m.Keymap.CreateUnique): 81 | m.Popup = taskpopup.NewPopup(m.Width, m.Height, enums.CreateUnique) 82 | m.ShowPopup = true 83 | return m, nil 84 | case key.Matches(msg, m.Keymap.CreateRecurring): 85 | m.Popup = taskpopup.NewPopup(m.Width, m.Height, enums.CreateRecurring) 86 | m.ShowPopup = true 87 | return m, nil 88 | case key.Matches(msg, m.Keymap.ChangeStatus): 89 | m.Popup = taskpopup.NewPopup(m.Width, m.Height, enums.ChangeStatus) 90 | m.ShowPopup = true 91 | return m, nil 92 | case key.Matches(msg, m.Keymap.DeleteTask): 93 | m.Popup = taskpopup.NewPopup(m.Width, m.Height, enums.Delete) 94 | m.ShowPopup = true 95 | return m, nil 96 | case key.Matches(msg, m.Keymap.EditTask): 97 | item := m.List.SelectedItem() 98 | 99 | task, ok := item.(Task) 100 | if !ok { // Making sure that item is of type Task 101 | return m, nil 102 | } 103 | 104 | m.Popup = taskpopup.NewPopup(m.Width, m.Height, enums.Edit) 105 | m.Popup.Form.TitleInput.SetValue(task.TaskTitle) 106 | m.Popup.Form.DescInput.SetValue(task.TaskDesc) 107 | 108 | m.ShowPopup = true 109 | return m, nil 110 | case key.Matches(msg, m.Keymap.BackToMenu): 111 | return m, func() tea.Msg { 112 | return messages.ChangePage{ 113 | Page: enums.MenuPage, 114 | } 115 | } 116 | } 117 | } 118 | 119 | var cmd tea.Cmd 120 | 121 | m.List, cmd = m.List.Update(msg) 122 | 123 | return m, cmd 124 | } 125 | 126 | func (m *Daily) View() string { 127 | if m.ShowPopup { 128 | return m.Popup.View() 129 | } 130 | 131 | main := lipgloss.JoinVertical(lipgloss.Left, 132 | styles.GetDailyText(m.Width, m.Height/3), 133 | lipgloss.Place(m.Width, 2*m.Height/3, lipgloss.Center, lipgloss.Left, m.List.View())) 134 | 135 | if len(m.List.Items()) == 0 { 136 | main = lipgloss.JoinVertical(lipgloss.Left, 137 | styles.GetDailyText(m.Width, m.Height/3), 138 | lipgloss.Place(m.Width, 2*m.Height/3, lipgloss.Center, lipgloss.Top, 139 | lipgloss.NewStyle().Foreground(colors.ColorPalette().Text).Render("You have no Tasks!"))) 140 | } 141 | 142 | help := lipgloss.NewStyle().PaddingLeft(2).Render(m.Help.View(keymap.NewDynamic(m.Keymap.Bindings()))) 143 | 144 | return lipgloss.JoinVertical(lipgloss.Left, main, help) 145 | } 146 | 147 | func (m *Daily) Refresh() { 148 | m.Tasks = GetItems() 149 | 150 | lst := list.New(TaskToItems(m.Tasks), TaskDelegate{}, m.Width/2, 2*m.Height/3) 151 | km := list.DefaultKeyMap() 152 | km.Quit.Unbind() 153 | lst.KeyMap = km 154 | lst.SetShowHelp(false) 155 | lst.SetShowTitle(false) 156 | 157 | m.List = lst 158 | } 159 | -------------------------------------------------------------------------------- /internal/models/homeModel/homeModel.go: -------------------------------------------------------------------------------- 1 | package homemodel 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/SourcewareLab/Toney/internal/config" 8 | "github.com/SourcewareLab/Toney/internal/enums" 9 | filetree "github.com/SourcewareLab/Toney/internal/fileTree" 10 | "github.com/SourcewareLab/Toney/internal/keymap" 11 | "github.com/SourcewareLab/Toney/internal/messages" 12 | fileexplorer "github.com/SourcewareLab/Toney/internal/models/fileExplorer" 13 | "github.com/SourcewareLab/Toney/internal/models/fzf" 14 | viewer "github.com/SourcewareLab/Toney/internal/models/viewer" 15 | 16 | "github.com/charmbracelet/bubbles/help" 17 | "github.com/charmbracelet/bubbles/key" 18 | tea "github.com/charmbracelet/bubbletea" 19 | "github.com/charmbracelet/lipgloss" 20 | ) 21 | 22 | type HomeModel struct { 23 | Width int 24 | Height int 25 | ShowFinder bool 26 | FocusOn enums.Splits 27 | FileExplorer *fileexplorer.FileExplorer 28 | Viewer *viewer.Viewer 29 | Keymap keymap.HomeKeyMap 30 | Finder fzf.FuzzyFinder 31 | Help help.Model 32 | } 33 | 34 | func NewHome(w int, h int) *HomeModel { 35 | home, _ := os.UserHomeDir() 36 | path := filepath.Join(home, config.AppConfig.General.NotesDir) 37 | files, _ := filetree.ListFilesRel(path) 38 | 39 | return &HomeModel{ 40 | Width: w, 41 | Height: h, 42 | ShowFinder: false, 43 | FocusOn: enums.File, 44 | FileExplorer: fileexplorer.NewFileExplorer(w, h), 45 | Viewer: viewer.NewViewer(w, h), 46 | Finder: fzf.NewFzf(files, w, h), 47 | Keymap: keymap.NewHomeKeyMap(), 48 | Help: help.New(), 49 | } 50 | } 51 | 52 | func (m HomeModel) Init() tea.Cmd { 53 | return nil 54 | } 55 | 56 | func (m *HomeModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 57 | switch msg := msg.(type) { 58 | case messages.RefreshView: 59 | return m.Viewer.Update(msg) 60 | case messages.FzfSelection: 61 | if msg.Exited { 62 | m.ShowFinder = false 63 | return m, nil 64 | } 65 | 66 | if m.Viewer.ShowFinder { 67 | updated, cmd := m.Viewer.Update(msg) 68 | if v, ok := updated.(*viewer.Viewer); ok { // Type matching, cause I cant assign it straightaway 69 | m.Viewer = v 70 | } 71 | return m, cmd 72 | } 73 | 74 | updated, cmd := m.FileExplorer.Update(msg) 75 | if fe, ok := updated.(*fileexplorer.FileExplorer); ok { // Type matching, cause I cant assign it straightaway 76 | m.FileExplorer = fe 77 | } 78 | 79 | updated, cmd = m.Viewer.Update(cmd()) 80 | if v, ok := updated.(*viewer.Viewer); ok { // Type matching, cause I cant assign it straightaway 81 | m.Viewer = v 82 | } 83 | 84 | m.ShowFinder = false 85 | return m, cmd 86 | case messages.ShowPopupMessage: 87 | return m, func() tea.Msg { 88 | return msg 89 | } 90 | case messages.ChangeFileMessage: 91 | return m.Viewer.Update(msg) 92 | case tea.WindowSizeMsg: 93 | m.Width = msg.Width 94 | m.Height = msg.Height 95 | 96 | m.FileExplorer.Resize(msg.Width, msg.Height) 97 | m.Viewer = viewer.NewViewer(msg.Width, m.Height) 98 | 99 | return m, nil 100 | case tea.KeyMsg: 101 | switch { 102 | case key.Matches(msg, m.Keymap.FocusExplorer): 103 | m.FocusOn = enums.File 104 | return m, nil 105 | case key.Matches(msg, m.Keymap.FocusViewer): 106 | m.FocusOn = enums.FViewer 107 | return m, nil 108 | case key.Matches(msg, m.Keymap.BackToMenu): 109 | if m.ShowFinder { 110 | var cmd tea.Cmd 111 | m.Finder, cmd = m.Finder.Update(msg) 112 | return m, cmd 113 | } 114 | return m, func() tea.Msg { 115 | return messages.ChangePage{Page: enums.MenuPage} 116 | } 117 | case key.Matches(msg, m.Keymap.Finder): 118 | m.ShowFinder = true 119 | return m, nil 120 | default: 121 | if m.ShowFinder { 122 | var cmd tea.Cmd 123 | m.Finder, cmd = m.Finder.Update(msg) 124 | return m, cmd 125 | } 126 | switch m.FocusOn { 127 | case enums.FViewer: 128 | return m.Viewer.Update(msg) 129 | case enums.File: 130 | return m.FileExplorer.Update(msg) 131 | } 132 | } 133 | } 134 | 135 | return m, nil 136 | } 137 | 138 | func (m HomeModel) View() string { 139 | if m.ShowFinder { 140 | return m.Finder.View() 141 | } 142 | 143 | m.FileExplorer.IsFocused = false 144 | m.Viewer.IsFocused = false 145 | 146 | bindings := []key.Binding{m.Keymap.FocusExplorer, m.Keymap.FocusViewer, m.Keymap.BackToMenu, m.Keymap.Finder} 147 | 148 | if m.FocusOn == enums.File { 149 | m.FileExplorer.IsFocused = true 150 | bindings = append(bindings, m.FileExplorer.Keymap.CreateFile, 151 | m.FileExplorer.Keymap.RenameFile, 152 | m.FileExplorer.Keymap.MoveFile, 153 | m.FileExplorer.Keymap.DeleteFile, 154 | m.FileExplorer.Keymap.OpenForEdit, 155 | ) 156 | } else if m.FocusOn == enums.FViewer { 157 | m.Viewer.IsFocused = true 158 | bindings = append(bindings, 159 | m.Viewer.Keymap.ScrollUp, 160 | m.Viewer.Keymap.ScrollDown, 161 | ) 162 | } 163 | 164 | main := lipgloss.JoinHorizontal(lipgloss.Top, m.FileExplorer.View(), m.Viewer.View()) 165 | 166 | help := lipgloss.NewStyle().PaddingLeft(2).Render(m.Help.View(keymap.NewDynamic(bindings))) 167 | 168 | return lipgloss.JoinVertical(lipgloss.Left, main, help) 169 | } 170 | -------------------------------------------------------------------------------- /internal/config/default.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/charmbracelet/lipgloss" 4 | 5 | func DefaultConfig() Config { 6 | return Config{ 7 | General: GeneralConfig{ 8 | Editor: []string{"nvim"}, 9 | NotesDir: ".toney", // From $HOME Directory 10 | StartScript: []string{}, 11 | StopScript: []string{}, 12 | Script: []string{}, 13 | }, 14 | Keybinds: KeybindsConfig{ 15 | Global: GlobalKeybinds{ 16 | Up: "up", 17 | Down: "down", 18 | Script: "ctrl+s", 19 | }, 20 | Fuzz: FuzzyKeybinds{ 21 | Up: "up", 22 | Down: "down", 23 | Enter: "enter", 24 | StartWriting: "/", 25 | Exit: "esc", 26 | }, 27 | Diary: DiaryKeybinds{ 28 | ScrollUp: "up", 29 | ScrollDown: "down", 30 | Edit: "e", 31 | Finder: "f", 32 | BackToMenu: "esc", 33 | }, 34 | Home: HomeKeybinds{ 35 | FocusViewer: "V", 36 | FocusExplorer: "F", 37 | Create: "c", 38 | Rename: "r", 39 | ScrollUp: "up", 40 | ScrollDown: "down", 41 | Move: "m", 42 | Delete: "d", 43 | Edit: "enter", 44 | BackToMenu: "esc", 45 | Finder: "f", 46 | }, 47 | Daily: DailyKeybinds{ 48 | Create: "c", 49 | CreateRecurring: "r", 50 | Edit: "e", 51 | StatusChange: "s", 52 | Delete: "d", 53 | ExitPopup: "esc", 54 | Enter: "enter", 55 | FormUp: "ctrl+up", 56 | FormDown: "ctrl+down", 57 | BackToMenu: "esc", 58 | }, 59 | }, 60 | Styles: StylesConfig{ 61 | Text: "#cdd6f4", 62 | Background: "#1e1e2e", 63 | Border: "#45475a", 64 | FocusedBorder: "#b4befe", 65 | MenuSelectedBg: "#b4befe", 66 | MenuSelectedText: "#1e1e2e", 67 | ErrorBg: "#11111b", 68 | ErrorText: "#f38ba8", 69 | Icons: IconsConfig{ 70 | FolderIcon: "󰷏", 71 | FileIcon: "", 72 | TaskIcons: TaskIcons{ 73 | CompletedIcon: "✓", 74 | AbandonedIcon: "×", 75 | PendingIcon: "~", 76 | StartedIcon: "○", 77 | }, 78 | }, 79 | TaskStyles: TaskStylesConfig{ 80 | FocusedBar: "#b4befe", 81 | UnfocusedBar: "#45475a", 82 | CompletedStyle: TaskStateStyle{ 83 | Title: "#a6e3a1", 84 | Desc: "#5a7a57", 85 | }, 86 | AbandonedStyle: TaskStateStyle{ 87 | Title: "#f38ba8", 88 | Desc: "#894454", 89 | }, 90 | PendingStyle: TaskStateStyle{ 91 | Title: "#6c7086", 92 | Desc: "#313244", 93 | }, 94 | StartedStyle: TaskStateStyle{ 95 | Title: "#f9e2af", 96 | Desc: "#a38e65", 97 | }, 98 | }, 99 | Renderer: RendererConfig{ 100 | ItemPrefix: lipgloss.NewStyle().Foreground(lipgloss.Color("#45475a")).Render("• "), 101 | EnumerationPrefix: ". ", 102 | Document: BlockStyle{ 103 | BlockPrefix: "\n", 104 | BlockSuffix: "\n", 105 | Margin: 1, 106 | Color: "#cdd6f4", 107 | }, 108 | BlockQuote: BlockStyle{ 109 | Indent: 1, 110 | IndentToken: "│ ", 111 | }, 112 | Heading: HeadingStyle{ 113 | Base: BlockStyle{ 114 | BlockSuffix: "\n", 115 | Color: "#94e2d5", // Pink 116 | Bold: true, 117 | }, 118 | Levels: []BlockStyle{ 119 | {Prefix: " ", Suffix: " ", Color: "#74c7ec", Background: "#313244", Bold: true}, // Sapphire bg 120 | {Prefix: "## "}, 121 | {Prefix: "### "}, 122 | {Prefix: "#### "}, 123 | {Prefix: "##### "}, 124 | {Prefix: "###### ", Color: "#9399b2", Bold: false}, // Overlay2 125 | }, 126 | }, 127 | HorizontalRule: InlineStyle{ 128 | Color: "#585b70", // Surface2 129 | Format: "\n────────\n", 130 | }, 131 | List: ListStyle{ 132 | LevelIndent: 2, 133 | Styles: InlineStyle{ 134 | Color: "#cdd6f4", // Green 135 | Bold: false, 136 | Suffix: "\n", 137 | }, 138 | Task: &TaskStyle{ 139 | Ticked: lipgloss.NewStyle().Foreground(lipgloss.Color("#a6e3a1")).Render("• [✓] "), 140 | Unticked: lipgloss.NewStyle().Foreground(lipgloss.Color("#585b70")).Render("• [ ] "), 141 | }, 142 | }, 143 | Link: InlineStyle{Color: "#89b4fa", Underline: true}, // Blue 144 | Image: InlineStyle{Color: "#fab387", Underline: true}, // Peach 145 | Emph: InlineStyle{Italic: true, Color: "#89b4fa"}, // Pink 146 | Strong: InlineStyle{Bold: true, Color: "#74c7ec"}, 147 | Strikethrough: InlineStyle{Strikethrough: true, Color: "#f38ba8"}, // Red 148 | 149 | Code: BlockStyle{ 150 | Prefix: " ", 151 | Suffix: " ", 152 | Color: "#89dceb", // Sky 153 | Background: "#11111b", // Base bg 154 | }, 155 | 156 | CodeBlock: CodeBlockStyle{ 157 | BlockStyle: BlockStyle{Background: "#11111b", Color: "#89dceb", Margin: 1}, 158 | Text: InlineStyle{Color: "#cdd6f4"}, // Text 159 | }, 160 | 161 | Table: TableStyle{ 162 | BlockStyle: BlockStyle{Color: "#cdd6f4"}, 163 | Header: BlockStyle{Color: "#f5c2e7", Background: "#1e1e2e", Bold: true}, 164 | Cell: BlockStyle{Color: "#cdd6f4", Background: "#1e1e2e"}, 165 | CenterSeparator: "|", 166 | ColumnSeparator: "|", 167 | RowSeparator: "-", 168 | }, 169 | }, 170 | }, 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /internal/models/diary/diary.go: -------------------------------------------------------------------------------- 1 | package diary 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "time" 9 | 10 | "github.com/SourcewareLab/Toney/internal/colors" 11 | "github.com/SourcewareLab/Toney/internal/config" 12 | "github.com/SourcewareLab/Toney/internal/enums" 13 | "github.com/SourcewareLab/Toney/internal/keymap" 14 | "github.com/SourcewareLab/Toney/internal/messages" 15 | "github.com/SourcewareLab/Toney/internal/models/fzf" 16 | "github.com/SourcewareLab/Toney/internal/styles" 17 | "github.com/charmbracelet/bubbles/help" 18 | "github.com/charmbracelet/bubbles/key" 19 | "github.com/charmbracelet/bubbles/viewport" 20 | tea "github.com/charmbracelet/bubbletea" 21 | "github.com/charmbracelet/glamour" 22 | "github.com/charmbracelet/lipgloss" 23 | ) 24 | 25 | type Diary struct { 26 | Width int 27 | Height int 28 | Keymap keymap.DiaryMap 29 | DirPath string 30 | CurrFileName string 31 | ShowFinder bool 32 | CurrDate time.Time 33 | Vp viewport.Model 34 | Help help.Model 35 | Finder fzf.FuzzyFinder 36 | Renderer *glamour.TermRenderer 37 | } 38 | 39 | func NewDiary(w int, h int) *Diary { 40 | today := time.Now().Format("2006-01-02") + ".md" 41 | dirpath := config.AppConfig.General.NotesDir 42 | 43 | r, _ := glamour.NewTermRenderer(glamour.WithStyles(config.ToGlamourStyle(config.AppConfig.Styles.Renderer)), 44 | glamour.WithWordWrap(w)) 45 | content, _ := r.Render(ReadDiary(dirpath, today)) 46 | 47 | vp := viewport.New(w, h-1) 48 | vp.Style = styles.BorderStyle(). 49 | BorderForeground(colors.ColorPalette().FocusedBorder). 50 | Foreground(colors.ColorPalette().Text) 51 | vp.SetContent(content) 52 | 53 | files, _ := AllFiles(dirpath) 54 | 55 | return &Diary{ 56 | Width: w, 57 | Height: h, 58 | Keymap: keymap.NewDiaryMap(), 59 | Vp: vp, 60 | Help: help.New(), 61 | Finder: fzf.NewFzf(files, w, h), 62 | CurrFileName: today, 63 | DirPath: dirpath, 64 | Renderer: r, 65 | } 66 | } 67 | 68 | func (m *Diary) Init() tea.Cmd { 69 | return nil 70 | } 71 | 72 | func (m *Diary) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 73 | switch msg := msg.(type) { 74 | case messages.FzfSelection: 75 | if msg.Exited { 76 | m.ShowFinder = false 77 | return m, nil 78 | } 79 | m.CurrFileName = msg.Selection 80 | m.Refresh() 81 | m.ShowFinder = false 82 | return m, nil 83 | case messages.EditorClose: 84 | m.Refresh() 85 | return m, nil 86 | case tea.KeyMsg: 87 | if m.ShowFinder { 88 | var cmd tea.Cmd 89 | m.Finder, cmd = m.Finder.Update(msg) 90 | return m, cmd 91 | } 92 | switch { 93 | case key.Matches(msg, m.Keymap.Edit): 94 | home, _ := os.UserHomeDir() 95 | 96 | args := config.AppConfig.General.Editor 97 | args = append(args, filepath.Join(home, m.DirPath, ".diary", m.CurrFileName)) 98 | 99 | c := exec.Command(args[0], args[1:]...) 100 | cmd := tea.ExecProcess(c, func(err error) tea.Msg { 101 | return messages.EditorClose{ 102 | Err: err, 103 | } 104 | }) 105 | 106 | return m, cmd 107 | case key.Matches(msg, m.Keymap.OpenFinder): 108 | m.ShowFinder = true 109 | return m, nil 110 | case key.Matches(msg, m.Keymap.BackToMenu): 111 | return m, func() tea.Msg { 112 | return messages.ChangePage{Page: enums.MenuPage} 113 | } 114 | default: 115 | var cmd tea.Cmd 116 | m.Vp, cmd = m.Vp.Update(msg) 117 | return m, cmd 118 | } 119 | } 120 | var cmd tea.Cmd 121 | if m.ShowFinder { 122 | m.Finder, cmd = m.Finder.Update(msg) 123 | } else { 124 | m.Vp, cmd = m.Vp.Update(msg) 125 | } 126 | return m, cmd 127 | } 128 | 129 | func (m *Diary) View() string { 130 | if m.ShowFinder { 131 | return m.Finder.View() 132 | } 133 | 134 | return lipgloss.JoinVertical(lipgloss.Left, m.Vp.View(), 135 | lipgloss.NewStyle().PaddingLeft(2).Render(m.Help.View(keymap.NewDynamic(m.Keymap.Bindings())))) 136 | } 137 | 138 | func (m *Diary) Refresh() { 139 | data := ReadDiary(m.DirPath, m.CurrFileName) 140 | content, _ := m.Renderer.Render(string(data)) 141 | m.Vp.SetContent(content) 142 | } 143 | 144 | func ReadDiary(dirpath string, today string) string { 145 | home, _ := os.UserHomeDir() 146 | path := filepath.Join(home, dirpath, ".diary", today) 147 | 148 | _, err := os.Stat(path) 149 | if os.IsNotExist(err) { 150 | f, _ := os.Create(path) 151 | 152 | now := time.Now() 153 | day := now.Day() 154 | suffix := getOrdinalSuffix(day) 155 | formatted := fmt.Sprintf("%d%s %s %d", day, suffix, now.Format("January"), now.Year()) 156 | 157 | f.WriteString(fmt.Sprintf("# %s \n", formatted)) 158 | f.Close() 159 | } else if err != nil { 160 | return fmt.Sprintf("Creating: %t", os.IsNotExist(err)) 161 | } 162 | 163 | data, err := os.ReadFile(path) 164 | if err != nil { 165 | return err.Error() 166 | } 167 | 168 | return string(data) 169 | } 170 | 171 | func AllFiles(dir string) ([]string, error) { 172 | home, _ := os.UserHomeDir() 173 | entries, err := os.ReadDir(filepath.Join(home, dir, ".diary")) 174 | if err != nil { 175 | return nil, err 176 | } 177 | var names []string 178 | for _, e := range entries { 179 | if !e.IsDir() { 180 | names = append(names, e.Name()) 181 | } 182 | } 183 | return names, nil 184 | } 185 | 186 | func getOrdinalSuffix(day int) string { 187 | if day >= 11 && day <= 13 { 188 | return "th" 189 | } 190 | switch day % 10 { 191 | case 1: 192 | return "st" 193 | case 2: 194 | return "nd" 195 | case 3: 196 | return "rd" 197 | default: 198 | return "th" 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /internal/models/fileExplorer/fileExplorer.go: -------------------------------------------------------------------------------- 1 | package fileexplorer 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/SourcewareLab/Toney/internal/colors" 11 | "github.com/SourcewareLab/Toney/internal/config" 12 | "github.com/SourcewareLab/Toney/internal/enums" 13 | filetree "github.com/SourcewareLab/Toney/internal/fileTree" 14 | "github.com/SourcewareLab/Toney/internal/keymap" 15 | "github.com/SourcewareLab/Toney/internal/messages" 16 | filepopup "github.com/SourcewareLab/Toney/internal/models/filePopup" 17 | "github.com/SourcewareLab/Toney/internal/styles" 18 | 19 | "github.com/charmbracelet/bubbles/key" 20 | "github.com/charmbracelet/bubbles/viewport" 21 | tea "github.com/charmbracelet/bubbletea" 22 | "github.com/charmbracelet/lipgloss" 23 | ) 24 | 25 | type FileExplorer struct { 26 | path string 27 | IsFocused bool 28 | Width int 29 | Height int 30 | Vp viewport.Model 31 | Root *filetree.Node 32 | CurrentNode *filetree.Node 33 | CurrentIndex int 34 | VisibleNodes []*filetree.Node 35 | LastSelection string 36 | Keymap keymap.ExplorerKeyMap 37 | } 38 | 39 | func NewFileExplorer(w int, h int) *FileExplorer { 40 | root, err := filetree.CreateTree() 41 | if err != nil { 42 | fmt.Println(err.Error()) 43 | os.Exit(1) 44 | } 45 | 46 | return &FileExplorer{ 47 | Width: w, 48 | Height: h, 49 | Vp: viewport.New(w/4-1, h), 50 | Root: root, 51 | CurrentNode: root, 52 | CurrentIndex: 0, 53 | VisibleNodes: filetree.FlattenVisibleTree(root), 54 | Keymap: keymap.NewExplorerKeyMap(), 55 | } 56 | } 57 | 58 | func (m FileExplorer) Init() tea.Cmd { 59 | return nil 60 | } 61 | 62 | func (m *FileExplorer) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 63 | switch msg := msg.(type) { 64 | case messages.FzfSelection: 65 | m.FindWithRelativePath(msg.Selection) 66 | m.Refresh() 67 | return m, m.SelectionChanged(m.CurrentNode) 68 | case messages.EditorClose: 69 | m.Refresh() 70 | return m, m.SelectionChanged(m.CurrentNode) 71 | case messages.RefreshFileExplorerMsg: 72 | m.Refresh() 73 | return m, nil 74 | case tea.KeyMsg: 75 | switch { 76 | case key.Matches(msg, m.Keymap.Down): 77 | if m.CurrentIndex >= len(m.VisibleNodes)-1 { 78 | return m, nil 79 | } 80 | m.CurrentIndex += 1 81 | m.CurrentNode = m.VisibleNodes[m.CurrentIndex] 82 | 83 | // fmt.Println(m.CurrentIndex*5, m.Vp.YOffset+m.Vp.Height-5) 84 | if m.CurrentIndex > m.Vp.YOffset+m.Vp.Height-6 { 85 | m.Vp.YOffset += 1 86 | } 87 | 88 | return m, m.SelectionChanged(m.CurrentNode) 89 | case key.Matches(msg, m.Keymap.Up): 90 | if m.CurrentIndex <= 0 { 91 | return m, nil 92 | } 93 | m.CurrentIndex -= 1 94 | m.CurrentNode = m.VisibleNodes[m.CurrentIndex] 95 | 96 | if m.CurrentIndex < m.Vp.YOffset { 97 | m.Vp.YOffset -= 1 98 | } 99 | 100 | return m, m.SelectionChanged(m.CurrentNode) 101 | case key.Matches(msg, m.Keymap.OpenForEdit): 102 | if m.CurrentNode.IsDirectory { 103 | m.CurrentNode.IsExpanded = !m.CurrentNode.IsExpanded 104 | m.VisibleNodes = filetree.FlattenVisibleTree(m.Root) 105 | return m, nil 106 | } 107 | 108 | args := config.AppConfig.General.Editor 109 | args = append(args, strings.TrimSuffix(filepopup.GetPath(m.CurrentNode), "/")) 110 | 111 | c := exec.Command(args[0], args[1:]...) 112 | cmd := tea.ExecProcess(c, func(err error) tea.Msg { 113 | return messages.EditorClose{ 114 | Err: err, 115 | } 116 | }) 117 | return m, cmd 118 | 119 | case key.Matches(msg, m.Keymap.CreateFile): 120 | return m, func() tea.Msg { 121 | return messages.ShowPopupMessage{ 122 | Type: enums.FileCreate, 123 | Curr: m.CurrentNode, 124 | } 125 | } 126 | case key.Matches(msg, m.Keymap.DeleteFile): 127 | return m, func() tea.Msg { 128 | return messages.ShowPopupMessage{ 129 | Type: enums.FileDelete, 130 | Curr: m.CurrentNode, 131 | } 132 | } 133 | case key.Matches(msg, m.Keymap.MoveFile): 134 | return m, func() tea.Msg { 135 | return messages.ShowPopupMessage{ 136 | Type: enums.FileMove, 137 | Curr: m.CurrentNode, 138 | } 139 | } 140 | case key.Matches(msg, m.Keymap.RenameFile): 141 | return m, func() tea.Msg { 142 | return messages.ShowPopupMessage{ 143 | Type: enums.FileRename, 144 | Curr: m.CurrentNode, 145 | } 146 | } 147 | default: 148 | var cmd tea.Cmd 149 | m.Vp, cmd = m.Vp.Update(msg) 150 | return m, cmd 151 | 152 | } 153 | } 154 | 155 | return m, nil 156 | } 157 | 158 | func (m FileExplorer) View() string { 159 | style := styles.BorderStyle() 160 | style = style.Align(lipgloss.Left, lipgloss.Top).MarginTop(1) 161 | 162 | if m.IsFocused { 163 | style = style.BorderForeground(colors.ColorPalette().FocusedBorder) 164 | } 165 | 166 | s := filetree.BuildNodeTree(m.Root, "", len(m.Root.Children) == 0, m.CurrentNode) 167 | 168 | m.Vp.SetContent(s) 169 | m.Vp.Style = style 170 | 171 | return m.Vp.View() 172 | } 173 | 174 | func (m *FileExplorer) Resize(w int, h int) { 175 | m.Height = h 176 | m.Width = w 177 | } 178 | 179 | func (m *FileExplorer) SelectionChanged(node *filetree.Node) tea.Cmd { 180 | path := filepopup.GetPath(node) 181 | if node.IsDirectory || m.LastSelection == path { 182 | return nil 183 | } 184 | 185 | m.LastSelection = path 186 | 187 | return func() tea.Msg { 188 | return messages.ChangeFileMessage{ 189 | Path: path, 190 | } 191 | } 192 | } 193 | 194 | func (m *FileExplorer) Refresh() { 195 | newRoot, _ := filetree.CreateTree() 196 | 197 | filepopup.MapExpanded(newRoot, m.Root) 198 | 199 | m.Root = newRoot 200 | m.VisibleNodes = filetree.FlattenVisibleTree(newRoot) 201 | 202 | idx := -1 203 | 204 | for i, val := range m.VisibleNodes { 205 | if val.Name == m.CurrentNode.Name && filepopup.GetPath(val) == filepopup.GetPath(m.CurrentNode) { 206 | idx = i 207 | } 208 | } 209 | 210 | if idx == -1 { 211 | if m.CurrentIndex != 0 { 212 | idx = m.CurrentIndex - 1 213 | } 214 | } 215 | 216 | m.CurrentIndex = idx 217 | m.CurrentNode = m.VisibleNodes[idx] 218 | } 219 | 220 | func (m *FileExplorer) FindWithRelativePath(path string) { 221 | cleanPath := filepath.ToSlash(path) 222 | parts := strings.Split(cleanPath, "/") 223 | 224 | i := 0 225 | curr := m.Root 226 | for { 227 | if !curr.IsDirectory { 228 | break 229 | } 230 | 231 | for _, v := range curr.Children { // Moving across the tree 232 | if v.Name == parts[i] { 233 | curr = v 234 | if curr.IsDirectory { 235 | curr.IsExpanded = true 236 | } 237 | break 238 | } 239 | } 240 | 241 | i++ 242 | } 243 | 244 | m.CurrentNode = curr 245 | m.VisibleNodes = filetree.FlattenVisibleTree(m.Root) 246 | 247 | currPath := filepopup.GetPath(curr) 248 | for k, v := range m.VisibleNodes { 249 | if filepopup.GetPath(v) == currPath { 250 | m.CurrentIndex = k 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/vue.js: -------------------------------------------------------------------------------- 1 | import { 2 | BaseTransition, 3 | BaseTransitionPropsValidators, 4 | Comment, 5 | DeprecationTypes, 6 | EffectScope, 7 | ErrorCodes, 8 | ErrorTypeStrings, 9 | Fragment, 10 | KeepAlive, 11 | ReactiveEffect, 12 | Static, 13 | Suspense, 14 | Teleport, 15 | Text, 16 | TrackOpTypes, 17 | Transition, 18 | TransitionGroup, 19 | TriggerOpTypes, 20 | VueElement, 21 | assertNumber, 22 | callWithAsyncErrorHandling, 23 | callWithErrorHandling, 24 | camelize, 25 | capitalize, 26 | cloneVNode, 27 | compatUtils, 28 | compile, 29 | computed, 30 | createApp, 31 | createBaseVNode, 32 | createBlock, 33 | createCommentVNode, 34 | createElementBlock, 35 | createHydrationRenderer, 36 | createPropsRestProxy, 37 | createRenderer, 38 | createSSRApp, 39 | createSlots, 40 | createStaticVNode, 41 | createTextVNode, 42 | createVNode, 43 | customRef, 44 | defineAsyncComponent, 45 | defineComponent, 46 | defineCustomElement, 47 | defineEmits, 48 | defineExpose, 49 | defineModel, 50 | defineOptions, 51 | defineProps, 52 | defineSSRCustomElement, 53 | defineSlots, 54 | devtools, 55 | effect, 56 | effectScope, 57 | getCurrentInstance, 58 | getCurrentScope, 59 | getCurrentWatcher, 60 | getTransitionRawChildren, 61 | guardReactiveProps, 62 | h, 63 | handleError, 64 | hasInjectionContext, 65 | hydrate, 66 | hydrateOnIdle, 67 | hydrateOnInteraction, 68 | hydrateOnMediaQuery, 69 | hydrateOnVisible, 70 | initCustomFormatter, 71 | initDirectivesForSSR, 72 | inject, 73 | isMemoSame, 74 | isProxy, 75 | isReactive, 76 | isReadonly, 77 | isRef, 78 | isRuntimeOnly, 79 | isShallow, 80 | isVNode, 81 | markRaw, 82 | mergeDefaults, 83 | mergeModels, 84 | mergeProps, 85 | nextTick, 86 | normalizeClass, 87 | normalizeProps, 88 | normalizeStyle, 89 | onActivated, 90 | onBeforeMount, 91 | onBeforeUnmount, 92 | onBeforeUpdate, 93 | onDeactivated, 94 | onErrorCaptured, 95 | onMounted, 96 | onRenderTracked, 97 | onRenderTriggered, 98 | onScopeDispose, 99 | onServerPrefetch, 100 | onUnmounted, 101 | onUpdated, 102 | onWatcherCleanup, 103 | openBlock, 104 | popScopeId, 105 | provide, 106 | proxyRefs, 107 | pushScopeId, 108 | queuePostFlushCb, 109 | reactive, 110 | readonly, 111 | ref, 112 | registerRuntimeCompiler, 113 | render, 114 | renderList, 115 | renderSlot, 116 | resolveComponent, 117 | resolveDirective, 118 | resolveDynamicComponent, 119 | resolveFilter, 120 | resolveTransitionHooks, 121 | setBlockTracking, 122 | setDevtoolsHook, 123 | setTransitionHooks, 124 | shallowReactive, 125 | shallowReadonly, 126 | shallowRef, 127 | ssrContextKey, 128 | ssrUtils, 129 | stop, 130 | toDisplayString, 131 | toHandlerKey, 132 | toHandlers, 133 | toRaw, 134 | toRef, 135 | toRefs, 136 | toValue, 137 | transformVNodeArgs, 138 | triggerRef, 139 | unref, 140 | useAttrs, 141 | useCssModule, 142 | useCssVars, 143 | useHost, 144 | useId, 145 | useModel, 146 | useSSRContext, 147 | useShadowRoot, 148 | useSlots, 149 | useTemplateRef, 150 | useTransitionState, 151 | vModelCheckbox, 152 | vModelDynamic, 153 | vModelRadio, 154 | vModelSelect, 155 | vModelText, 156 | vShow, 157 | version, 158 | warn, 159 | watch, 160 | watchEffect, 161 | watchPostEffect, 162 | watchSyncEffect, 163 | withAsyncContext, 164 | withCtx, 165 | withDefaults, 166 | withDirectives, 167 | withKeys, 168 | withMemo, 169 | withModifiers, 170 | withScopeId 171 | } from "./chunk-DDXJJ377.js"; 172 | export { 173 | BaseTransition, 174 | BaseTransitionPropsValidators, 175 | Comment, 176 | DeprecationTypes, 177 | EffectScope, 178 | ErrorCodes, 179 | ErrorTypeStrings, 180 | Fragment, 181 | KeepAlive, 182 | ReactiveEffect, 183 | Static, 184 | Suspense, 185 | Teleport, 186 | Text, 187 | TrackOpTypes, 188 | Transition, 189 | TransitionGroup, 190 | TriggerOpTypes, 191 | VueElement, 192 | assertNumber, 193 | callWithAsyncErrorHandling, 194 | callWithErrorHandling, 195 | camelize, 196 | capitalize, 197 | cloneVNode, 198 | compatUtils, 199 | compile, 200 | computed, 201 | createApp, 202 | createBlock, 203 | createCommentVNode, 204 | createElementBlock, 205 | createBaseVNode as createElementVNode, 206 | createHydrationRenderer, 207 | createPropsRestProxy, 208 | createRenderer, 209 | createSSRApp, 210 | createSlots, 211 | createStaticVNode, 212 | createTextVNode, 213 | createVNode, 214 | customRef, 215 | defineAsyncComponent, 216 | defineComponent, 217 | defineCustomElement, 218 | defineEmits, 219 | defineExpose, 220 | defineModel, 221 | defineOptions, 222 | defineProps, 223 | defineSSRCustomElement, 224 | defineSlots, 225 | devtools, 226 | effect, 227 | effectScope, 228 | getCurrentInstance, 229 | getCurrentScope, 230 | getCurrentWatcher, 231 | getTransitionRawChildren, 232 | guardReactiveProps, 233 | h, 234 | handleError, 235 | hasInjectionContext, 236 | hydrate, 237 | hydrateOnIdle, 238 | hydrateOnInteraction, 239 | hydrateOnMediaQuery, 240 | hydrateOnVisible, 241 | initCustomFormatter, 242 | initDirectivesForSSR, 243 | inject, 244 | isMemoSame, 245 | isProxy, 246 | isReactive, 247 | isReadonly, 248 | isRef, 249 | isRuntimeOnly, 250 | isShallow, 251 | isVNode, 252 | markRaw, 253 | mergeDefaults, 254 | mergeModels, 255 | mergeProps, 256 | nextTick, 257 | normalizeClass, 258 | normalizeProps, 259 | normalizeStyle, 260 | onActivated, 261 | onBeforeMount, 262 | onBeforeUnmount, 263 | onBeforeUpdate, 264 | onDeactivated, 265 | onErrorCaptured, 266 | onMounted, 267 | onRenderTracked, 268 | onRenderTriggered, 269 | onScopeDispose, 270 | onServerPrefetch, 271 | onUnmounted, 272 | onUpdated, 273 | onWatcherCleanup, 274 | openBlock, 275 | popScopeId, 276 | provide, 277 | proxyRefs, 278 | pushScopeId, 279 | queuePostFlushCb, 280 | reactive, 281 | readonly, 282 | ref, 283 | registerRuntimeCompiler, 284 | render, 285 | renderList, 286 | renderSlot, 287 | resolveComponent, 288 | resolveDirective, 289 | resolveDynamicComponent, 290 | resolveFilter, 291 | resolveTransitionHooks, 292 | setBlockTracking, 293 | setDevtoolsHook, 294 | setTransitionHooks, 295 | shallowReactive, 296 | shallowReadonly, 297 | shallowRef, 298 | ssrContextKey, 299 | ssrUtils, 300 | stop, 301 | toDisplayString, 302 | toHandlerKey, 303 | toHandlers, 304 | toRaw, 305 | toRef, 306 | toRefs, 307 | toValue, 308 | transformVNodeArgs, 309 | triggerRef, 310 | unref, 311 | useAttrs, 312 | useCssModule, 313 | useCssVars, 314 | useHost, 315 | useId, 316 | useModel, 317 | useSSRContext, 318 | useShadowRoot, 319 | useSlots, 320 | useTemplateRef, 321 | useTransitionState, 322 | vModelCheckbox, 323 | vModelDynamic, 324 | vModelRadio, 325 | vModelSelect, 326 | vModelText, 327 | vShow, 328 | version, 329 | warn, 330 | watch, 331 | watchEffect, 332 | watchPostEffect, 333 | watchSyncEffect, 334 | withAsyncContext, 335 | withCtx, 336 | withDefaults, 337 | withDirectives, 338 | withKeys, 339 | withMemo, 340 | withModifiers, 341 | withScopeId 342 | }; 343 | //# sourceMappingURL=vue.js.map 344 | -------------------------------------------------------------------------------- /internal/config/styles.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | type StylesConfig struct { 4 | Text string `mapstructure:"text"` 5 | Background string `mapstructure:"background"` 6 | Border string `mapstructure:"border"` 7 | FocusedBorder string `mapstructure:"focused_border"` 8 | MenuSelectedBg string `mapstructure:"menu_selected_bg"` 9 | MenuSelectedText string `mapstructure:"menu_selected_text"` 10 | ErrorBg string `mapstructure:"error_background"` // TODO: Docs 11 | ErrorText string `mapstructure:"error_text"` // TODO: Docs 12 | Icons IconsConfig `mapstructure:"icons"` 13 | Renderer RendererConfig `mapstructure:"renderer"` 14 | TaskStyles TaskStylesConfig `mapstructure:"task_styles"` 15 | } 16 | 17 | type IconsConfig struct { 18 | FolderIcon string `mapstructure:"folder_icon"` 19 | FileIcon string `mapstructure:"file_icon"` 20 | TaskIcons TaskIcons `mapstructure:"task_icons"` 21 | } 22 | 23 | type TaskIcons struct { 24 | CompletedIcon string `mapstructure:"completed_icon"` 25 | PendingIcon string `mapstructure:"pending_icon"` 26 | AbandonedIcon string `mapstructure:"abandoned_icon"` 27 | StartedIcon string `mapstructure:"started_icon"` 28 | } 29 | 30 | type RendererConfig struct { 31 | ItemPrefix string `mapstructure:"item_prefix"` 32 | EnumerationPrefix string `mapstructure:"enumeration_prefix"` 33 | Document BlockStyle `mapstructure:"document"` 34 | BlockQuote BlockStyle `mapstructure:"blockquote"` 35 | Heading HeadingStyle `mapstructure:"heading"` 36 | HorizontalRule InlineStyle `mapstructure:"horizontal_rule"` 37 | Paragraph BlockStyle `mapstructure:"paragraph"` 38 | List ListStyle `mapstructure:"list"` 39 | Enumeration InlineStyle `mapstructure:"enumeration"` 40 | Link InlineStyle `mapstructure:"link"` 41 | Image InlineStyle `mapstructure:"image"` 42 | Emph InlineStyle `mapstructure:"emph"` 43 | Strong InlineStyle `mapstructure:"strong"` 44 | Strikethrough InlineStyle `mapstructure:"strikethrough"` 45 | Code BlockStyle `mapstructure:"code"` 46 | CodeBlock CodeBlockStyle `mapstructure:"code_block"` 47 | Table TableStyle `mapstructure:"table"` 48 | } 49 | 50 | type BlockStyle struct { 51 | Format string `mapstructure:"format"` 52 | BlockPrefix string `mapstructure:"block_prefix"` 53 | BlockSuffix string `mapstructure:"block_suffix"` 54 | Prefix string `mapstructure:"prefix"` 55 | Suffix string `mapstructure:"suffix"` 56 | IndentToken string `mapstructure:"indent_token"` 57 | Margin int `mapstructure:"margin"` 58 | Padding int `mapstructure:"padding"` 59 | Indent int `mapstructure:"indent"` 60 | Color string `mapstructure:"color"` 61 | Background string `mapstructure:"background"` 62 | Bold bool `mapstructure:"bold"` 63 | Italic bool `mapstructure:"italic"` 64 | Underline bool `mapstructure:"underline"` 65 | } 66 | 67 | type HeadingStyle struct { 68 | Base BlockStyle `mapstructure:"base"` 69 | Levels []BlockStyle `mapstructure:"levels"` 70 | } 71 | 72 | type ListStyle struct { 73 | LevelIndent int `mapstructure:"indent"` 74 | Styles InlineStyle `mapstructure:"styles"` 75 | Task *TaskStyle `mapstructure:"task,omitempty"` 76 | } 77 | 78 | type TaskStyle struct { 79 | Ticked string `mapstructure:"ticked"` 80 | Unticked string `mapstructure:"unticked"` 81 | } 82 | 83 | type InlineStyle struct { 84 | BlockPrefix string `mapstructure:"block_prefix"` 85 | BlockSuffix string `mapstructure:"block_suffix"` 86 | Prefix string `mapstructure:"prefix"` 87 | Suffix string `mapstructure:"suffix"` 88 | Color string `mapstructure:"color"` 89 | Background string `mapstructure:"background"` 90 | Bold bool `mapstructure:"bold"` 91 | Italic bool `mapstructure:"italic"` 92 | Underline bool `mapstructure:"underline"` 93 | Strikethrough bool `mapstructure:"strikethrough"` 94 | Format string `mapstructure:"format"` 95 | } 96 | 97 | type CodeBlockStyle struct { 98 | BlockStyle `mapstructure:",squash"` 99 | Theme string `mapstructure:"theme"` 100 | Text InlineStyle `mapstructure:"text"` 101 | Error InlineStyle `mapstructure:"error"` 102 | Comment InlineStyle `mapstructure:"comment"` 103 | CommentPreproc InlineStyle `mapstructure:"comment_preproc"` 104 | Keyword InlineStyle `mapstructure:"keyword"` 105 | KeywordReserved InlineStyle `mapstructure:"keyword_reserved"` 106 | KeywordNamespace InlineStyle `mapstructure:"keyword_namespace"` 107 | KeywordType InlineStyle `mapstructure:"keyword_type"` 108 | Operator InlineStyle `mapstructure:"operator"` 109 | Punctuation InlineStyle `mapstructure:"punctuation"` 110 | Name InlineStyle `mapstructure:"name"` 111 | NameBuiltin InlineStyle `mapstructure:"name_builtin"` 112 | NameTag InlineStyle `mapstructure:"name_tag"` 113 | NameAttribute InlineStyle `mapstructure:"name_attribute"` 114 | NameClass InlineStyle `mapstructure:"name_class"` 115 | NameConstant InlineStyle `mapstructure:"name_constant"` 116 | NameDecorator InlineStyle `mapstructure:"name_decorator"` 117 | NameException InlineStyle `mapstructure:"name_exception"` 118 | NameFunction InlineStyle `mapstructure:"name_function"` 119 | NameOther InlineStyle `mapstructure:"name_other"` 120 | Literal InlineStyle `mapstructure:"literal"` 121 | LiteralNumber InlineStyle `mapstructure:"literal_number"` 122 | LiteralDate InlineStyle `mapstructure:"literal_date"` 123 | LiteralString InlineStyle `mapstructure:"literal_string"` 124 | LiteralStringEscape InlineStyle `mapstructure:"literal_string_escape"` 125 | GenericDeleted InlineStyle `mapstructure:"generic_deleted"` 126 | GenericEmph InlineStyle `mapstructure:"generic_emph"` 127 | GenericInserted InlineStyle `mapstructure:"generic_inserted"` 128 | GenericStrong InlineStyle `mapstructure:"generic_strong"` 129 | GenericSubheading InlineStyle `mapstructure:"generic_subheading"` 130 | Background InlineStyle `mapstructure:"background"` 131 | } 132 | 133 | type TableStyle struct { 134 | BlockStyle `mapstructure:",squash"` 135 | Header BlockStyle `mapstructure:"header"` 136 | Cell BlockStyle `mapstructure:"cell"` 137 | CenterSeparator string `mapstructure:"center_separator"` 138 | ColumnSeparator string `mapstructure:"column_separator"` 139 | RowSeparator string `mapstructure:"row_separator"` 140 | } 141 | 142 | type TaskStylesConfig struct { 143 | FocusedBar string `mapstructure:"focused_color"` 144 | UnfocusedBar string `mapstructure:"unfocused_color"` 145 | CompletedStyle TaskStateStyle `mapstructure:"completed_style"` 146 | PendingStyle TaskStateStyle `mapstructure:"pending_style"` 147 | AbandonedStyle TaskStateStyle `mapstructure:"abandoned_style"` 148 | StartedStyle TaskStateStyle `mapstructure:"started_style"` 149 | } 150 | 151 | type TaskStateStyle struct { 152 | Title string `mapstructure:"color"` 153 | Desc string `mapstructure:"text_color"` 154 | } 155 | -------------------------------------------------------------------------------- /internal/models/rootModel.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | "time" 9 | 10 | "github.com/SourcewareLab/Toney/internal/config" 11 | "github.com/SourcewareLab/Toney/internal/enums" 12 | "github.com/SourcewareLab/Toney/internal/messages" 13 | "github.com/SourcewareLab/Toney/internal/models/daily" 14 | "github.com/SourcewareLab/Toney/internal/models/diary" 15 | errorpopup "github.com/SourcewareLab/Toney/internal/models/error" 16 | filepopup "github.com/SourcewareLab/Toney/internal/models/filePopup" 17 | homemodel "github.com/SourcewareLab/Toney/internal/models/homeModel" 18 | "github.com/SourcewareLab/Toney/internal/models/menu" 19 | "github.com/SourcewareLab/Toney/internal/utils" 20 | overlay "github.com/rmhubbert/bubbletea-overlay" 21 | 22 | "github.com/SourcewareLab/Toney/internal/colors" 23 | tea "github.com/charmbracelet/bubbletea" 24 | "github.com/charmbracelet/lipgloss" 25 | ) 26 | 27 | type RootModel struct { 28 | Width int 29 | Height int 30 | Page enums.Page 31 | Home *homemodel.HomeModel 32 | Menu *menu.Menu 33 | Daily *daily.Daily 34 | Diary *diary.Diary 35 | ErrorPopup *errorpopup.ErrorPopup 36 | Overlay *overlay.Model 37 | CurrentPage enums.Page 38 | ShowPopup bool 39 | FilePopupType enums.PopupType 40 | FilePopup *filepopup.FilePopup 41 | isLoading bool 42 | IsShowingError bool 43 | } 44 | 45 | func NewRoot() *RootModel { 46 | return &RootModel{ 47 | Page: enums.MenuPage, 48 | ShowPopup: false, 49 | isLoading: true, 50 | } 51 | } 52 | 53 | func (m RootModel) Init() tea.Cmd { 54 | return nil 55 | } 56 | 57 | func (m *RootModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { 58 | switch msg := msg.(type) { 59 | case messages.CloseError: 60 | m.IsShowingError = false 61 | return m, nil 62 | case messages.ErrorMsg: 63 | m.IsShowingError = true 64 | m.ErrorPopup = errorpopup.NewErrorPopup(m.Width, m.Height, msg.Msg, msg.Title, msg.Locn) 65 | 66 | var curr tea.Model 67 | switch m.CurrentPage { 68 | case enums.DailyPage: 69 | curr = m.Daily 70 | case enums.HomePage: 71 | curr = m.Home 72 | case enums.DiaryPage: 73 | curr = m.Diary 74 | } 75 | 76 | m.Overlay = overlay.New(m.ErrorPopup, curr, overlay.Right, overlay.Top, 2, 2) 77 | return m, tea.Tick(3*time.Second, func(_ time.Time) tea.Msg { return messages.CloseError{} }) 78 | case messages.FzfSelection: 79 | switch m.CurrentPage { 80 | case enums.MenuPage: 81 | pg, cmd := m.Menu.Update(msg) 82 | if d, ok := pg.(*menu.Menu); ok { // Type matching, cause I cant assign it straightaway 83 | m.Menu = d 84 | return m, cmd 85 | } 86 | case enums.HomePage: 87 | pg, cmd := m.Home.Update(msg) 88 | if d, ok := pg.(*homemodel.HomeModel); ok { // Type matching, cause I cant assign it straightaway 89 | m.Home = d 90 | return m, cmd 91 | } 92 | case enums.DailyPage: 93 | pg, cmd := m.Daily.Update(msg) 94 | if d, ok := pg.(*daily.Daily); ok { // Type matching, cause I cant assign it straightaway 95 | m.Daily = d 96 | return m, cmd 97 | } 98 | case enums.DiaryPage: 99 | pg, cmd := m.Diary.Update(msg) 100 | if d, ok := pg.(*diary.Diary); ok { // Type matching, cause I cant assign it straightaway 101 | m.Diary = d 102 | return m, cmd 103 | } 104 | } 105 | case messages.TaskPopupMessage: 106 | if m.CurrentPage == enums.DailyPage { 107 | dailypage, cmd := m.Daily.Update(msg) 108 | if d, ok := dailypage.(*daily.Daily); ok { // Type matching, cause I cant assign it straightaway 109 | m.Daily = d 110 | return m, cmd 111 | } 112 | return m, cmd 113 | } 114 | case messages.ChangePage: 115 | switch msg.Page { 116 | case enums.MenuPage: 117 | m.CurrentPage = enums.MenuPage 118 | case enums.HomePage: 119 | m.CurrentPage = enums.HomePage 120 | case enums.DailyPage: 121 | m.CurrentPage = enums.DailyPage 122 | case enums.DiaryPage: 123 | m.CurrentPage = enums.DiaryPage 124 | case enums.Quit: 125 | return m, tea.Quit 126 | } 127 | case messages.ShowLoader: 128 | m.isLoading = true 129 | return m, nil 130 | case messages.HideLoader: 131 | m.isLoading = false 132 | return m, nil 133 | case messages.ShowPopupMessage: 134 | m.FilePopup = filepopup.NewPopup(msg.Type, msg.Curr) 135 | m.ShowPopup = true 136 | case messages.HidePopupMessage: 137 | m.ShowPopup = false 138 | case messages.RefreshFileExplorerMsg: 139 | m.Home.FileExplorer.Update(msg) 140 | return m, nil 141 | case messages.EditorClose: 142 | if m.CurrentPage == enums.DiaryPage { 143 | m.Diary.Update(msg) 144 | return m, nil 145 | } 146 | if msg.Err != nil { 147 | return m, utils.ReturnError("Root", "Error Closing Editor", msg.Err) 148 | } 149 | m.Home.FileExplorer.Update(msg) 150 | m.Home.Viewer.Update(msg) 151 | 152 | return m, nil 153 | 154 | case tea.KeyMsg: 155 | switch msg.String() { 156 | case config.AppConfig.Keybinds.Global.Script: 157 | // Script 158 | script := strings.Join(config.AppConfig.General.Script, " ") 159 | command := exec.Command("bash", "-c", script) 160 | 161 | command.Stdout = os.Stdout 162 | command.Stderr = os.Stderr 163 | 164 | err := command.Run() 165 | if err != nil { 166 | return m, utils.ReturnError("Root", "Error Closing Editor", err) 167 | } 168 | return m, nil 169 | case "ctrl+c": 170 | // Stop script 171 | script := strings.Join(config.AppConfig.General.StopScript, " ") 172 | command := exec.Command("bash", "-c", script) 173 | 174 | command.Stdout = os.Stdout 175 | command.Stderr = os.Stderr 176 | 177 | err := command.Run() 178 | if err != nil { 179 | return m, utils.ReturnError("Root", "Error Closing Editor", err) 180 | } 181 | return m, tea.Quit 182 | } 183 | 184 | case tea.WindowSizeMsg: 185 | m.Width = msg.Width 186 | m.Height = msg.Height 187 | 188 | if m.Home != nil { // Checking whether this is an app resize or app open 189 | m.Menu.Update(msg) 190 | m.Home.Update(msg) 191 | m.Diary.Update(msg) 192 | m.Daily.Update(msg) 193 | } else { 194 | m.Home = homemodel.NewHome(msg.Width, msg.Height) 195 | m.Menu = menu.NewMenu(msg.Width, msg.Height) 196 | m.Daily = daily.NewDaily(msg.Width, msg.Height) 197 | m.Diary = diary.NewDiary(msg.Width, msg.Height) 198 | } 199 | 200 | m.isLoading = false 201 | 202 | return m, nil 203 | } 204 | 205 | var cmd tea.Cmd 206 | 207 | if m.ShowPopup { 208 | _, cmd = m.FilePopup.Update(msg) 209 | } else { 210 | switch m.CurrentPage { 211 | case enums.MenuPage: 212 | _, cmd = m.Menu.Update(msg) 213 | case enums.HomePage: 214 | _, cmd = m.Home.Update(msg) 215 | case enums.DailyPage: 216 | _, cmd = m.Daily.Update(msg) 217 | case enums.DiaryPage: 218 | _, cmd = m.Diary.Update(msg) 219 | default: 220 | fmt.Printf("UNHANDLED MSG: %#v\n", msg) 221 | } 222 | } 223 | 224 | return m, cmd 225 | } 226 | 227 | func (m *RootModel) View() string { 228 | if m.isLoading { 229 | return lipgloss.NewStyle().Render("Loading...") 230 | } 231 | 232 | if m.IsShowingError { 233 | return m.Overlay.View() 234 | } 235 | 236 | if m.ShowPopup && m.FilePopup != nil { 237 | return lipgloss.Place(m.Width+2, m.Height+2, lipgloss.Center, lipgloss.Center, m.FilePopup.View()) 238 | } 239 | 240 | switch m.CurrentPage { 241 | case enums.HomePage: 242 | return lipgloss.NewStyle().Background(colors.ColorPalette().Background).Render(m.Home.View()) 243 | case enums.MenuPage: 244 | return m.Menu.View() 245 | case enums.DailyPage: 246 | return m.Daily.View() 247 | case enums.DiaryPage: 248 | return m.Diary.View() 249 | default: 250 | return lipgloss.NewStyle().Background(colors.ColorPalette().Background).Render(m.Home.View()) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /internal/config/glamour.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/charmbracelet/glamour/ansi" 4 | 5 | func ToGlamourStyle(styles RendererConfig) ansi.StyleConfig { 6 | cfg := ansi.StyleConfig{ 7 | Document: toStyle(styles.Document), 8 | BlockQuote: toStyle(styles.BlockQuote), 9 | Paragraph: toStyle(styles.Paragraph), 10 | Heading: toHeading(styles.Heading, 0), 11 | H1: toHeading(styles.Heading, 1), 12 | H2: toHeading(styles.Heading, 2), 13 | H3: toHeading(styles.Heading, 3), 14 | H4: toHeading(styles.Heading, 4), 15 | H5: toHeading(styles.Heading, 5), 16 | H6: toHeading(styles.Heading, 6), 17 | HorizontalRule: toInline(styles.HorizontalRule), 18 | List: toList(styles.List), 19 | Enumeration: toInline(styles.Enumeration), 20 | Link: toInline(styles.Link), 21 | Image: toInline(styles.Image), 22 | Emph: toInline(styles.Emph), 23 | Strong: toInline(styles.Strong), 24 | Strikethrough: toInline(styles.Strikethrough), 25 | Code: toStyle(styles.Code), 26 | CodeBlock: toCodeBlockStyle(styles.CodeBlock), 27 | Table: toTableStyle(styles.Table), 28 | } 29 | 30 | cfg.Item = ansi.StylePrimitive{ 31 | BlockPrefix: styles.ItemPrefix, 32 | } 33 | cfg.Enumeration = ansi.StylePrimitive{ 34 | BlockPrefix: styles.EnumerationPrefix, 35 | } 36 | 37 | if styles.List.Task != nil { 38 | cfg.Task = ansi.StyleTask{ 39 | StylePrimitive: ansi.StylePrimitive{}, 40 | Ticked: styles.List.Task.Ticked, 41 | Unticked: styles.List.Task.Unticked, 42 | } 43 | } 44 | 45 | return cfg 46 | } 47 | 48 | func toTableStyle(cfg TableStyle) ansi.StyleTable { 49 | return ansi.StyleTable{ 50 | StyleBlock: toStyle(cfg.BlockStyle), 51 | CenterSeparator: stringPtr(cfg.CenterSeparator), 52 | ColumnSeparator: stringPtr(cfg.ColumnSeparator), 53 | RowSeparator: stringPtr(cfg.RowSeparator), 54 | } 55 | } 56 | 57 | func toCodeBlockStyle(cfg CodeBlockStyle) ansi.StyleCodeBlock { 58 | var chroma ansi.Chroma 59 | 60 | chroma.Text = toInline(cfg.Text) 61 | chroma.Error = toInline(cfg.Error) 62 | chroma.Comment = toInline(cfg.Comment) 63 | chroma.CommentPreproc = toInline(cfg.CommentPreproc) 64 | chroma.Keyword = toInline(cfg.Keyword) 65 | chroma.KeywordReserved = toInline(cfg.KeywordReserved) 66 | chroma.KeywordNamespace = toInline(cfg.KeywordNamespace) 67 | chroma.KeywordType = toInline(cfg.KeywordType) 68 | chroma.Operator = toInline(cfg.Operator) 69 | chroma.Punctuation = toInline(cfg.Punctuation) 70 | chroma.Name = toInline(cfg.Name) 71 | chroma.NameBuiltin = toInline(cfg.NameBuiltin) 72 | chroma.NameTag = toInline(cfg.NameTag) 73 | chroma.NameAttribute = toInline(cfg.NameAttribute) 74 | chroma.NameClass = toInline(cfg.NameClass) 75 | chroma.NameConstant = toInline(cfg.NameConstant) 76 | chroma.NameDecorator = toInline(cfg.NameDecorator) 77 | chroma.NameException = toInline(cfg.NameException) 78 | chroma.NameFunction = toInline(cfg.NameFunction) 79 | chroma.NameOther = toInline(cfg.NameOther) 80 | chroma.Literal = toInline(cfg.Literal) 81 | chroma.LiteralNumber = toInline(cfg.LiteralNumber) 82 | chroma.LiteralDate = toInline(cfg.LiteralDate) 83 | chroma.LiteralString = toInline(cfg.LiteralString) 84 | chroma.LiteralStringEscape = toInline(cfg.LiteralStringEscape) 85 | chroma.GenericDeleted = toInline(cfg.GenericDeleted) 86 | chroma.GenericEmph = toInline(cfg.GenericEmph) 87 | chroma.GenericInserted = toInline(cfg.GenericInserted) 88 | chroma.GenericStrong = toInline(cfg.GenericStrong) 89 | chroma.GenericSubheading = toInline(cfg.GenericSubheading) 90 | chroma.Background = toInline(cfg.Background) 91 | 92 | return ansi.StyleCodeBlock{ 93 | StyleBlock: toStyle(cfg.BlockStyle), 94 | Theme: cfg.Theme, 95 | Chroma: &chroma, 96 | } 97 | } 98 | 99 | func toHeading(cfg HeadingStyle, level int) ansi.StyleBlock { 100 | base := toStyle(cfg.Base) 101 | 102 | if level >= 1 && level <= len(cfg.Levels) { 103 | override := toStyle(cfg.Levels[level-1]) 104 | return mergeBlocks(base, override) 105 | } 106 | 107 | return base 108 | } 109 | 110 | func toList(l ListStyle) ansi.StyleList { 111 | prim := toInline(l.Styles) 112 | 113 | return ansi.StyleList{ 114 | StyleBlock: ansi.StyleBlock{ 115 | StylePrimitive: prim, 116 | Indent: toUintPtr(l.LevelIndent), 117 | }, 118 | LevelIndent: uint(l.LevelIndent), 119 | } 120 | } 121 | 122 | func toInline(i InlineStyle) ansi.StylePrimitive { 123 | return ansi.StylePrimitive{ 124 | BlockPrefix: i.BlockPrefix, 125 | BlockSuffix: i.BlockSuffix, 126 | Prefix: i.Prefix, 127 | Suffix: i.Suffix, 128 | Color: stringPtr(i.Color), 129 | BackgroundColor: stringPtr(i.Background), 130 | Bold: boolPtr(i.Bold), 131 | Italic: boolPtr(i.Italic), 132 | Underline: boolPtr(i.Underline), 133 | CrossedOut: boolPtr(i.Strikethrough), 134 | Format: i.Format, 135 | } 136 | } 137 | 138 | func toStyle(b BlockStyle) ansi.StyleBlock { 139 | prim := ansi.StylePrimitive{ 140 | BlockPrefix: b.BlockPrefix, 141 | BlockSuffix: b.BlockSuffix, 142 | Prefix: b.Prefix, 143 | Suffix: b.Suffix, 144 | Color: stringPtr(b.Color), 145 | BackgroundColor: stringPtr(b.Background), 146 | Bold: boolPtr(b.Bold), 147 | Italic: boolPtr(b.Italic), 148 | Underline: boolPtr(b.Underline), 149 | CrossedOut: nil, 150 | Faint: nil, 151 | Conceal: nil, 152 | Overlined: nil, 153 | Inverse: nil, 154 | Blink: nil, 155 | Format: b.Format, 156 | } 157 | return ansi.StyleBlock{ 158 | StylePrimitive: prim, 159 | Indent: toUintPtr(b.Indent), 160 | IndentToken: stringPtr(b.IndentToken), 161 | Margin: toUintPtr(b.Margin), 162 | } 163 | } 164 | 165 | func stringPtr(s string) *string { 166 | if s == "" { 167 | return nil 168 | } 169 | return &s 170 | } 171 | 172 | func boolPtr(b bool) *bool { 173 | if !b { 174 | return nil 175 | } 176 | return &b 177 | } 178 | 179 | func toUintPtr(v int) *uint { 180 | u := uint(v) 181 | return &u 182 | } 183 | 184 | func mergeBlocks(a, b ansi.StyleBlock) ansi.StyleBlock { 185 | prim := a.StylePrimitive 186 | op := b.StylePrimitive 187 | 188 | if op.Prefix != "" { 189 | prim.Prefix = op.Prefix 190 | } 191 | if op.Suffix != "" { 192 | prim.Suffix = op.Suffix 193 | } 194 | if op.Color != nil { 195 | prim.Color = op.Color 196 | } 197 | if op.BackgroundColor != nil { 198 | prim.BackgroundColor = op.BackgroundColor 199 | } 200 | if op.Bold != nil { 201 | prim.Bold = op.Bold 202 | } 203 | if op.Italic != nil { 204 | prim.Italic = op.Italic 205 | } 206 | if op.Underline != nil { 207 | prim.Underline = op.Underline 208 | } 209 | if op.CrossedOut != nil { 210 | prim.CrossedOut = op.CrossedOut 211 | } 212 | if op.Faint != nil { 213 | prim.Faint = op.Faint 214 | } 215 | if op.Conceal != nil { 216 | prim.Conceal = op.Conceal 217 | } 218 | if op.Overlined != nil { 219 | prim.Overlined = op.Overlined 220 | } 221 | if op.Blink != nil { 222 | prim.Blink = op.Blink 223 | } 224 | if op.Inverse != nil { 225 | prim.Inverse = op.Inverse 226 | } 227 | if op.Format != "" { 228 | prim.Format = op.Format 229 | } 230 | if op.BlockPrefix != "" { 231 | prim.BlockPrefix = op.BlockPrefix 232 | } 233 | if op.BlockSuffix != "" { 234 | prim.BlockSuffix = op.BlockSuffix 235 | } 236 | 237 | if b.Indent != nil { 238 | a.Indent = b.Indent 239 | } 240 | 241 | if b.Margin != nil { 242 | a.Margin = b.Margin 243 | } 244 | 245 | if b.IndentToken != nil { 246 | a.IndentToken = b.IndentToken 247 | } 248 | 249 | a.StylePrimitive = prim 250 | return a 251 | } 252 | -------------------------------------------------------------------------------- /docs/docs/.vitepress/cache/deps/vitepress___@vueuse_core.js: -------------------------------------------------------------------------------- 1 | import { 2 | DefaultMagicKeysAliasMap, 3 | StorageSerializers, 4 | TransitionPresets, 5 | assert, 6 | breakpointsAntDesign, 7 | breakpointsBootstrapV5, 8 | breakpointsElement, 9 | breakpointsMasterCss, 10 | breakpointsPrimeFlex, 11 | breakpointsQuasar, 12 | breakpointsSematic, 13 | breakpointsTailwind, 14 | breakpointsVuetify, 15 | breakpointsVuetifyV2, 16 | breakpointsVuetifyV3, 17 | bypassFilter, 18 | camelize, 19 | clamp, 20 | cloneFnJSON, 21 | computedAsync, 22 | computedEager, 23 | computedInject, 24 | computedWithControl, 25 | containsProp, 26 | controlledRef, 27 | createEventHook, 28 | createFetch, 29 | createFilterWrapper, 30 | createGlobalState, 31 | createInjectionState, 32 | createRef, 33 | createReusableTemplate, 34 | createSharedComposable, 35 | createSingletonPromise, 36 | createTemplatePromise, 37 | createUnrefFn, 38 | customStorageEventName, 39 | debounceFilter, 40 | defaultDocument, 41 | defaultLocation, 42 | defaultNavigator, 43 | defaultWindow, 44 | executeTransition, 45 | extendRef, 46 | formatDate, 47 | formatTimeAgo, 48 | get, 49 | getLifeCycleTarget, 50 | getSSRHandler, 51 | hasOwn, 52 | hyphenate, 53 | identity, 54 | increaseWithUnit, 55 | injectLocal, 56 | invoke, 57 | isClient, 58 | isDef, 59 | isDefined, 60 | isIOS, 61 | isObject, 62 | isWorker, 63 | makeDestructurable, 64 | mapGamepadToXbox360Controller, 65 | noop, 66 | normalizeDate, 67 | notNullish, 68 | now, 69 | objectEntries, 70 | objectOmit, 71 | objectPick, 72 | onClickOutside, 73 | onElementRemoval, 74 | onKeyDown, 75 | onKeyPressed, 76 | onKeyStroke, 77 | onKeyUp, 78 | onLongPress, 79 | onStartTyping, 80 | pausableFilter, 81 | promiseTimeout, 82 | provideLocal, 83 | provideSSRWidth, 84 | pxValue, 85 | rand, 86 | reactify, 87 | reactifyObject, 88 | reactiveComputed, 89 | reactiveOmit, 90 | reactivePick, 91 | refAutoReset, 92 | refDebounced, 93 | refDefault, 94 | refThrottled, 95 | refWithControl, 96 | resolveRef, 97 | resolveUnref, 98 | set, 99 | setSSRHandler, 100 | syncRef, 101 | syncRefs, 102 | templateRef, 103 | throttleFilter, 104 | timestamp, 105 | toArray, 106 | toReactive, 107 | toRef, 108 | toRefs, 109 | toValue, 110 | tryOnBeforeMount, 111 | tryOnBeforeUnmount, 112 | tryOnMounted, 113 | tryOnScopeDispose, 114 | tryOnUnmounted, 115 | unrefElement, 116 | until, 117 | useActiveElement, 118 | useAnimate, 119 | useArrayDifference, 120 | useArrayEvery, 121 | useArrayFilter, 122 | useArrayFind, 123 | useArrayFindIndex, 124 | useArrayFindLast, 125 | useArrayIncludes, 126 | useArrayJoin, 127 | useArrayMap, 128 | useArrayReduce, 129 | useArraySome, 130 | useArrayUnique, 131 | useAsyncQueue, 132 | useAsyncState, 133 | useBase64, 134 | useBattery, 135 | useBluetooth, 136 | useBreakpoints, 137 | useBroadcastChannel, 138 | useBrowserLocation, 139 | useCached, 140 | useClipboard, 141 | useClipboardItems, 142 | useCloned, 143 | useColorMode, 144 | useConfirmDialog, 145 | useCountdown, 146 | useCounter, 147 | useCssVar, 148 | useCurrentElement, 149 | useCycleList, 150 | useDark, 151 | useDateFormat, 152 | useDebounceFn, 153 | useDebouncedRefHistory, 154 | useDeviceMotion, 155 | useDeviceOrientation, 156 | useDevicePixelRatio, 157 | useDevicesList, 158 | useDisplayMedia, 159 | useDocumentVisibility, 160 | useDraggable, 161 | useDropZone, 162 | useElementBounding, 163 | useElementByPoint, 164 | useElementHover, 165 | useElementSize, 166 | useElementVisibility, 167 | useEventBus, 168 | useEventListener, 169 | useEventSource, 170 | useEyeDropper, 171 | useFavicon, 172 | useFetch, 173 | useFileDialog, 174 | useFileSystemAccess, 175 | useFocus, 176 | useFocusWithin, 177 | useFps, 178 | useFullscreen, 179 | useGamepad, 180 | useGeolocation, 181 | useIdle, 182 | useImage, 183 | useInfiniteScroll, 184 | useIntersectionObserver, 185 | useInterval, 186 | useIntervalFn, 187 | useKeyModifier, 188 | useLastChanged, 189 | useLocalStorage, 190 | useMagicKeys, 191 | useManualRefHistory, 192 | useMediaControls, 193 | useMediaQuery, 194 | useMemoize, 195 | useMemory, 196 | useMounted, 197 | useMouse, 198 | useMouseInElement, 199 | useMousePressed, 200 | useMutationObserver, 201 | useNavigatorLanguage, 202 | useNetwork, 203 | useNow, 204 | useObjectUrl, 205 | useOffsetPagination, 206 | useOnline, 207 | usePageLeave, 208 | useParallax, 209 | useParentElement, 210 | usePerformanceObserver, 211 | usePermission, 212 | usePointer, 213 | usePointerLock, 214 | usePointerSwipe, 215 | usePreferredColorScheme, 216 | usePreferredContrast, 217 | usePreferredDark, 218 | usePreferredLanguages, 219 | usePreferredReducedMotion, 220 | usePreferredReducedTransparency, 221 | usePrevious, 222 | useRafFn, 223 | useRefHistory, 224 | useResizeObserver, 225 | useSSRWidth, 226 | useScreenOrientation, 227 | useScreenSafeArea, 228 | useScriptTag, 229 | useScroll, 230 | useScrollLock, 231 | useSessionStorage, 232 | useShare, 233 | useSorted, 234 | useSpeechRecognition, 235 | useSpeechSynthesis, 236 | useStepper, 237 | useStorage, 238 | useStorageAsync, 239 | useStyleTag, 240 | useSupported, 241 | useSwipe, 242 | useTemplateRefsList, 243 | useTextDirection, 244 | useTextSelection, 245 | useTextareaAutosize, 246 | useThrottleFn, 247 | useThrottledRefHistory, 248 | useTimeAgo, 249 | useTimeout, 250 | useTimeoutFn, 251 | useTimeoutPoll, 252 | useTimestamp, 253 | useTitle, 254 | useToNumber, 255 | useToString, 256 | useToggle, 257 | useTransition, 258 | useUrlSearchParams, 259 | useUserMedia, 260 | useVModel, 261 | useVModels, 262 | useVibrate, 263 | useVirtualList, 264 | useWakeLock, 265 | useWebNotification, 266 | useWebSocket, 267 | useWebWorker, 268 | useWebWorkerFn, 269 | useWindowFocus, 270 | useWindowScroll, 271 | useWindowSize, 272 | watchArray, 273 | watchAtMost, 274 | watchDebounced, 275 | watchDeep, 276 | watchIgnorable, 277 | watchImmediate, 278 | watchOnce, 279 | watchPausable, 280 | watchThrottled, 281 | watchTriggerable, 282 | watchWithFilter, 283 | whenever 284 | } from "./chunk-3GYA4YLH.js"; 285 | import "./chunk-DDXJJ377.js"; 286 | export { 287 | DefaultMagicKeysAliasMap, 288 | StorageSerializers, 289 | TransitionPresets, 290 | assert, 291 | computedAsync as asyncComputed, 292 | refAutoReset as autoResetRef, 293 | breakpointsAntDesign, 294 | breakpointsBootstrapV5, 295 | breakpointsElement, 296 | breakpointsMasterCss, 297 | breakpointsPrimeFlex, 298 | breakpointsQuasar, 299 | breakpointsSematic, 300 | breakpointsTailwind, 301 | breakpointsVuetify, 302 | breakpointsVuetifyV2, 303 | breakpointsVuetifyV3, 304 | bypassFilter, 305 | camelize, 306 | clamp, 307 | cloneFnJSON, 308 | computedAsync, 309 | computedEager, 310 | computedInject, 311 | computedWithControl, 312 | containsProp, 313 | computedWithControl as controlledComputed, 314 | controlledRef, 315 | createEventHook, 316 | createFetch, 317 | createFilterWrapper, 318 | createGlobalState, 319 | createInjectionState, 320 | reactify as createReactiveFn, 321 | createRef, 322 | createReusableTemplate, 323 | createSharedComposable, 324 | createSingletonPromise, 325 | createTemplatePromise, 326 | createUnrefFn, 327 | customStorageEventName, 328 | debounceFilter, 329 | refDebounced as debouncedRef, 330 | watchDebounced as debouncedWatch, 331 | defaultDocument, 332 | defaultLocation, 333 | defaultNavigator, 334 | defaultWindow, 335 | computedEager as eagerComputed, 336 | executeTransition, 337 | extendRef, 338 | formatDate, 339 | formatTimeAgo, 340 | get, 341 | getLifeCycleTarget, 342 | getSSRHandler, 343 | hasOwn, 344 | hyphenate, 345 | identity, 346 | watchIgnorable as ignorableWatch, 347 | increaseWithUnit, 348 | injectLocal, 349 | invoke, 350 | isClient, 351 | isDef, 352 | isDefined, 353 | isIOS, 354 | isObject, 355 | isWorker, 356 | makeDestructurable, 357 | mapGamepadToXbox360Controller, 358 | noop, 359 | normalizeDate, 360 | notNullish, 361 | now, 362 | objectEntries, 363 | objectOmit, 364 | objectPick, 365 | onClickOutside, 366 | onElementRemoval, 367 | onKeyDown, 368 | onKeyPressed, 369 | onKeyStroke, 370 | onKeyUp, 371 | onLongPress, 372 | onStartTyping, 373 | pausableFilter, 374 | watchPausable as pausableWatch, 375 | promiseTimeout, 376 | provideLocal, 377 | provideSSRWidth, 378 | pxValue, 379 | rand, 380 | reactify, 381 | reactifyObject, 382 | reactiveComputed, 383 | reactiveOmit, 384 | reactivePick, 385 | refAutoReset, 386 | refDebounced, 387 | refDefault, 388 | refThrottled, 389 | refWithControl, 390 | resolveRef, 391 | resolveUnref, 392 | set, 393 | setSSRHandler, 394 | syncRef, 395 | syncRefs, 396 | templateRef, 397 | throttleFilter, 398 | refThrottled as throttledRef, 399 | watchThrottled as throttledWatch, 400 | timestamp, 401 | toArray, 402 | toReactive, 403 | toRef, 404 | toRefs, 405 | toValue, 406 | tryOnBeforeMount, 407 | tryOnBeforeUnmount, 408 | tryOnMounted, 409 | tryOnScopeDispose, 410 | tryOnUnmounted, 411 | unrefElement, 412 | until, 413 | useActiveElement, 414 | useAnimate, 415 | useArrayDifference, 416 | useArrayEvery, 417 | useArrayFilter, 418 | useArrayFind, 419 | useArrayFindIndex, 420 | useArrayFindLast, 421 | useArrayIncludes, 422 | useArrayJoin, 423 | useArrayMap, 424 | useArrayReduce, 425 | useArraySome, 426 | useArrayUnique, 427 | useAsyncQueue, 428 | useAsyncState, 429 | useBase64, 430 | useBattery, 431 | useBluetooth, 432 | useBreakpoints, 433 | useBroadcastChannel, 434 | useBrowserLocation, 435 | useCached, 436 | useClipboard, 437 | useClipboardItems, 438 | useCloned, 439 | useColorMode, 440 | useConfirmDialog, 441 | useCountdown, 442 | useCounter, 443 | useCssVar, 444 | useCurrentElement, 445 | useCycleList, 446 | useDark, 447 | useDateFormat, 448 | refDebounced as useDebounce, 449 | useDebounceFn, 450 | useDebouncedRefHistory, 451 | useDeviceMotion, 452 | useDeviceOrientation, 453 | useDevicePixelRatio, 454 | useDevicesList, 455 | useDisplayMedia, 456 | useDocumentVisibility, 457 | useDraggable, 458 | useDropZone, 459 | useElementBounding, 460 | useElementByPoint, 461 | useElementHover, 462 | useElementSize, 463 | useElementVisibility, 464 | useEventBus, 465 | useEventListener, 466 | useEventSource, 467 | useEyeDropper, 468 | useFavicon, 469 | useFetch, 470 | useFileDialog, 471 | useFileSystemAccess, 472 | useFocus, 473 | useFocusWithin, 474 | useFps, 475 | useFullscreen, 476 | useGamepad, 477 | useGeolocation, 478 | useIdle, 479 | useImage, 480 | useInfiniteScroll, 481 | useIntersectionObserver, 482 | useInterval, 483 | useIntervalFn, 484 | useKeyModifier, 485 | useLastChanged, 486 | useLocalStorage, 487 | useMagicKeys, 488 | useManualRefHistory, 489 | useMediaControls, 490 | useMediaQuery, 491 | useMemoize, 492 | useMemory, 493 | useMounted, 494 | useMouse, 495 | useMouseInElement, 496 | useMousePressed, 497 | useMutationObserver, 498 | useNavigatorLanguage, 499 | useNetwork, 500 | useNow, 501 | useObjectUrl, 502 | useOffsetPagination, 503 | useOnline, 504 | usePageLeave, 505 | useParallax, 506 | useParentElement, 507 | usePerformanceObserver, 508 | usePermission, 509 | usePointer, 510 | usePointerLock, 511 | usePointerSwipe, 512 | usePreferredColorScheme, 513 | usePreferredContrast, 514 | usePreferredDark, 515 | usePreferredLanguages, 516 | usePreferredReducedMotion, 517 | usePreferredReducedTransparency, 518 | usePrevious, 519 | useRafFn, 520 | useRefHistory, 521 | useResizeObserver, 522 | useSSRWidth, 523 | useScreenOrientation, 524 | useScreenSafeArea, 525 | useScriptTag, 526 | useScroll, 527 | useScrollLock, 528 | useSessionStorage, 529 | useShare, 530 | useSorted, 531 | useSpeechRecognition, 532 | useSpeechSynthesis, 533 | useStepper, 534 | useStorage, 535 | useStorageAsync, 536 | useStyleTag, 537 | useSupported, 538 | useSwipe, 539 | useTemplateRefsList, 540 | useTextDirection, 541 | useTextSelection, 542 | useTextareaAutosize, 543 | refThrottled as useThrottle, 544 | useThrottleFn, 545 | useThrottledRefHistory, 546 | useTimeAgo, 547 | useTimeout, 548 | useTimeoutFn, 549 | useTimeoutPoll, 550 | useTimestamp, 551 | useTitle, 552 | useToNumber, 553 | useToString, 554 | useToggle, 555 | useTransition, 556 | useUrlSearchParams, 557 | useUserMedia, 558 | useVModel, 559 | useVModels, 560 | useVibrate, 561 | useVirtualList, 562 | useWakeLock, 563 | useWebNotification, 564 | useWebSocket, 565 | useWebWorker, 566 | useWebWorkerFn, 567 | useWindowFocus, 568 | useWindowScroll, 569 | useWindowSize, 570 | watchArray, 571 | watchAtMost, 572 | watchDebounced, 573 | watchDeep, 574 | watchIgnorable, 575 | watchImmediate, 576 | watchOnce, 577 | watchPausable, 578 | watchThrottled, 579 | watchTriggerable, 580 | watchWithFilter, 581 | whenever 582 | }; 583 | //# sourceMappingURL=vitepress___@vueuse_core.js.map 584 | --------------------------------------------------------------------------------