amfora/display/bookmarks.go

195 lines
5.7 KiB
Go

package display
import (
"fmt"
"regexp"
"strings"
"code.rocketnine.space/tslocum/cview"
"github.com/gdamore/tcell/v2"
"github.com/makeworld-the-better-one/amfora/bookmarks"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs"
"github.com/spf13/viper"
)
// For adding and removing bookmarks, basically a clone of the input modal.
var bkmkModal = cview.NewModal()
type bkmkAction int
const (
add bkmkAction = iota
change
cancel
remove
)
// bkmkCh is for the user action
var bkmkCh = make(chan bkmkAction)
var bkmkModalText string // The current text of the input field in the modal
// Regex for extracting top level 1 heading. The title will extracted from the 1st submatch.
var topHeadingRegex = regexp.MustCompile(`(?m)^#[^#][\t ]*[^\s].*$`)
func bkmkInit() {
panels.AddPanel(PanelBookmarks, bkmkModal, false, false)
m := bkmkModal
if viper.GetBool("a-general.color") {
m.SetBackgroundColor(config.GetColor("bkmk_modal_bg"))
m.SetButtonBackgroundColor(config.GetColor("btn_bg"))
m.SetButtonTextColor(config.GetColor("btn_text"))
m.SetTextColor(config.GetColor("bkmk_modal_text"))
form := m.GetForm()
form.SetLabelColor(config.GetColor("bkmk_modal_label"))
form.SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg"))
form.SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
form.SetFieldBackgroundColorFocused(config.GetColor("bkmk_modal_field_text"))
form.SetFieldTextColorFocused(config.GetTextColor("bkmk_modal_field_bg", "bkmk_modal_field_text"))
form.SetButtonBackgroundColorFocused(config.GetColor("btn_text"))
form.SetButtonTextColorFocused(config.GetTextColor("btn_bg", "btn_text"))
frame := m.GetFrame()
frame.SetBorderColor(config.GetColor("bkmk_modal_text"))
frame.SetTitleColor(config.GetColor("bkmk_modal_text"))
} else {
m.SetBackgroundColor(tcell.ColorBlack)
m.SetButtonBackgroundColor(tcell.ColorWhite)
m.SetButtonTextColor(tcell.ColorBlack)
m.SetTextColor(tcell.ColorWhite)
form := m.GetForm()
form.SetLabelColor(tcell.ColorWhite)
form.SetFieldBackgroundColor(tcell.ColorWhite)
form.SetFieldTextColor(tcell.ColorBlack)
form.SetButtonBackgroundColorFocused(tcell.ColorBlack)
form.SetButtonTextColorFocused(tcell.ColorWhite)
frame := m.GetFrame()
frame.SetBorderColor(tcell.ColorWhite)
frame.SetTitleColor(tcell.ColorWhite)
}
m.SetBorder(true)
frame := m.GetFrame()
frame.SetTitleAlign(cview.AlignCenter)
frame.SetTitle(" Add Bookmark ")
m.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
switch buttonLabel {
case "Add":
bkmkCh <- add
case "Change":
bkmkCh <- change
case "Remove":
bkmkCh <- remove
case "Cancel":
bkmkCh <- cancel
case "":
bkmkCh <- cancel
}
})
}
// Bkmk displays the "Add a bookmark" modal.
// It accepts the default value for the bookmark name that will be displayed, but can be changed by the user.
// It also accepts a bool indicating whether this page already has a bookmark.
// It returns the bookmark name and the bookmark action.
func openBkmkModal(name string, exists bool) (string, bkmkAction) {
// Basically a copy of Input()
// Reset buttons before input field, to make sure the input is in focus
bkmkModal.ClearButtons()
if exists {
bkmkModal.SetText("Change or remove the bookmark for the current page?")
bkmkModal.AddButtons([]string{"Change", "Remove", "Cancel"})
} else {
bkmkModal.SetText("Create a bookmark for the current page?")
bkmkModal.AddButtons([]string{"Add", "Cancel"})
}
// Remove and re-add input field - to clear the old text
bkmkModal.GetForm().Clear(false)
bkmkModalText = name
bkmkModal.GetForm().AddInputField("Name: ", name, 0, nil,
func(text string) {
// Store for use later
bkmkModalText = text
})
panels.ShowPanel(PanelBookmarks)
panels.SendToFront(PanelBookmarks)
App.SetFocus(bkmkModal)
App.Draw()
action := <-bkmkCh
panels.HidePanel(PanelBookmarks)
App.SetFocus(tabs[curTab].view)
App.Draw()
return bkmkModalText, action
}
// Bookmarks displays the bookmarks page on the current tab.
func Bookmarks(t *tab) {
bkmkPageRaw := "# Bookmarks\r\n\r\n"
// Gather bookmarks
names, urls := bookmarks.All()
for i := range names {
bkmkPageRaw += fmt.Sprintf("=> %s\r\n", urls[i])
if names[i] != "" {
bkmkPageRaw += fmt.Sprintf("%s\r\n", names[i])
}
bkmkPageRaw += fmt.Sprintf("\r\n")
}
// Render and display
content, links := renderer.RenderGemini(bkmkPageRaw, textWidth(), false)
page := structs.Page{
Raw: bkmkPageRaw,
Content: content,
Links: links,
URL: "about:bookmarks",
TermWidth: termW,
Mediatype: structs.TextGemini,
}
setPage(t, &page)
t.applyBottomBar()
}
// addBookmark goes through the process of adding a bookmark for the current page.
// It is the high-level way of doing it. It should be called in a goroutine.
// It can also be called to edit an existing bookmark.
func addBookmark() {
t := tabs[curTab]
p := t.page
if !t.hasContent() || t.isAnAboutPage() {
// It's an about: page, or a malformed one
return
}
name, exists := bookmarks.Get(p.URL)
// Retrieve & use top level 1 heading for name if bookmark does not already exist.
if !exists {
match := topHeadingRegex.FindString(p.Raw)
if match != "" {
name = strings.TrimSpace(match[1:])
}
}
// Open a bookmark modal with the current name of the bookmark, if it exists
// otherwise use the top level 1 heading as a suggested name
newName, action := openBkmkModal(name, exists)
//nolint:exhaustive
switch action {
case add:
bookmarks.Add(p.URL, newName)
case change:
bookmarks.Change(p.URL, newName)
case remove:
bookmarks.Remove(p.URL)
}
// Other case is action == cancel, so nothing needs to happen
}