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