notebooks.go

  1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
  2//
  3// SPDX-License-Identifier: AGPL-3.0-or-later
  4
  5package init
  6
  7import (
  8	"errors"
  9	"fmt"
 10
 11	"github.com/charmbracelet/huh"
 12
 13	"git.secluded.site/lune/internal/config"
 14)
 15
 16func maybeConfigureNotebooks(cfg *config.Config) error {
 17	var configure bool
 18
 19	err := huh.NewConfirm().
 20		Title("Configure notebooks?").
 21		Description("Notebooks organize your notes in Lunatask.").
 22		Affirmative("Yes").
 23		Negative("Not now").
 24		Value(&configure).
 25		Run()
 26	if err != nil {
 27		if errors.Is(err, huh.ErrUserAborted) {
 28			return nil
 29		}
 30
 31		return err
 32	}
 33
 34	if configure {
 35		return manageNotebooks(cfg)
 36	}
 37
 38	return nil
 39}
 40
 41func manageNotebooks(cfg *config.Config) error {
 42	for {
 43		options := buildNotebookOptions(cfg.Notebooks)
 44
 45		choice, err := runListSelect("Notebooks", "Manage your notebooks.", options)
 46		if err != nil {
 47			return err
 48		}
 49
 50		if choice == choiceBack {
 51			return nil
 52		}
 53
 54		if choice == choiceAdd {
 55			if err := addNotebook(cfg); err != nil {
 56				return err
 57			}
 58
 59			continue
 60		}
 61
 62		idx, ok := parseEditIndex(choice)
 63		if !ok {
 64			continue
 65		}
 66
 67		if err := manageNotebookActions(cfg, idx); err != nil {
 68			return err
 69		}
 70	}
 71}
 72
 73func buildNotebookOptions(notebooks []config.Notebook) []huh.Option[string] {
 74	options := []huh.Option[string]{
 75		huh.NewOption("Add new notebook", choiceAdd),
 76	}
 77
 78	for idx, notebook := range notebooks {
 79		label := fmt.Sprintf("%s (%s)", notebook.Name, notebook.Key)
 80		options = append(options, huh.NewOption(label, fmt.Sprintf("edit:%d", idx)))
 81	}
 82
 83	options = append(options, huh.NewOption("Back", choiceBack))
 84
 85	return options
 86}
 87
 88func addNotebook(cfg *config.Config) error {
 89	notebook, err := editNotebook(nil, cfg)
 90	if err != nil {
 91		if errors.Is(err, errUserAborted) {
 92			return nil
 93		}
 94
 95		return err
 96	}
 97
 98	cfg.Notebooks = append(cfg.Notebooks, *notebook)
 99
100	return nil
101}
102
103func manageNotebookActions(cfg *config.Config, idx int) error {
104	if idx < 0 || idx >= len(cfg.Notebooks) {
105		return fmt.Errorf("%w: notebook %d", errIndexOutRange, idx)
106	}
107
108	notebook := &cfg.Notebooks[idx]
109
110	action, err := runActionSelect(fmt.Sprintf("Notebook: %s (%s)", notebook.Name, notebook.Key), false)
111	if err != nil {
112		return err
113	}
114
115	switch action {
116	case itemActionEdit:
117		updated, err := editNotebook(notebook, cfg)
118		if err != nil && !errors.Is(err, errUserAborted) {
119			return err
120		}
121
122		if updated != nil {
123			cfg.Notebooks[idx] = *updated
124		}
125	case itemActionDelete:
126		return deleteNotebook(cfg, idx)
127	case itemActionNone, itemActionGoals:
128		// User cancelled or went back; goals not applicable here
129	}
130
131	return nil
132}
133
134func editNotebook(existing *config.Notebook, cfg *config.Config) (*config.Notebook, error) {
135	notebook := config.Notebook{}
136	if existing != nil {
137		notebook = *existing
138	}
139
140	err := runItemForm(&notebook.Name, &notebook.Key, &notebook.ID, itemFormConfig{
141		itemType:        "notebook",
142		namePlaceholder: "Gaelic Notes",
143		keyPlaceholder:  "gaelic",
144		keyValidator:    validateNotebookKey(cfg, existing),
145	})
146	if err != nil {
147		return nil, err
148	}
149
150	return &notebook, nil
151}
152
153func validateNotebookKey(cfg *config.Config, existing *config.Notebook) func(string) error {
154	return func(input string) error {
155		if err := validateKeyFormat(input); err != nil {
156			return err
157		}
158
159		for idx := range cfg.Notebooks {
160			if existing != nil && &cfg.Notebooks[idx] == existing {
161				continue
162			}
163
164			if cfg.Notebooks[idx].Key == input {
165				return errKeyDuplicate
166			}
167		}
168
169		return nil
170	}
171}
172
173func deleteNotebook(cfg *config.Config, idx int) error {
174	if idx < 0 || idx >= len(cfg.Notebooks) {
175		return fmt.Errorf("%w: notebook %d", errIndexOutRange, idx)
176	}
177
178	notebook := cfg.Notebooks[idx]
179
180	var confirm bool
181
182	err := huh.NewConfirm().
183		Title(fmt.Sprintf("Delete notebook '%s'?", notebook.Name)).
184		Description("This cannot be undone.").
185		Affirmative("Delete").
186		Negative("Cancel").
187		Value(&confirm).
188		Run()
189	if err != nil {
190		if errors.Is(err, huh.ErrUserAborted) {
191			return nil
192		}
193
194		return err
195	}
196
197	if confirm {
198		cfg.Notebooks = append(cfg.Notebooks[:idx], cfg.Notebooks[idx+1:]...)
199		if cfg.Defaults.Notebook == notebook.Key {
200			cfg.Defaults.Notebook = ""
201		}
202	}
203
204	return nil
205}