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 %s\r\n", urls[i], names[i]) } // 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 }