Themeing support

This commit is contained in:
makeworld 2020-07-28 16:58:32 -04:00
parent fdd8a6e59b
commit 56a56896c9
18 changed files with 503 additions and 139 deletions

View File

@ -6,18 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- **Themeing** - check out [default-config.toml](./default-config.toml) for details (#46)
- <kbd>Tab</kbd> now also enters link selecting mode, like <kbd>Enter</kbd> (#48)
- Number keys can be pressed to navigate to links 1 through 10 (#47)
- Permanent redirects are cached for the session (#22)
- `.ansi` is also supported for `text/x-ansi` files, as well as the already supported `.ans`
### Changed
- Documented <kbd>Ctrl-C</kbd> has "Hard quit"
- Updated [cview](https://gitlab.com/tslocum/cview/) to latest commit: `cc7796c4ca44e3908f80d93e92e73694562d936a`
- The bottom bar label now uses the same color as the tabs at the top
- Tab and blue link colors were changed very slightly to be part of the 256 Xterm colors, for better terminal support
### Fixed
- You can't change link selection while the page is loading
- Only one request is made for each URL - `v1.3.0` accidentally made two requests each time (#50)
- Using the `..` command doesn't keep the query string (#49)
- Any error that occurs when downloading a file will be displayed, and the partially download file will be deleted
- Any error that occurs when downloading a file will be displayed, and the partially downloaded file will be deleted
## [1.3.0] - 2020-07-10
@ -34,7 +39,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Pages are rewrapped dynamically, whenever the terminal size changes (#33)
- TOFU warning message mentions how long the previous cert was still valid for (#34)
- Update [cview](https://gitlab.com/tslocum/cview/) to latest commit
### Fixed
- Many potential network and display race conditions eliminated

View File

@ -3,6 +3,9 @@
## Issues
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
- Can't go back or do other things while page is loading - need a way to stop `handleURL`
- Allow for opening a new tab while the current one is loading
- Can't leave the help window
- Can't interact with page after clicking Cancel on bkmk (and other?) modal(s)
## Upstream Bugs
- Wrapping messes up on brackets

View File

@ -73,6 +73,7 @@ Features in *italics* are in the master branch, but not in the latest release.
- [x] Built-in search (uses GUS by default)
- [x] Bookmarks
- [x] Download pages and arbitrary data
- [x] *Themeing*
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
- [ ] Emoji favicons
- See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details

18
cache/redir.go vendored
View File

@ -7,12 +7,12 @@ import (
// Functions for caching redirects.
var redirUrls = make(map[string]string) // map original URL to redirect
var redirMut = sync.RWMutex{}
var redirMu = sync.RWMutex{}
// AddRedir adds a original-to-redirect pair to the cache.
func AddRedir(og, redir string) {
redirMut.Lock()
defer redirMut.Unlock()
redirMu.Lock()
defer redirMu.Unlock()
for k, v := range redirUrls {
if og == v {
@ -32,8 +32,8 @@ func AddRedir(og, redir string) {
// ClearRedirs removes all redirects from the cache.
func ClearRedirs() {
redirMut.Lock()
defer redirMut.Unlock()
redirMu.Lock()
defer redirMu.Unlock()
redirUrls = make(map[string]string)
}
@ -41,8 +41,8 @@ func ClearRedirs() {
// exists for that URL in the cache.
// If one does not then the original URL is returned.
func Redirect(u string) string {
redirMut.RLock()
defer redirMut.RUnlock()
redirMu.RLock()
defer redirMu.RUnlock()
// A single lookup is enough, because AddRedir
// removes loops and chains.
@ -54,7 +54,7 @@ func Redirect(u string) string {
}
func NumRedirs() int {
redirMut.RLock()
defer redirMut.RUnlock()
redirMu.RLock()
defer redirMu.RUnlock()
return len(redirUrls)
}

View File

@ -7,9 +7,11 @@ import (
"runtime"
"strings"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/cache"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"gitlab.com/tslocum/cview"
)
var amforaAppData string // Where amfora files are stored on Windows - cached here
@ -196,5 +198,24 @@ func Init() error {
cache.SetMaxSize(viper.GetInt("cache.max_size"))
cache.SetMaxPages(viper.GetInt("cache.max_pages"))
// Theme
configTheme := viper.Sub("theme")
if configTheme != nil {
for k, v := range configTheme.AllSettings() {
colorStr, ok := v.(string)
if !ok {
return fmt.Errorf(`value for "%s" is not a string: %v`, k, v)
}
color := tcell.GetColor(strings.ToLower(colorStr))
if color == tcell.ColorDefault {
return fmt.Errorf(`invalid color format for "%s": %s`, k, colorStr)
}
SetColor(k, color)
}
}
if viper.GetBool("a-general.color") {
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
} // Otherwise it's black by default
return nil
}

View File

@ -1,5 +1,6 @@
package config
//go:generate ./default.sh
var defaultConf = []byte(`# This is the default config file.
# It also shows all the default values, if you don't create the file.
@ -39,4 +40,73 @@ page_max_time = 10
# Zero values mean there is no limit
max_size = 0 # Size in bytes
max_pages = 30 # The maximum number of pages the cache will store
`)
[theme]
# This section is for changing the COLORS used in Amfora.
# These colors only apply if color is enabled above.
# Colors can be set using a W3C color name, or a hex value such as #ffffff".
# Note that not all colors will work on terminals that do not have truecolor support.
# If you want to stick to the standard 16 or 256 colors, you can get
# a list of those here: https://jonasjacek.github.io/colors/
# Do NOT use the names from that site, just the hex codes.
# Definitions:
# bg = background
# fg = foreground
# dl = download
# btn = button
# hdg = heading
# bkmk = bookmark
# modal = a popup window/box in the middle of the screen
# EXAMPLES:
# hdg_1 = "green"
# hdg_2 = "#5f0000"
# Available keys to set:
# bg: background for pages, tab row, app in general
# tab_num: The number/highlight of the tabs at the top
# tab_divider: The color of the divider character between tab numbers: |
# bottombar_label: The color of the prompt that appears when you press space
# bottombar_text: The color of the text you type
# bottombar_bg
# hdg_1
# hdg_2
# hdg_3
# amfora_link: A link that Amfora supports viewing. For now this is only gemini://
# foreign_link: HTTP(S), Gopher, etc
# link_number: The silver number that appears to the left of a link
# regular_text: Normal gemini text, and plaintext documents
# quote_text
# preformatted_text
# list_text
# btn_bg: The bg color for all modal buttons
# btn_text: The text color for all modal buttons
# dl_choice_modal_bg
# dl_choice_modal_text
# dl_modal_bg
# dl_modal_text
# info_modal_bg
# info_modal_text
# error_modal_bg
# error_modal_text
# yesno_modal_bg
# yesno_modal_text
# tofu_modal_bg
# tofu_modal_text
# input_modal_bg
# input_modal_text
# input_modal_field_bg: The bg of the input field, where you type the text
# input_modal_field_text: The color of the text you type
# bkmk_modal_bg
# bkmk_modal_text
# bkmk_modal_label
# bkmk_modal_field_bg
# bkmk_modal_field_text`)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env bash
head -n 1 default.go | tee default.go > /dev/null
head -n 3 default.go | tee default.go > /dev/null
echo -n 'var defaultConf = []byte(`' >> default.go
cat ../default-config.toml >> default.go
echo '`)' >> default.go

85
config/theme.go Normal file
View File

@ -0,0 +1,85 @@
package config
import (
"fmt"
"sync"
"github.com/gdamore/tcell"
)
// Functions to allow themeing configuration.
// UI element colors are mapped to a string key, such as "error" or "tab_background"
// These are the same keys used in the config file.
var themeMu = sync.RWMutex{}
var theme = map[string]tcell.Color{
// Default values below
"bg": tcell.ColorBlack, // Used for cview.Styles.PrimitiveBackgroundColor
"tab_num": tcell.Color30, // xterm:Turquoise4, #008787
"tab_divider": tcell.ColorWhite,
"bottombar_label": tcell.Color30,
"bottombar_text": tcell.ColorBlack,
"bottombar_bg": tcell.ColorWhite,
// Modals
"btn_bg": tcell.ColorNavy, // All modal buttons
"btn_text": tcell.ColorWhite,
"dl_choice_modal_bg": tcell.ColorPurple,
"dl_choice_modal_text": tcell.ColorWhite,
"dl_modal_bg": tcell.Color130, // xterm:DarkOrange3, #af5f00
"dl_modal_text": tcell.ColorWhite,
"info_modal_bg": tcell.ColorGray,
"info_modal_text": tcell.ColorWhite,
"error_modal_bg": tcell.ColorMaroon,
"error_modal_text": tcell.ColorWhite,
"yesno_modal_bg": tcell.ColorPurple,
"yesno_modal_text": tcell.ColorWhite,
"tofu_modal_bg": tcell.ColorMaroon,
"tofu_modal_text": tcell.ColorWhite,
"input_modal_bg": tcell.ColorGreen,
"input_modal_text": tcell.ColorWhite,
"input_modal_field_bg": tcell.ColorBlue,
"input_modal_field_text": tcell.ColorWhite,
"bkmk_modal_bg": tcell.ColorTeal,
"bkmk_modal_text": tcell.ColorWhite,
"bkmk_modal_label": tcell.ColorYellow,
"bkmk_modal_field_bg": tcell.ColorBlue,
"bkmk_modal_field_text": tcell.ColorWhite,
"hdg_1": tcell.ColorRed,
"hdg_2": tcell.ColorLime,
"hdg_3": tcell.ColorFuchsia,
"amfora_link": tcell.Color33, // xterm:DodgerBlue1, #0087ff
"foreign_link": tcell.Color92, // xterm:DarkViolet, #8700d7
"link_number": tcell.ColorSilver,
"regular_text": tcell.ColorWhite,
"quote_text": tcell.ColorWhite,
"preformatted_text": tcell.ColorWhite,
"list_text": tcell.ColorWhite,
}
func SetColor(key string, color tcell.Color) {
themeMu.Lock()
defer themeMu.Unlock()
theme[key] = color
}
// GetColor will return tcell.ColorBlack if there is no color for the provided key.
func GetColor(key string) tcell.Color {
themeMu.RLock()
defer themeMu.RUnlock()
return theme[key]
}
// GetColorString returns a string that can be used in a cview color tag,
// for the given theme key.
// It will return "#000000" if there is no color for the provided key.
func GetColorString(key string) string {
themeMu.RLock()
defer themeMu.RUnlock()
return fmt.Sprintf("#%06x", theme[key].Hex())
}

View File

@ -37,3 +37,73 @@ page_max_time = 10
# Zero values mean there is no limit
max_size = 0 # Size in bytes
max_pages = 30 # The maximum number of pages the cache will store
[theme]
# This section is for changing the COLORS used in Amfora.
# These colors only apply if color is enabled above.
# Colors can be set using a W3C color name, or a hex value such as #ffffff".
# Note that not all colors will work on terminals that do not have truecolor support.
# If you want to stick to the standard 16 or 256 colors, you can get
# a list of those here: https://jonasjacek.github.io/colors/
# Do NOT use the names from that site, just the hex codes.
# Definitions:
# bg = background
# fg = foreground
# dl = download
# btn = button
# hdg = heading
# bkmk = bookmark
# modal = a popup window/box in the middle of the screen
# EXAMPLES:
# hdg_1 = "green"
# hdg_2 = "#5f0000"
# Available keys to set:
# bg: background for pages, tab row, app in general
# tab_num: The number/highlight of the tabs at the top
# tab_divider: The color of the divider character between tab numbers: |
# bottombar_label: The color of the prompt that appears when you press space
# bottombar_text: The color of the text you type
# bottombar_bg
# hdg_1
# hdg_2
# hdg_3
# amfora_link: A link that Amfora supports viewing. For now this is only gemini://
# foreign_link: HTTP(S), Gopher, etc
# link_number: The silver number that appears to the left of a link
# regular_text: Normal gemini text, and plaintext documents
# quote_text
# preformatted_text
# list_text
# btn_bg: The bg color for all modal buttons
# btn_text: The text color for all modal buttons
# dl_choice_modal_bg
# dl_choice_modal_text
# dl_modal_bg
# dl_modal_text
# info_modal_bg
# info_modal_text
# error_modal_bg
# error_modal_text
# yesno_modal_bg
# yesno_modal_text
# tofu_modal_bg
# tofu_modal_text
# input_modal_bg
# input_modal_text
# input_modal_field_bg: The bg of the input field, where you type the text
# input_modal_field_text: The color of the text you type
# bkmk_modal_bg
# bkmk_modal_text
# bkmk_modal_label
# bkmk_modal_field_bg
# bkmk_modal_field_text

View File

@ -7,6 +7,7 @@ import (
"github.com/gdamore/tcell"
"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"
@ -14,8 +15,7 @@ import (
)
// For adding and removing bookmarks, basically a clone of the input modal.
var bkmkModal = cview.NewModal().
SetTextColor(tcell.ColorWhite)
var bkmkModal = cview.NewModal()
// bkmkCh is for the user action
var bkmkCh = make(chan int) // 1, 0, -1 for add/update, cancel, and remove
@ -23,21 +23,35 @@ var bkmkModalText string // The current text of the input field in the modal
func bkmkInit() {
if viper.GetBool("a-general.color") {
bkmkModal.SetBackgroundColor(tcell.ColorTeal).
SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite)
bkmkModal.SetBackgroundColor(config.GetColor("bkmk_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("bkmk_modal_text"))
bkmkModal.GetForm().
SetLabelColor(config.GetColor("bkmk_modal_label")).
SetFieldBackgroundColor(config.GetColor("bkmk_modal_field_bg")).
SetFieldTextColor(config.GetColor("bkmk_modal_field_text"))
bkmkModal.GetFrame().
SetBorderColor(config.GetColor("bkmk_modal_text")).
SetTitleColor(config.GetColor("bkmk_modal_text"))
} else {
bkmkModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack)
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
bkmkModal.GetForm().
SetLabelColor(tcell.ColorWhite).
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
bkmkModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
}
bkmkModal.SetBorder(true)
bkmkModal.SetBorderColor(tcell.ColorWhite)
bkmkModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Add Bookmark ")
bkmkModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
switch buttonLabel {
case "Add":
@ -52,9 +66,6 @@ func bkmkInit() {
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - handled in bkmk()
})
bkmkModal.GetFrame().SetTitleColor(tcell.ColorWhite)
bkmkModal.GetFrame().SetTitleAlign(cview.AlignCenter)
bkmkModal.GetFrame().SetTitle(" Add Bookmark ")
}
// Bkmk displays the "Add a bookmark" modal.

View File

@ -9,6 +9,7 @@ import (
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/cache"
"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"
@ -23,9 +24,7 @@ var termW int
var termH int
// The user input and URL display bar at the bottom
var bottomBar = cview.NewInputField().
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
var bottomBar = cview.NewInputField()
// Viewer for the tab primitives
// Pages are named as strings of tab numbers - so the textview for the first tab
@ -50,18 +49,7 @@ var tabRow = cview.NewTextView().
// Root layout
var layout = cview.NewFlex().
SetDirection(cview.FlexRow).
AddItem(tabRow, 1, 1, false).
AddItem(nil, 1, 1, false). // One line of empty space above the page
AddItem(tabPages, 0, 1, true).
// AddItem(cview.NewFlex(). // The page text in the middle is held in another flex, to center it
// SetDirection(cview.FlexColumn).
// AddItem(nil, 0, 1, false).
// AddItem(tabPages, 0, 7, true). // The text occupies 7/9 of the screen horizontally
// AddItem(nil, 0, 1, false),
// 0, 1, true).
AddItem(nil, 1, 1, false). // One line of empty space before bottomBar
AddItem(bottomBar, 1, 1, false)
SetDirection(cview.FlexRow)
var renderedNewTabContent string
var newTabLinks []string
@ -77,8 +65,8 @@ var App = cview.NewApplication().
// Make sure the current tab content is reformatted when the terminal size changes
go func(t *tab) {
t.reformatMut.Lock() // Only one reformat job per tab
defer t.reformatMut.Unlock()
t.reformatMu.Lock() // Only one reformat job per tab
defer t.reformatMu.Unlock()
// Use the current tab, but don't affect other tabs if the user switches tabs
reformatPageAndSetView(t, t.page)
}(tabs[curTab])
@ -91,12 +79,29 @@ func Init() {
helpInit()
layout.
AddItem(tabRow, 1, 1, false).
AddItem(nil, 1, 1, false). // One line of empty space above the page
AddItem(tabPages, 0, 1, true).
AddItem(nil, 1, 1, false). // One line of empty space before bottomBar
AddItem(bottomBar, 1, 1, false)
if viper.GetBool("a-general.color") {
bottomBar.SetLabelColor(tcell.ColorGreen)
layout.SetBackgroundColor(config.GetColor("bg"))
tabRow.SetBackgroundColor(config.GetColor("bg"))
bottomBar.SetBackgroundColor(config.GetColor("bottombar_bg"))
bottomBar.
SetLabelColor(config.GetColor("bottombar_label")).
SetFieldBackgroundColor(config.GetColor("bottombar_bg")).
SetFieldTextColor(config.GetColor("bottombar_text"))
} else {
bottomBar.SetLabelColor(tcell.ColorBlack)
bottomBar.SetBackgroundColor(tcell.ColorWhite)
bottomBar.
SetLabelColor(tcell.ColorBlack).
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
}
bottomBar.SetBackgroundColor(tcell.ColorWhite)
bottomBar.SetDoneFunc(func(key tcell.Key) {
tab := curTab
@ -432,7 +437,12 @@ func NewTab() {
// Add tab number to the actual place where tabs are show on the screen
// Tab regions are 0-indexed but text displayed on the screen starts at 1
if viper.GetBool("a-general.color") {
fmt.Fprintf(tabRow, `["%d"][darkcyan] %d [white][""]|`, curTab, curTab+1)
fmt.Fprintf(tabRow, `["%d"][%s] %d [%s][""]|`,
curTab,
config.GetColorString("tab_num"),
curTab+1,
config.GetColorString("tab_divider"),
)
} else {
fmt.Fprintf(tabRow, `["%d"] %d [""]|`, curTab, curTab+1)
}
@ -477,7 +487,12 @@ func CloseTab() {
tabRow.Clear()
if viper.GetBool("a-general.color") {
for i := 0; i < NumTabs(); i++ {
fmt.Fprintf(tabRow, `["%d"][darkcyan] %d [white][""]|`, i, i+1)
fmt.Fprintf(tabRow, `["%d"][%s] %d [%s][""]|`,
i,
config.GetColorString("tab_num"),
i+1,
config.GetColorString("tab_divider"),
)
}
} else {
for i := 0; i < NumTabs(); i++ {

View File

@ -23,50 +23,62 @@ import (
// For choosing between download and the portal - copy of YesNo basically
var dlChoiceModal = cview.NewModal().
SetTextColor(tcell.ColorWhite).
AddButtons([]string{"Download", "Open in portal", "Cancel"})
// Channel to indicate what choice they made using the button text
var dlChoiceCh = make(chan string)
var dlModal = cview.NewModal().
SetTextColor(tcell.ColorWhite)
var dlModal = cview.NewModal()
func dlInit() {
if viper.GetBool("a-general.color") {
dlChoiceModal.SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite).
SetBackgroundColor(tcell.ColorPurple)
dlModal.SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite).
SetBackgroundColor(tcell.Color130) // DarkOrange3, #af5f00
dlChoiceModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetBackgroundColor(config.GetColor("dl_choice_modal_bg")).
SetTextColor(config.GetColor("dl_choice_modal_text"))
dlChoiceModal.GetFrame().
SetBorderColor(config.GetColor("dl_choice_modal_text")).
SetTitleColor(config.GetColor("dl_choice_modal_text"))
dlModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetBackgroundColor(config.GetColor("dl_modal_bg")).
SetTextColor(config.GetColor("dl_modal_text"))
dlModal.GetFrame().
SetBorderColor(config.GetColor("dl_modal_text")).
SetTitleColor(config.GetColor("dl_modal_text"))
} else {
dlChoiceModal.SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorBlack)
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
dlChoiceModal.SetBorderColor(tcell.ColorWhite)
dlChoiceModal.GetFrame().SetTitleColor(tcell.ColorWhite)
dlModal.SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorBlack)
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
dlModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
}
dlChoiceModal.SetBorder(true)
dlChoiceModal.SetBorderColor(tcell.ColorWhite)
dlChoiceModal.GetFrame().SetTitleAlign(cview.AlignCenter)
dlChoiceModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
dlChoiceCh <- buttonLabel
})
dlChoiceModal.GetFrame().SetTitleColor(tcell.ColorWhite)
dlChoiceModal.GetFrame().SetTitleAlign(cview.AlignCenter)
dlModal.SetBorder(true)
dlModal.SetBorderColor(tcell.ColorWhite)
dlModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Download ")
dlModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Ok" {
tabPages.SwitchToPage(strconv.Itoa(curTab))
}
})
dlModal.GetFrame().SetTitleColor(tcell.ColorWhite)
dlModal.GetFrame().SetTitleAlign(cview.AlignCenter)
dlModal.GetFrame().SetTitle(" Download ")
}
// dlChoice displays the download choice modal and acts on the user's choice.

View File

@ -9,7 +9,7 @@ import (
)
var helpCells = strings.TrimSpace(`
?|Bring up this help.
?|Bring up this help. You can scroll!
Esc|Leave the help
Arrow keys, h/j/k/l|Scroll and move a page.
Tab|Navigate to the next item in a popup.
@ -22,6 +22,7 @@ spacebar|Open bar at the bottom - type a URL, link number, search term.
|You can also type two dots (..) to go up a directory in the URL.
|Typing new:N will open link number N in a new tab
|instead of the current one.
Numbers|Go to links 1-10 respectively.
Enter, Tab|On a page this will start link highlighting.
|Press Tab and Shift-Tab to pick different links.
|Press Enter again to go to one, or Esc to stop.
@ -37,13 +38,14 @@ Ctrl-B|View bookmarks
Ctrl-D|Add, change, or remove a bookmark for the current page.
Ctrl-S|Save the current page to your downloads.
q, Ctrl-Q|Quit
Ctrl-C|Hard quit. This can be used when in the middle of downloading, for example.
Ctrl-C|Hard quit. This can be used when in the middle of downloading,
|for example.
`)
var helpTable = cview.NewTable().
SetSelectable(false, false).
SetBorders(false).
SetBordersColor(tcell.ColorGray)
SetScrollBarVisibility(cview.ScrollBarNever)
// Help displays the help and keybindings.
func Help() {

View File

@ -8,6 +8,7 @@ import (
"github.com/dustin/go-humanize"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/spf13/viper"
"gitlab.com/tslocum/cview"
)
@ -16,22 +17,16 @@ import (
// The bookmark modal is in bookmarks.go
var infoModal = cview.NewModal().
SetTextColor(tcell.ColorWhite).
AddButtons([]string{"Ok"})
var errorModal = cview.NewModal().
SetTextColor(tcell.ColorWhite).
AddButtons([]string{"Ok"})
var inputModal = cview.NewModal().
SetTextColor(tcell.ColorWhite)
//AddButtons([]string{"Send", "Cancel"}) - Added in func
var inputModal = cview.NewModal()
var inputCh = make(chan string)
var inputModalText string // The current text of the input field in the modal
var yesNoModal = cview.NewModal().
SetTextColor(tcell.ColorWhite).
AddButtons([]string{"Yes", "No"})
// Channel to receive yesNo answer on
@ -48,29 +43,60 @@ func modalInit() {
// Color setup
if viper.GetBool("a-general.color") {
infoModal.SetBackgroundColor(tcell.ColorGray).
SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite)
errorModal.SetBackgroundColor(tcell.ColorMaroon).
SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite)
inputModal.SetBackgroundColor(tcell.ColorGreen).
SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite)
yesNoModal.SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite)
infoModal.SetBackgroundColor(config.GetColor("info_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("info_modal_text"))
infoModal.GetFrame().
SetBorderColor(config.GetColor("info_modal_text")).
SetTitleColor(config.GetColor("info_modal_text"))
errorModal.SetBackgroundColor(config.GetColor("error_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("error_modal_text"))
errorModal.GetFrame().
SetBorderColor(config.GetColor("error_modal_text")).
SetTitleColor(config.GetColor("error_modal_text"))
inputModal.SetBackgroundColor(config.GetColor("input_modal_bg")).
SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text")).
SetTextColor(config.GetColor("input_modal_text"))
inputModal.GetFrame().
SetBorderColor(config.GetColor("input_modal_text")).
SetTitleColor(config.GetColor("input_modal_text"))
inputModal.GetForm().
SetFieldBackgroundColor(config.GetColor("input_modal_field_bg")).
SetFieldTextColor(config.GetColor("input_modal_field_text"))
yesNoModal.SetButtonBackgroundColor(config.GetColor("btn_bg")).
SetButtonTextColor(config.GetColor("btn_text"))
} else {
infoModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack)
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
infoModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
errorModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack)
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
errorModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
inputModal.SetBackgroundColor(tcell.ColorBlack).
SetButtonBackgroundColor(tcell.ColorWhite).
SetButtonTextColor(tcell.ColorBlack)
SetButtonTextColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
inputModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
inputModal.GetForm().
SetLabelColor(tcell.ColorWhite).
SetFieldBackgroundColor(tcell.ColorWhite).
SetFieldTextColor(tcell.ColorBlack)
@ -82,24 +108,23 @@ func modalInit() {
// Modal functions that can't be added up above, because they return the wrong type
infoModal.SetBorder(true)
infoModal.SetBorderColor(tcell.ColorWhite)
infoModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Info ")
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
})
infoModal.GetFrame().SetTitleColor(tcell.ColorWhite)
infoModal.GetFrame().SetTitleAlign(cview.AlignCenter)
infoModal.GetFrame().SetTitle(" Info ")
errorModal.SetBorder(true)
errorModal.SetBorderColor(tcell.ColorWhite)
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
})
errorModal.GetFrame().SetTitleColor(tcell.ColorWhite)
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
inputModal.SetBorder(true)
inputModal.SetBorderColor(tcell.ColorWhite)
inputModal.GetFrame().
SetTitleAlign(cview.AlignCenter).
SetTitle(" Input ")
inputModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Send" {
inputCh <- inputModalText
@ -107,15 +132,10 @@ func modalInit() {
}
// Empty string indicates no input
inputCh <- ""
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - handled in Input()
})
inputModal.GetFrame().SetTitleColor(tcell.ColorWhite)
inputModal.GetFrame().SetTitleAlign(cview.AlignCenter)
inputModal.GetFrame().SetTitle(" Input ")
yesNoModal.SetBorder(true)
yesNoModal.SetBorderColor(tcell.ColorWhite)
yesNoModal.GetFrame().SetTitleAlign(cview.AlignCenter)
yesNoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" {
yesNoCh <- true
@ -125,8 +145,6 @@ func modalInit() {
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - Handled in YesNo()
})
yesNoModal.GetFrame().SetTitleColor(tcell.ColorWhite)
yesNoModal.GetFrame().SetTitleAlign(cview.AlignCenter)
bkmkInit()
dlInit()
@ -174,7 +192,7 @@ func Input(prompt string) (string, bool) {
inputModalText = text
})
inputModal.SetText(prompt)
inputModal.SetText(prompt + " ")
tabPages.ShowPage("input")
tabPages.SendToFront("input")
App.SetFocus(inputModal)
@ -191,9 +209,19 @@ func Input(prompt string) (string, bool) {
// YesNo displays a modal asking a yes-or-no question.
func YesNo(prompt string) bool {
if viper.GetBool("a-general.color") {
yesNoModal.SetBackgroundColor(tcell.ColorPurple)
yesNoModal.
SetBackgroundColor(config.GetColor("yesno_modal_bg")).
SetTextColor(config.GetColor("yesno_modal_text"))
yesNoModal.GetFrame().
SetBorderColor(config.GetColor("yesno_modal_text")).
SetTitleColor(config.GetColor("yesno_modal_text"))
} else {
yesNoModal.SetBackgroundColor(tcell.ColorBlack)
yesNoModal.
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
yesNoModal.GetFrame().
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
}
yesNoModal.GetFrame().SetTitle("")
yesNoModal.SetText(prompt)
@ -210,12 +238,22 @@ func YesNo(prompt string) bool {
// Tofu displays the TOFU warning modal.
// It returns a bool indicating whether the user wants to continue.
func Tofu(host string, expiry time.Time) bool {
// Reuses yesNoModal, with error colour
// Reuses yesNoModal, with error color
if viper.GetBool("a-general.color") {
yesNoModal.SetBackgroundColor(tcell.ColorMaroon)
yesNoModal.
SetBackgroundColor(config.GetColor("tofu_modal_bg")).
SetTextColor(config.GetColor("tofu_modal_text"))
yesNoModal.GetFrame().
SetBorderColor(config.GetColor("tofu_modal_text")).
SetTitleColor(config.GetColor("tofu_modal_text"))
} else {
yesNoModal.SetBackgroundColor(tcell.ColorBlack)
yesNoModal.
SetBackgroundColor(tcell.ColorBlack).
SetTextColor(tcell.ColorWhite)
yesNoModal.
SetBorderColor(tcell.ColorWhite).
SetTitleColor(tcell.ColorWhite)
}
yesNoModal.GetFrame().SetTitle(" TOFU ")
yesNoModal.SetText(

View File

@ -24,13 +24,13 @@ type tabHistory struct {
// tab hold the information needed for each browser tab.
type tab struct {
page *structs.Page
view *cview.TextView
history *tabHistory
mode tabMode
reformatMut *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once
barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab
page *structs.Page
view *cview.TextView
history *tabHistory
mode tabMode
reformatMu *sync.Mutex // Mutex for reformatting, so there's only one reformat job at once
barLabel string // The bottomBar label for the tab
barText string // The bottomBar text for the tab
}
// makeNewTab initializes an tab struct with no content.
@ -45,9 +45,9 @@ func makeNewTab() *tab {
SetChangedFunc(func() {
App.Draw()
}),
history: &tabHistory{},
reformatMut: &sync.Mutex{},
mode: tabModeDone,
history: &tabHistory{},
reformatMu: &sync.Mutex{},
mode: tabModeDone,
}
t.view.SetDoneFunc(func(key tcell.Key) {
// Altered from: https://gitlab.com/tslocum/cview/-/blob/master/demos/textview/main.go

View File

@ -109,7 +109,7 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int) (*structs
Links: links,
}, nil
} else if strings.HasPrefix(mediatype, "text/") {
if mediatype == "text/x-ansi" || strings.HasSuffix(url, ".ans") {
if mediatype == "text/x-ansi" || strings.HasSuffix(url, ".ans") || strings.HasSuffix(url, ".ansi") {
// ANSI
return &structs.Page{
Mediatype: structs.TextAnsi,

View File

@ -5,10 +5,12 @@
package renderer
import (
"fmt"
urlPkg "net/url"
"strconv"
"strings"
"github.com/makeworld-the-better-one/amfora/config"
"github.com/spf13/viper"
"gitlab.com/tslocum/cview"
)
@ -33,7 +35,9 @@ func RenderPlainText(s string, leftMargin int) string {
var shifted string
lines := strings.Split(cview.Escape(s), "\n")
for i := range lines {
shifted += strings.Repeat(" ", leftMargin) + lines[i] + "\n"
shifted += strings.Repeat(" ", leftMargin) +
"[" + config.GetColorString("regular_text") + "]" + lines[i] + "[-]" +
"\n"
}
return shifted
}
@ -70,6 +74,17 @@ func wrapLine(line string, width int, prefix, suffix string, includeFirst bool)
return ret
}
// tagLines splits a string into lines and adds a the given
// string to the start and another to the end.
// It is used for adding cview color tags.
func tagLines(s, start, end string) string {
lines := strings.Split(s, "\n")
for i := range lines {
lines[i] = start + lines[i] + end
}
return strings.Join(lines, "\n")
}
// convertRegularGemini converts non-preformatted blocks of text/gemini
// into a cview-compatible format.
// It also returns a slice of link URLs.
@ -88,14 +103,16 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
if strings.HasPrefix(lines[i], "#") {
// Headings
var tag string
if viper.GetBool("a-general.color") {
if strings.HasPrefix(lines[i], "###") {
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "[fuchsia::b]", "[-::-]", true)...)
tag = fmt.Sprintf("[%s::b]", config.GetColorString("hdg_3"))
} else if strings.HasPrefix(lines[i], "##") {
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "[lime::b]", "[-::-]", true)...)
tag = fmt.Sprintf("[%s::b]", config.GetColorString("hdg_2"))
} else if strings.HasPrefix(lines[i], "#") {
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "[red::b]", "[-::-]", true)...)
tag = fmt.Sprintf("[%s::b]", config.GetColorString("hdg_1"))
}
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, tag, "[-::-]", true)...)
} else {
// Just bold, no colors
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "[::b]", "[-::-]", true)...)
@ -141,34 +158,37 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
if err == nil && (pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
// A gemini link
// Add the link text in blue (in a region), and a gray link number to the left of it
// Those are the default colors, anyway
wrappedLink = wrapLine(linkText, width,
strings.Repeat(" ", len(strconv.Itoa(numLinks+len(links)))+4)+ // +4 for spaces and brackets
`["`+strconv.Itoa(numLinks+len(links)-1)+`"][dodgerblue]`,
`["`+strconv.Itoa(numLinks+len(links)-1)+`"][`+config.GetColorString("amfora_link")+`]`,
`[-][""]`,
false, // Don't indent the first line, it's the one with link number
)
// Add special stuff to first line, like the link number
wrappedLink[0] = `[silver::b][` + strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
`["` + strconv.Itoa(numLinks+len(links)-1) + `"][dodgerblue]` +
wrappedLink[0] = fmt.Sprintf(`[%s::b][`, config.GetColorString("link_number")) +
strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
`["` + strconv.Itoa(numLinks+len(links)-1) + `"][` + config.GetColorString("amfora_link") + `]` +
wrappedLink[0] + `[-][""]`
} else {
// Not a gemini link, use purple instead
// Not a gemini link
wrappedLink = wrapLine(linkText, width,
strings.Repeat(" ", len(strconv.Itoa(numLinks+len(links)))+4)+ // +4 for spaces and brackets
`["`+strconv.Itoa(numLinks+len(links)-1)+`"][#8700d7]`,
`["`+strconv.Itoa(numLinks+len(links)-1)+`"][`+config.GetColorString("foreign_link")+`]`,
`[-][""]`,
false, // Don't indent the first line, it's the one with link number
)
wrappedLink[0] = `[silver::b][` + strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
`["` + strconv.Itoa(numLinks+len(links)-1) + `"][#8700d7]` +
wrappedLink[0] = fmt.Sprintf(`[%s::b][`, config.GetColorString("link_number")) +
strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +
`["` + strconv.Itoa(numLinks+len(links)-1) + `"][` + config.GetColorString("foreign_link") + `]` +
wrappedLink[0] + `[-][""]`
}
} else {
// No colours allowed
// No colors allowed
wrappedLink = wrapLine(linkText, width,
strings.Repeat(" ", len(strconv.Itoa(numLinks+len(links)))+4)+ // +4 for spaces and brackets
@ -188,9 +208,12 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
} else if strings.HasPrefix(lines[i], "* ") {
if viper.GetBool("a-general.bullets") {
// Wrap list item, and indent wrapped lines past the bullet
wrappedItem := wrapLine(lines[i][1:], width, " ", "", false)
wrappedItem := wrapLine(lines[i][1:], width,
fmt.Sprintf(" [%s]", config.GetColorString("list_text")),
"[-]", false)
// Add bullet
wrappedItem[0] = " \u2022" + wrappedItem[0]
wrappedItem[0] = fmt.Sprintf(" [%s]\u2022", config.GetColorString("list_text")) +
wrappedItem[0] + "[-]"
wrappedLines = append(wrappedLines, wrappedItem...)
}
// Optionally list lines could be colored here too, if color is enabled
@ -200,14 +223,19 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
// Remove beginning quote and maybe space
lines[i] = strings.TrimPrefix(lines[i], ">")
lines[i] = strings.TrimPrefix(lines[i], " ")
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "> [::i]", "[::-]", true)...)
wrappedLines = append(wrappedLines,
wrapLine(lines[i], width, fmt.Sprintf("[%s::i]> ", config.GetColorString("quote_text")),
"[-::-]", true)...,
)
} else if strings.TrimSpace(lines[i]) == "" {
// Just add empty line without processing
wrappedLines = append(wrappedLines, "")
} else {
// Regular line, just wrap it
wrappedLines = append(wrappedLines, wrapLine(lines[i], width, "", "", true)...)
wrappedLines = append(wrappedLines, wrapLine(lines[i], width,
fmt.Sprintf("[%s]", config.GetColorString("regular_text")),
"[-]", true)...)
}
}
@ -237,7 +265,11 @@ func RenderGemini(s string, width, leftMargin int) (string, []string) {
if pre {
// In a preformatted block, so add the text as is
// Don't add the current line with backticks
rendered += buf
rendered += tagLines(
buf,
fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")),
"[-]",
)
} else {
// Not preformatted, regular text
ren, lks := convertRegularGemini(buf, len(links), width)

View File

@ -21,7 +21,7 @@ type Page struct {
Url string
Mediatype Mediatype
Raw string // The raw response, as received over the network
Content string // The processed content, NOT raw. Uses cview colour tags. All link/link texts must have region tags. It will also have a left margin.
Content string // The processed content, NOT raw. Uses cview color tags. All link/link texts must have region tags. It will also have a left margin.
Links []string // URLs, for each region in the content.
Row int // Scroll position
Column int // ditto