mirror of
https://github.com/makew0rld/amfora.git
synced 2024-06-11 19:00:43 +00:00
Merge changes from master
This commit is contained in:
commit
e9fecaca25
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
|
@ -24,6 +24,6 @@ jobs:
|
|||
uses: golangci/golangci-lint-action@v2
|
||||
with:
|
||||
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
|
||||
version: v1.35
|
||||
version: v1.43
|
||||
# Optional: show only new issues if it's a pull request. The default value is `false`.
|
||||
only-new-issues: true
|
||||
|
|
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
|
@ -16,7 +16,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
go-version: 1.17
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
|
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
go-version: ['1.14', '1.15', '1.16']
|
||||
go-version: ['1.15', '1.16', '1.17']
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
|
|
@ -19,15 +19,13 @@ linters:
|
|||
- goerr113
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- revive
|
||||
- goprintffuncname
|
||||
- interfacer
|
||||
- lll
|
||||
- maligned
|
||||
- misspell
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- scopelint
|
||||
- exportloopref
|
||||
- unconvert
|
||||
- unparam
|
||||
|
||||
|
|
24
CHANGELOG.md
24
CHANGELOG.md
|
@ -14,21 +14,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `bind_beginning` and `bind_end` keybindings
|
||||
- Display gemtext from stdin (#205, #242)
|
||||
- Specifying `default` in the theme config uses the terminal's default background color, including transparency (#244, #245)
|
||||
- Redirects occur automatically if it only adds a trailing slash (#271)
|
||||
- Non-gemini links are underlined by default to help color blind users (#189)
|
||||
- Text and element colors of default theme change to be black on terminals with light backgrounds (#181)
|
||||
- Support paths with spaces in `[url-handlers]` config settings (#214)
|
||||
- Display info modal when opening URL with custom application
|
||||
- Files can be opened by relative path on the commandline (#231, #257)
|
||||
- Support keybindings that use <kbd>Shift</kbd> (#269)
|
||||
|
||||
### Changed
|
||||
- Favicon support removed (#199)
|
||||
- Bookmarks are stored using XML in the XBEL format, old bookmarks are transferred (#68)
|
||||
- Text no longer disappears under the left margin when scrolling (regression from v1.8.0) (#197)
|
||||
- Text no longer disappears under the left margin when scrolling (regression in v1.8.0) (#197)
|
||||
- Default search engine changed to geminispace.info from gus.guru
|
||||
- The user's terminal theme colors are used by default (#181)
|
||||
- By default, non-gemini URI schemes are opened in the default application. This requires a config change for previous users, see the [wiki](https://github.com/makeworld-the-better-one/amfora/wiki/Handling-Other-URL-Schemes) (#207)
|
||||
- Windows uses paths set by `XDG` variables over `APPDATA` if they are set (#255)
|
||||
- Treat status codes like 22 as equivalent to 20 as per the latest spec (#266)
|
||||
- Show minimal loading page instead of `about:newtab` when loading a URL in a new tab (#272)
|
||||
|
||||
## Removed
|
||||
- Favicon support (#199)
|
||||
- The default Amfora theme, get it back [here](https://github.com/makeworld-the-better-one/amfora/blob/master/contrib/themes/amfora.toml)
|
||||
|
||||
### Fixed
|
||||
- Help text is now the same color as `regular_text` in the theme config
|
||||
- Non-ASCII (multibyte) characters can now be used as keybindings (#198, #200)
|
||||
- Possible subscription update race condition on startup
|
||||
- Plaintext documents are escaped properly (regression from v1.8.0)
|
||||
- Plaintext documents are escaped properly (regression in v1.8.0)
|
||||
- Help page scrollbar color matches what's in the theme config
|
||||
- Regression where lists would not appear if `bullets = false` (#234, #235)
|
||||
- Support multiple bookmarks with the same name
|
||||
- Cert change message grammar: "an security" -> "a security" (#274)
|
||||
- Display an error modal for status codes that can't be handled
|
||||
- Prevent user from getting trapped in the help menu when keybindings are pressed (#241, #261)
|
||||
|
||||
|
||||
## [1.8.0] - 2021-02-17
|
||||
|
|
1
NOTES.md
1
NOTES.md
|
@ -6,7 +6,6 @@
|
|||
## Upstream Bugs
|
||||
- Bookmark keys aren't deleted, just set to `""`
|
||||
- Waiting on [this viper PR](https://github.com/spf13/viper/pull/519) to be merged
|
||||
- [cview.Styles not being used](https://code.rocketnine.space/tslocum/cview/issues/47) - issue is circumvented in Amfora
|
||||
- [ANSI conversion is messed up](https://code.rocketnine.space/tslocum/cview/issues/48)
|
||||
- [WordWrap is broken in some cases](https://code.rocketnine.space/tslocum/cview/issues/27) - close #156 if this is fixed
|
||||
- [Prevent panic when reformatting](https://code.rocketnine.space/tslocum/cview/issues/50) - can't reliably reproduce or debug
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
###### Recording of v1.0.0
|
||||
|
||||
Amfora aims to be the best looking [Gemini](https://gemini.circumlunar.space/) client with the most features... all in the terminal. It does not support Gopher or other non-Web protocols - check out [Bombadillo](http://bombadillo.colorfield.space/) for that.
|
||||
Amfora aims to be the best looking [Gemini](https://geminiquickst.art/) client with the most features... all in the terminal. It does not support Gopher or other non-Web protocols - check out [Bombadillo](http://bombadillo.colorfield.space/) for that.
|
||||
|
||||
It also aims to be completely cross platform, with full Windows support. If you're on Windows, I would not recommend using the default terminal software. Use [Windows Terminal](https://www.microsoft.com/en-us/p/windows-terminal/9n0dx20hk701) instead, and make sure it [works with UTF-8](https://akr.am/blog/posts/using-utf-8-in-the-windows-terminal). Note that some of the application colors might not display correctly on Windows, but all functionality will still work.
|
||||
|
||||
|
@ -80,7 +80,7 @@ This section is for advanced users who want to install the latest (possibly unst
|
|||
<summary>Click to expand</summary>
|
||||
|
||||
**Requirements:**
|
||||
- Go 1.14 or later
|
||||
- Go 1.15 or later
|
||||
- GNU Make
|
||||
|
||||
Please note the Makefile does not intend to support Windows, and so there may be issues.
|
||||
|
@ -163,6 +163,8 @@ Amfora ❤️ open source!
|
|||
- [progressbar](https://github.com/schollz/progressbar)
|
||||
- [go-humanize](https://github.com/dustin/go-humanize)
|
||||
- [gofeed](https://github.com/mmcdole/gofeed)
|
||||
- [clipboard](https://github.com/atotto/clipboard)
|
||||
- [termenv](https://github.com/muesli/termenv)
|
||||
|
||||
## License
|
||||
This project is licensed under the GPL v3.0. See the [LICENSE](./LICENSE) file for details.
|
||||
|
|
|
@ -23,3 +23,5 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||
* Anas Mohamed (@amohamed11)
|
||||
* David Jimenez (@dvejmz)
|
||||
* Michael McDonagh (@m-mcdonagh)
|
||||
* mooff (@awfulcooking)
|
||||
* Josias (@justjosias)
|
||||
|
|
39
amfora.go
39
amfora.go
|
@ -4,12 +4,14 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/makeworld-the-better-one/amfora/bookmarks"
|
||||
"github.com/makeworld-the-better-one/amfora/client"
|
||||
"github.com/makeworld-the-better-one/amfora/config"
|
||||
"github.com/makeworld-the-better-one/amfora/display"
|
||||
"github.com/makeworld-the-better-one/amfora/logger"
|
||||
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
||||
)
|
||||
|
||||
|
@ -20,10 +22,15 @@ var (
|
|||
)
|
||||
|
||||
func main() {
|
||||
// err := logger.Init()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
log, err := logger.GetLogger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
debugModeEnabled := os.Getenv("AMFORA_DEBUG") == "1"
|
||||
if debugModeEnabled {
|
||||
log.Println("Debug mode enabled")
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
if os.Args[1] == "--version" || os.Args[1] == "-v" {
|
||||
|
@ -42,7 +49,7 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
err := config.Init()
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Config error: %v\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -67,11 +74,29 @@ func main() {
|
|||
|
||||
// Initialize Amfora's settings
|
||||
display.Init(version, commit, builtBy)
|
||||
display.NewTab()
|
||||
|
||||
// Load a URL, file, or render from stdin
|
||||
if len(os.Args[1:]) > 0 {
|
||||
display.URL(os.Args[1])
|
||||
url := os.Args[1]
|
||||
if !strings.Contains(url, "://") || strings.HasPrefix(url, "../") || strings.HasPrefix(url, "./") {
|
||||
fileName := url
|
||||
if _, err := os.Stat(fileName); err == nil {
|
||||
if !strings.HasPrefix(fileName, "/") {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error getting working directory path: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fileName = filepath.Join(cwd, fileName)
|
||||
}
|
||||
url = "file://" + fileName
|
||||
}
|
||||
}
|
||||
display.NewTabWithURL(url)
|
||||
} else if !isStdinEmpty() {
|
||||
renderFromStdin()
|
||||
} else {
|
||||
display.NewTab()
|
||||
}
|
||||
|
||||
// Start
|
||||
|
|
|
@ -61,7 +61,6 @@ func Init() error {
|
|||
|
||||
err = os.Remove(config.OldBkmkPath)
|
||||
if err != nil {
|
||||
//nolint:goerr113
|
||||
return fmt.Errorf(
|
||||
"couldn't delete old bookmarks file (%s), you must delete it yourself to prevent duplicate bookmarks: %w",
|
||||
config.OldBkmkPath,
|
||||
|
|
|
@ -62,7 +62,6 @@ func loadTofuEntry(domain string, port string) (string, time.Time, error) {
|
|||
return id, expiry, nil
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
// certID returns a generic string representing a cert or domain.
|
||||
func certID(cert *x509.Certificate) string {
|
||||
h := sha256.New()
|
||||
|
@ -73,7 +72,7 @@ func certID(cert *x509.Certificate) string {
|
|||
// origCertID uses cert.Raw, which was used in v1.0.0 of the app.
|
||||
func origCertID(cert *x509.Certificate) string {
|
||||
h := sha256.New()
|
||||
h.Write(cert.Raw) //nolint:errcheck
|
||||
h.Write(cert.Raw)
|
||||
return fmt.Sprintf("%X", h.Sum(nil))
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/makeworld-the-better-one/amfora/cache"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/muesli/termenv"
|
||||
"github.com/rkoesters/xdg/basedir"
|
||||
"github.com/rkoesters/xdg/userdirs"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -59,9 +60,16 @@ var MediaHandlers = make(map[string]MediaHandler)
|
|||
// Defaults to ScrollBarAuto on an invalid value
|
||||
var ScrollBar cview.ScrollBarVisibility
|
||||
|
||||
// Whether the user's terminal is dark or light
|
||||
// Defaults to dark, but is determined in Init()
|
||||
// Used to prevent white text on a white background with the default theme
|
||||
var hasDarkTerminalBackground bool
|
||||
|
||||
func Init() error {
|
||||
|
||||
// *** Set paths ***
|
||||
// Windows uses paths under APPDATA, Unix systems use XDG paths
|
||||
// Windows systems use XDG paths if variables are defined, see #255
|
||||
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
|
@ -78,10 +86,10 @@ func Init() error {
|
|||
}
|
||||
|
||||
// Store config directory and file paths
|
||||
if runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == "windows" && os.Getenv("XDG_CONFIG_HOME") == "" {
|
||||
configDir = amforaAppData
|
||||
} else {
|
||||
// Unix / POSIX system
|
||||
// Unix / POSIX system, or Windows with XDG_CONFIG_HOME defined
|
||||
configDir = filepath.Join(basedir.ConfigHome, "amfora")
|
||||
}
|
||||
configPath = filepath.Join(configDir, "config.toml")
|
||||
|
@ -94,7 +102,7 @@ func Init() error {
|
|||
}
|
||||
|
||||
// Store TOFU db directory and file paths
|
||||
if runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == "windows" && os.Getenv("XDG_CACHE_HOME") == "" {
|
||||
// Windows just stores it in APPDATA along with other stuff
|
||||
tofuDBDir = amforaAppData
|
||||
} else {
|
||||
|
@ -104,7 +112,7 @@ func Init() error {
|
|||
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
|
||||
|
||||
// Store bookmarks dir and path
|
||||
if runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == "windows" && os.Getenv("XDG_DATA_HOME") == "" {
|
||||
// Windows just keeps it in APPDATA along with other Amfora files
|
||||
bkmkDir = amforaAppData
|
||||
} else {
|
||||
|
@ -115,18 +123,12 @@ func Init() error {
|
|||
BkmkPath = filepath.Join(bkmkDir, "bookmarks.xml")
|
||||
|
||||
// Feeds dir and path
|
||||
if runtime.GOOS == "windows" {
|
||||
if runtime.GOOS == "windows" && os.Getenv("XDG_DATA_HOME") == "" {
|
||||
// In APPDATA beside other Amfora files
|
||||
subscriptionDir = amforaAppData
|
||||
} else {
|
||||
// XDG data dir on POSIX systems
|
||||
xdg_data, ok := os.LookupEnv("XDG_DATA_HOME")
|
||||
if ok && strings.TrimSpace(xdg_data) != "" {
|
||||
subscriptionDir = filepath.Join(xdg_data, "amfora")
|
||||
} else {
|
||||
// Default to ~/.local/share/amfora
|
||||
subscriptionDir = filepath.Join(home, ".local", "share", "amfora")
|
||||
}
|
||||
subscriptionDir = filepath.Join(basedir.DataHome, "amfora")
|
||||
}
|
||||
SubscriptionPath = filepath.Join(subscriptionDir, "subscriptions.json")
|
||||
|
||||
|
@ -203,6 +205,7 @@ func Init() error {
|
|||
viper.SetDefault("a-general.page_max_size", 2097152)
|
||||
viper.SetDefault("a-general.page_max_time", 10)
|
||||
viper.SetDefault("a-general.scrollbar", "auto")
|
||||
viper.SetDefault("a-general.underline", true)
|
||||
viper.SetDefault("commands.command1", "")
|
||||
viper.SetDefault("commands.command2", "")
|
||||
viper.SetDefault("commands.command3", "")
|
||||
|
@ -281,7 +284,7 @@ func Init() error {
|
|||
viper.SetDefault("keybindings.bind_beginning", []string{"Home", "g"})
|
||||
viper.SetDefault("keybindings.bind_end", []string{"End", "G"})
|
||||
viper.SetDefault("keybindings.shift_numbers", "")
|
||||
viper.SetDefault("url-handlers.other", "off")
|
||||
viper.SetDefault("url-handlers.other", "default")
|
||||
viper.SetDefault("cache.max_size", 0)
|
||||
viper.SetDefault("cache.max_pages", 20)
|
||||
viper.SetDefault("cache.timeout", 1800)
|
||||
|
@ -398,7 +401,15 @@ func Init() error {
|
|||
}
|
||||
if viper.GetBool("a-general.color") {
|
||||
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
|
||||
} // Otherwise it's black by default
|
||||
} else {
|
||||
// No colors allowed, set background to black instead of default
|
||||
themeMu.Lock()
|
||||
theme["bg"] = tcell.ColorBlack
|
||||
cview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
||||
themeMu.Unlock()
|
||||
}
|
||||
|
||||
hasDarkTerminalBackground = termenv.HasDarkBackground()
|
||||
|
||||
// Parse HTTP command
|
||||
HTTPCommand = viper.GetStringSlice("a-general.http")
|
||||
|
|
|
@ -3,6 +3,17 @@ 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.
|
||||
# You can edit this file to set your own configuration for Amfora.
|
||||
|
||||
# When Amfora updates, defaults may change, but this file on your drive will not.
|
||||
# You can always get the latest defaults on GitHub.
|
||||
# https://github.com/makeworld-the-better-one/amfora/blob/master/default-config.toml
|
||||
|
||||
# Please also check out the Amfora Wiki for more help
|
||||
# https://github.com/makeworld-the-better-one/amfora/wiki
|
||||
# gemini://makeworld.space/amfora-wiki/
|
||||
|
||||
|
||||
|
||||
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
||||
# Valid URL examples:
|
||||
|
@ -26,7 +37,7 @@ auto_redirect = false
|
|||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||
# A space will be prepended to the URL.
|
||||
#
|
||||
# The best to define a command is using a string array.
|
||||
# The best way to define a command is using a string array.
|
||||
# Examples:
|
||||
# http = ['firefox']
|
||||
# http = ['custom-browser', '--flag', '--option=2']
|
||||
|
@ -74,6 +85,10 @@ page_max_time = 10
|
|||
# "auto" means the scrollbar only appears when the page is longer than the window.
|
||||
scrollbar = "auto"
|
||||
|
||||
# Underline non-gemini URLs
|
||||
# This is done to help color blind users
|
||||
underline = true
|
||||
|
||||
|
||||
[auth]
|
||||
# Authentication settings
|
||||
|
@ -216,19 +231,30 @@ scrollbar = "auto"
|
|||
[url-handlers]
|
||||
# Allows setting the commands to run for various URL schemes.
|
||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||
# ftp = 'filezilla'
|
||||
# You can set any scheme to "off" or "" to disable handling it, or
|
||||
# ftp = ['filezilla']
|
||||
# You can set any scheme to 'off' or '' to disable handling it, or
|
||||
# just leave the key unset.
|
||||
#
|
||||
# DO NOT use this for setting the HTTP command.
|
||||
# Use the http setting in the "a-general" section above.
|
||||
#
|
||||
# NOTE: These settings are overrided by the ones in the proxies section.
|
||||
#
|
||||
# The best way to define a command is using a string array.
|
||||
# Examples:
|
||||
# magnet = ['transmission']
|
||||
# foo = ['custom-browser', '--flag', '--option=2']
|
||||
# tel = ['/path/with spaces/in it/telephone']
|
||||
#
|
||||
# Note the use of single quotes, so that backslashes will not be escaped.
|
||||
# Using just a string will also work, but it is deprecated, and will degrade if
|
||||
# you use paths with spaces.
|
||||
|
||||
# This is a special key that defines the handler for all URL schemes for which
|
||||
# no handler is defined.
|
||||
other = 'off'
|
||||
# It uses the special value 'default', which will try and use the default
|
||||
# application on your computer for opening this kind of URI.
|
||||
other = 'default'
|
||||
|
||||
|
||||
# [[mediatype-handlers]] section
|
||||
|
@ -353,6 +379,8 @@ entries_per_page = 20
|
|||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||
# Setting a background to "default" keeps the terminal default
|
||||
# If your terminal has transparency, set any background to "default" to keep it transparent
|
||||
# The key "bg" is already set to "default", but this can be used on other backgrounds,
|
||||
# like for modals.
|
||||
|
||||
# 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
|
||||
|
|
|
@ -101,7 +101,7 @@ var tcellKeys map[string]tcell.Key
|
|||
// a string in the format used by the configuration file. Support
|
||||
// function for GetKeyBinding(), used to make the help panel helpful.
|
||||
func keyBindingToString(kb keyBinding) (string, bool) {
|
||||
var prefix string = ""
|
||||
var prefix string
|
||||
|
||||
if kb.mod&tcell.ModAlt == tcell.ModAlt {
|
||||
prefix = "Alt-"
|
||||
|
@ -124,7 +124,7 @@ func keyBindingToString(kb keyBinding) (string, bool) {
|
|||
// Used by the help panel so bindable keys display with their
|
||||
// bound values rather than hardcoded defaults.
|
||||
func GetKeyBinding(cmd Command) string {
|
||||
var s string = ""
|
||||
var s string
|
||||
for kb, c := range bindings {
|
||||
if c == cmd {
|
||||
t, ok := keyBindingToString(kb)
|
||||
|
@ -143,14 +143,19 @@ func GetKeyBinding(cmd Command) string {
|
|||
// Parse a single keybinding string and add it to the binding map
|
||||
func parseBinding(cmd Command, binding string) {
|
||||
var k tcell.Key
|
||||
var m tcell.ModMask = 0
|
||||
var r rune = 0
|
||||
var m tcell.ModMask
|
||||
var r rune
|
||||
|
||||
if strings.HasPrefix(binding, "Alt-") {
|
||||
m = tcell.ModAlt
|
||||
binding = binding[4:]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(binding, "Shift-") {
|
||||
m += tcell.ModShift
|
||||
binding = binding[6:]
|
||||
}
|
||||
|
||||
if len([]rune(binding)) == 1 {
|
||||
k = tcell.KeyRune
|
||||
r = []rune(binding)[0]
|
||||
|
|
290
config/theme.go
290
config/theme.go
|
@ -8,97 +8,164 @@ import (
|
|||
)
|
||||
|
||||
// Functions to allow themeing configuration.
|
||||
// UI element colors are mapped to a string key, such as "error" or "tab_bg"
|
||||
// UI element tcell.Colors are mapped to a string key, such as "error" or "tab_bg"
|
||||
// These are the same keys used in the config file.
|
||||
|
||||
// Special color with no real color value
|
||||
// Used for a default foreground color
|
||||
// White is the terminal background is black, black if the terminal background is white
|
||||
// Converted to a real color in this file before being sent out to other modules
|
||||
const ColorFg = tcell.ColorSpecial | 2
|
||||
|
||||
// The same as ColorFg, but inverted
|
||||
const ColorBg = tcell.ColorSpecial | 3
|
||||
|
||||
var themeMu = sync.RWMutex{}
|
||||
var theme = map[string]tcell.Color{
|
||||
// Default values below
|
||||
// Map these for special uses in code
|
||||
"ColorBg": ColorBg,
|
||||
"ColorFg": ColorFg,
|
||||
|
||||
"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,
|
||||
"scrollbar": tcell.ColorWhite,
|
||||
// Default values below
|
||||
// Only the 16 Xterm system tcell.Colors are used, because those are the tcell.Colors overrided
|
||||
// by the user's default terminal theme
|
||||
|
||||
// Used for cview.Styles.PrimitiveBackgroundColor
|
||||
// Set to tcell.ColorDefault because that allows transparent terminals to work
|
||||
// The rest of this theme assumes that the background is equivalent to black, but
|
||||
// white colors switched to black later if the background is determined to be white.
|
||||
//
|
||||
// Also, this is set to tcell.ColorBlack in config.go if colors are disabled in the config.
|
||||
"bg": tcell.ColorDefault,
|
||||
|
||||
"tab_num": tcell.ColorTeal,
|
||||
"tab_divider": ColorFg,
|
||||
"bottombar_label": tcell.ColorTeal,
|
||||
"bottombar_text": ColorBg,
|
||||
"bottombar_bg": ColorFg,
|
||||
"scrollbar": ColorFg,
|
||||
|
||||
// Modals
|
||||
"btn_bg": tcell.ColorNavy, // All modal buttons
|
||||
"btn_text": tcell.ColorWhite,
|
||||
"btn_bg": tcell.ColorTeal, // All modal buttons
|
||||
"btn_text": tcell.ColorWhite, // White instead of ColorFg because background is known to be Teal
|
||||
|
||||
"dl_choice_modal_bg": tcell.ColorPurple,
|
||||
"dl_choice_modal_bg": tcell.ColorOlive,
|
||||
"dl_choice_modal_text": tcell.ColorWhite,
|
||||
"dl_modal_bg": tcell.Color130, // xterm:DarkOrange3, #af5f00
|
||||
"dl_modal_bg": tcell.ColorOlive,
|
||||
"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_bg": tcell.ColorTeal,
|
||||
"yesno_modal_text": tcell.ColorWhite,
|
||||
"tofu_modal_bg": tcell.ColorMaroon,
|
||||
"tofu_modal_text": tcell.ColorWhite,
|
||||
"subscription_modal_bg": tcell.Color61, // xterm:SlateBlue3, #5f5faf
|
||||
"subscription_modal_bg": tcell.ColorTeal,
|
||||
"subscription_modal_text": tcell.ColorWhite,
|
||||
|
||||
"input_modal_bg": tcell.ColorGreen,
|
||||
"input_modal_text": tcell.ColorWhite,
|
||||
"input_modal_field_bg": tcell.ColorBlue,
|
||||
"input_modal_field_bg": tcell.ColorNavy,
|
||||
"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_bg": tcell.ColorNavy,
|
||||
"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
|
||||
"amfora_link": tcell.ColorBlue,
|
||||
"foreign_link": tcell.ColorPurple,
|
||||
"link_number": tcell.ColorSilver,
|
||||
"regular_text": tcell.ColorWhite,
|
||||
"quote_text": tcell.ColorWhite,
|
||||
"preformatted_text": tcell.Color229, // xterm:Wheat1, #ffffaf
|
||||
"list_text": tcell.ColorWhite,
|
||||
"regular_text": ColorFg,
|
||||
"quote_text": ColorFg,
|
||||
"preformatted_text": ColorFg,
|
||||
"list_text": ColorFg,
|
||||
}
|
||||
|
||||
func SetColor(key string, color tcell.Color) {
|
||||
themeMu.Lock()
|
||||
theme[key] = color
|
||||
// Use truecolor because this is only called with user-set tcell.Colors
|
||||
// Which should be represented exactly
|
||||
theme[key] = color.TrueColor()
|
||||
themeMu.Unlock()
|
||||
}
|
||||
|
||||
// GetColor will return tcell.ColorBlack if there is no color for the provided key.
|
||||
// GetColor will return tcell.ColorBlack if there is no tcell.Color for the provided key.
|
||||
func GetColor(key string) tcell.Color {
|
||||
themeMu.RLock()
|
||||
defer themeMu.RUnlock()
|
||||
return theme[key].TrueColor()
|
||||
|
||||
color := theme[key]
|
||||
|
||||
if color == ColorFg {
|
||||
if hasDarkTerminalBackground {
|
||||
return tcell.ColorWhite
|
||||
}
|
||||
return tcell.ColorBlack
|
||||
}
|
||||
if color == ColorBg {
|
||||
if hasDarkTerminalBackground {
|
||||
return tcell.ColorBlack
|
||||
}
|
||||
return tcell.ColorWhite
|
||||
}
|
||||
|
||||
return color
|
||||
}
|
||||
|
||||
// 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()
|
||||
color := theme[key].TrueColor()
|
||||
// colorToString converts a color to a string for use in a cview tag
|
||||
func colorToString(color tcell.Color) string {
|
||||
if color == tcell.ColorDefault {
|
||||
return "-"
|
||||
}
|
||||
|
||||
if color == ColorFg {
|
||||
if hasDarkTerminalBackground {
|
||||
return "white"
|
||||
}
|
||||
return "black"
|
||||
}
|
||||
if color == ColorBg {
|
||||
if hasDarkTerminalBackground {
|
||||
return "black"
|
||||
}
|
||||
return "white"
|
||||
}
|
||||
|
||||
if color&tcell.ColorIsRGB == 0 {
|
||||
// tcell.Color is not RGB/TrueColor, it's a tcell.Color from the default terminal
|
||||
// theme as set above
|
||||
// Return a tcell.Color name instead of a hex code, so that cview doesn't use TrueColor
|
||||
return ColorToColorName[color]
|
||||
}
|
||||
|
||||
// Color set by user, must be respected exactly so hex code is used
|
||||
return fmt.Sprintf("#%06x", color.Hex())
|
||||
}
|
||||
|
||||
// GetContrastingColor returns ColorBlack if color is brighter than gray
|
||||
// otherwise returns ColorWhite if color is dimmer than gray
|
||||
// if color is ColorDefault (undefined luminance) this returns ColorDefault
|
||||
// GetColorString returns a string that can be used in a cview tcell.Color tag,
|
||||
// for the given theme key.
|
||||
// It will return "#000000" if there is no tcell.Color for the provided key.
|
||||
func GetColorString(key string) string {
|
||||
themeMu.RLock()
|
||||
defer themeMu.RUnlock()
|
||||
|
||||
return colorToString(theme[key])
|
||||
}
|
||||
|
||||
// GetContrastingColor returns tcell.ColorBlack if tcell.Color is brighter than gray
|
||||
// otherwise returns tcell.ColorWhite if tcell.Color is dimmer than gray
|
||||
// if tcell.Color is tcell.ColorDefault (undefined luminance) this returns tcell.ColorDefault
|
||||
func GetContrastingColor(color tcell.Color) tcell.Color {
|
||||
if color == tcell.ColorDefault {
|
||||
// color should never be tcell.ColorDefault
|
||||
// tcell.Color should never be tcell.ColorDefault
|
||||
// only config keys which end in bg are allowed to be set to default
|
||||
// and the only way the argument of this function is set to ColorDefault
|
||||
// and the only way the argument of this function is set to tcell.ColorDefault
|
||||
// is if both the text and bg of an element in the UI are set to default
|
||||
return tcell.ColorDefault
|
||||
}
|
||||
|
@ -128,6 +195,149 @@ func GetTextColor(key, bg string) tcell.Color {
|
|||
// This happens on focus of a UI element which has a bg of default, in which case
|
||||
// It return tcell.ColorBlack or tcell.ColorWhite, depending on which is more readable
|
||||
func GetTextColorString(key, bg string) string {
|
||||
color := GetTextColor(key, bg)
|
||||
return fmt.Sprintf("#%06x", color.Hex())
|
||||
return colorToString(GetTextColor(key, bg))
|
||||
}
|
||||
|
||||
// Inverted version of a tcell map
|
||||
// https://github.com/gdamore/tcell/blob/v2.3.3/color.go#L845
|
||||
var ColorToColorName = map[tcell.Color]string{
|
||||
tcell.ColorBlack: "black",
|
||||
tcell.ColorMaroon: "maroon",
|
||||
tcell.ColorGreen: "green",
|
||||
tcell.ColorOlive: "olive",
|
||||
tcell.ColorNavy: "navy",
|
||||
tcell.ColorPurple: "purple",
|
||||
tcell.ColorTeal: "teal",
|
||||
tcell.ColorSilver: "silver",
|
||||
tcell.ColorGray: "gray",
|
||||
tcell.ColorRed: "red",
|
||||
tcell.ColorLime: "lime",
|
||||
tcell.ColorYellow: "yellow",
|
||||
tcell.ColorBlue: "blue",
|
||||
tcell.ColorFuchsia: "fuchsia",
|
||||
tcell.ColorAqua: "aqua",
|
||||
tcell.ColorWhite: "white",
|
||||
tcell.ColorAliceBlue: "aliceblue",
|
||||
tcell.ColorAntiqueWhite: "antiquewhite",
|
||||
tcell.ColorAquaMarine: "aquamarine",
|
||||
tcell.ColorAzure: "azure",
|
||||
tcell.ColorBeige: "beige",
|
||||
tcell.ColorBisque: "bisque",
|
||||
tcell.ColorBlanchedAlmond: "blanchedalmond",
|
||||
tcell.ColorBlueViolet: "blueviolet",
|
||||
tcell.ColorBrown: "brown",
|
||||
tcell.ColorBurlyWood: "burlywood",
|
||||
tcell.ColorCadetBlue: "cadetblue",
|
||||
tcell.ColorChartreuse: "chartreuse",
|
||||
tcell.ColorChocolate: "chocolate",
|
||||
tcell.ColorCoral: "coral",
|
||||
tcell.ColorCornflowerBlue: "cornflowerblue",
|
||||
tcell.ColorCornsilk: "cornsilk",
|
||||
tcell.ColorCrimson: "crimson",
|
||||
tcell.ColorDarkBlue: "darkblue",
|
||||
tcell.ColorDarkCyan: "darkcyan",
|
||||
tcell.ColorDarkGoldenrod: "darkgoldenrod",
|
||||
tcell.ColorDarkGray: "darkgray",
|
||||
tcell.ColorDarkGreen: "darkgreen",
|
||||
tcell.ColorDarkKhaki: "darkkhaki",
|
||||
tcell.ColorDarkMagenta: "darkmagenta",
|
||||
tcell.ColorDarkOliveGreen: "darkolivegreen",
|
||||
tcell.ColorDarkOrange: "darkorange",
|
||||
tcell.ColorDarkOrchid: "darkorchid",
|
||||
tcell.ColorDarkRed: "darkred",
|
||||
tcell.ColorDarkSalmon: "darksalmon",
|
||||
tcell.ColorDarkSeaGreen: "darkseagreen",
|
||||
tcell.ColorDarkSlateBlue: "darkslateblue",
|
||||
tcell.ColorDarkSlateGray: "darkslategray",
|
||||
tcell.ColorDarkTurquoise: "darkturquoise",
|
||||
tcell.ColorDarkViolet: "darkviolet",
|
||||
tcell.ColorDeepPink: "deeppink",
|
||||
tcell.ColorDeepSkyBlue: "deepskyblue",
|
||||
tcell.ColorDimGray: "dimgray",
|
||||
tcell.ColorDodgerBlue: "dodgerblue",
|
||||
tcell.ColorFireBrick: "firebrick",
|
||||
tcell.ColorFloralWhite: "floralwhite",
|
||||
tcell.ColorForestGreen: "forestgreen",
|
||||
tcell.ColorGainsboro: "gainsboro",
|
||||
tcell.ColorGhostWhite: "ghostwhite",
|
||||
tcell.ColorGold: "gold",
|
||||
tcell.ColorGoldenrod: "goldenrod",
|
||||
tcell.ColorGreenYellow: "greenyellow",
|
||||
tcell.ColorHoneydew: "honeydew",
|
||||
tcell.ColorHotPink: "hotpink",
|
||||
tcell.ColorIndianRed: "indianred",
|
||||
tcell.ColorIndigo: "indigo",
|
||||
tcell.ColorIvory: "ivory",
|
||||
tcell.ColorKhaki: "khaki",
|
||||
tcell.ColorLavender: "lavender",
|
||||
tcell.ColorLavenderBlush: "lavenderblush",
|
||||
tcell.ColorLawnGreen: "lawngreen",
|
||||
tcell.ColorLemonChiffon: "lemonchiffon",
|
||||
tcell.ColorLightBlue: "lightblue",
|
||||
tcell.ColorLightCoral: "lightcoral",
|
||||
tcell.ColorLightCyan: "lightcyan",
|
||||
tcell.ColorLightGoldenrodYellow: "lightgoldenrodyellow",
|
||||
tcell.ColorLightGray: "lightgray",
|
||||
tcell.ColorLightGreen: "lightgreen",
|
||||
tcell.ColorLightPink: "lightpink",
|
||||
tcell.ColorLightSalmon: "lightsalmon",
|
||||
tcell.ColorLightSeaGreen: "lightseagreen",
|
||||
tcell.ColorLightSkyBlue: "lightskyblue",
|
||||
tcell.ColorLightSlateGray: "lightslategray",
|
||||
tcell.ColorLightSteelBlue: "lightsteelblue",
|
||||
tcell.ColorLightYellow: "lightyellow",
|
||||
tcell.ColorLimeGreen: "limegreen",
|
||||
tcell.ColorLinen: "linen",
|
||||
tcell.ColorMediumAquamarine: "mediumaquamarine",
|
||||
tcell.ColorMediumBlue: "mediumblue",
|
||||
tcell.ColorMediumOrchid: "mediumorchid",
|
||||
tcell.ColorMediumPurple: "mediumpurple",
|
||||
tcell.ColorMediumSeaGreen: "mediumseagreen",
|
||||
tcell.ColorMediumSlateBlue: "mediumslateblue",
|
||||
tcell.ColorMediumSpringGreen: "mediumspringgreen",
|
||||
tcell.ColorMediumTurquoise: "mediumturquoise",
|
||||
tcell.ColorMediumVioletRed: "mediumvioletred",
|
||||
tcell.ColorMidnightBlue: "midnightblue",
|
||||
tcell.ColorMintCream: "mintcream",
|
||||
tcell.ColorMistyRose: "mistyrose",
|
||||
tcell.ColorMoccasin: "moccasin",
|
||||
tcell.ColorNavajoWhite: "navajowhite",
|
||||
tcell.ColorOldLace: "oldlace",
|
||||
tcell.ColorOliveDrab: "olivedrab",
|
||||
tcell.ColorOrange: "orange",
|
||||
tcell.ColorOrangeRed: "orangered",
|
||||
tcell.ColorOrchid: "orchid",
|
||||
tcell.ColorPaleGoldenrod: "palegoldenrod",
|
||||
tcell.ColorPaleGreen: "palegreen",
|
||||
tcell.ColorPaleTurquoise: "paleturquoise",
|
||||
tcell.ColorPaleVioletRed: "palevioletred",
|
||||
tcell.ColorPapayaWhip: "papayawhip",
|
||||
tcell.ColorPeachPuff: "peachpuff",
|
||||
tcell.ColorPeru: "peru",
|
||||
tcell.ColorPink: "pink",
|
||||
tcell.ColorPlum: "plum",
|
||||
tcell.ColorPowderBlue: "powderblue",
|
||||
tcell.ColorRebeccaPurple: "rebeccapurple",
|
||||
tcell.ColorRosyBrown: "rosybrown",
|
||||
tcell.ColorRoyalBlue: "royalblue",
|
||||
tcell.ColorSaddleBrown: "saddlebrown",
|
||||
tcell.ColorSalmon: "salmon",
|
||||
tcell.ColorSandyBrown: "sandybrown",
|
||||
tcell.ColorSeaGreen: "seagreen",
|
||||
tcell.ColorSeashell: "seashell",
|
||||
tcell.ColorSienna: "sienna",
|
||||
tcell.ColorSkyblue: "skyblue",
|
||||
tcell.ColorSlateBlue: "slateblue",
|
||||
tcell.ColorSlateGray: "slategray",
|
||||
tcell.ColorSnow: "snow",
|
||||
tcell.ColorSpringGreen: "springgreen",
|
||||
tcell.ColorSteelBlue: "steelblue",
|
||||
tcell.ColorTan: "tan",
|
||||
tcell.ColorThistle: "thistle",
|
||||
tcell.ColorTomato: "tomato",
|
||||
tcell.ColorTurquoise: "turquoise",
|
||||
tcell.ColorViolet: "violet",
|
||||
tcell.ColorWheat: "wheat",
|
||||
tcell.ColorWhiteSmoke: "whitesmoke",
|
||||
tcell.ColorYellowGreen: "yellowgreen",
|
||||
}
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
|
||||
You can use these themes by replacing the `[theme]` section of your [config](https://github.com/makeworld-the-better-one/amfora/wiki/Configuration) with their contents. Some themes won't display properly on terminals that do not have truecolor support.
|
||||
|
||||
## Amfora
|
||||
|
||||
This is the original Amfora theme we all know and love. From v1.9.0 and onwards, the user's terminal theme is used by default. Use this theme to restore the original Amfora look.
|
||||
|
||||
<a href="https://raw.githubusercontent.com/makeworld-the-better-one/amfora/master/demo-large.gif">
|
||||
<img src="../../demo-large.gif" alt="Demo GIF" width="80%">
|
||||
</a>
|
||||
|
||||
## Nord
|
||||
|
||||
Contributed by **[@lokesh-krishna](https://github.com/lokesh-krishna)**.
|
||||
|
|
50
contrib/themes/amfora.toml
Normal file
50
contrib/themes/amfora.toml
Normal file
|
@ -0,0 +1,50 @@
|
|||
[theme]
|
||||
|
||||
# Only the 256 xterm colors are used, so truecolor support is not needed
|
||||
|
||||
bg = "black"
|
||||
tab_num = "#008787"
|
||||
tab_divider = "white"
|
||||
bottombar_label = "#008787"
|
||||
bottombar_text = "black"
|
||||
bottombar_bg = "white"
|
||||
scrollbar = "white"
|
||||
|
||||
btn_bg = "#000080"
|
||||
btn_text = "white"
|
||||
|
||||
dl_choice_modal_bg = "#800080"
|
||||
dl_choice_modal_text = "white"
|
||||
dl_modal_bg = "#af5f00"
|
||||
dl_modal_text = "white"
|
||||
info_modal_bg = "#808080"
|
||||
info_modal_text = "white"
|
||||
error_modal_bg = "#800000"
|
||||
error_modal_text = "white"
|
||||
yesno_modal_bg = "#800080"
|
||||
yesno_modal_text = "white"
|
||||
tofu_modal_bg = "#800000"
|
||||
tofu_modal_text = "white"
|
||||
subscription_modal_bg = "#5f5faf"
|
||||
subscription_modal_text = "white"
|
||||
input_modal_bg = "#008000"
|
||||
input_modal_text = "white"
|
||||
input_modal_field_bg = "#0000ff"
|
||||
input_modal_field_text = "white"
|
||||
|
||||
bkmk_modal_bg = "#008080"
|
||||
bkmk_modal_text = "white"
|
||||
bkmk_modal_label = "#ffff00"
|
||||
bkmk_modal_field_bg = "#0000ff"
|
||||
bkmk_modal_field_text = "white"
|
||||
|
||||
hdg_1 = "#ff0000"
|
||||
hdg_2 = "#00ff00"
|
||||
hdg_3 = "#ff00ff"
|
||||
amfora_link = "#0087ff"
|
||||
foreign_link = "#8700d7"
|
||||
link_number = "#c0c0c0"
|
||||
regular_text = "white"
|
||||
quote_text = "white"
|
||||
preformatted_text = "#ffffaf"
|
||||
list_text = "white"
|
|
@ -1,5 +1,16 @@
|
|||
# This is the default config file.
|
||||
# It also shows all the default values, if you don't create the file.
|
||||
# You can edit this file to set your own configuration for Amfora.
|
||||
|
||||
# When Amfora updates, defaults may change, but this file on your drive will not.
|
||||
# You can always get the latest defaults on GitHub.
|
||||
# https://github.com/makeworld-the-better-one/amfora/blob/master/default-config.toml
|
||||
|
||||
# Please also check out the Amfora Wiki for more help
|
||||
# https://github.com/makeworld-the-better-one/amfora/wiki
|
||||
# gemini://makeworld.space/amfora-wiki/
|
||||
|
||||
|
||||
|
||||
# All URL values may omit the scheme and/or port, as well as the beginning double slash
|
||||
# Valid URL examples:
|
||||
|
@ -23,7 +34,7 @@ auto_redirect = false
|
|||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||
# A space will be prepended to the URL.
|
||||
#
|
||||
# The best to define a command is using a string array.
|
||||
# The best way to define a command is using a string array.
|
||||
# Examples:
|
||||
# http = ['firefox']
|
||||
# http = ['custom-browser', '--flag', '--option=2']
|
||||
|
@ -71,6 +82,10 @@ page_max_time = 10
|
|||
# "auto" means the scrollbar only appears when the page is longer than the window.
|
||||
scrollbar = "auto"
|
||||
|
||||
# Underline non-gemini URLs
|
||||
# This is done to help color blind users
|
||||
underline = true
|
||||
|
||||
|
||||
[auth]
|
||||
# Authentication settings
|
||||
|
@ -213,19 +228,30 @@ scrollbar = "auto"
|
|||
[url-handlers]
|
||||
# Allows setting the commands to run for various URL schemes.
|
||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||
# ftp = 'filezilla'
|
||||
# You can set any scheme to "off" or "" to disable handling it, or
|
||||
# ftp = ['filezilla']
|
||||
# You can set any scheme to 'off' or '' to disable handling it, or
|
||||
# just leave the key unset.
|
||||
#
|
||||
# DO NOT use this for setting the HTTP command.
|
||||
# Use the http setting in the "a-general" section above.
|
||||
#
|
||||
# NOTE: These settings are overrided by the ones in the proxies section.
|
||||
#
|
||||
# The best way to define a command is using a string array.
|
||||
# Examples:
|
||||
# magnet = ['transmission']
|
||||
# foo = ['custom-browser', '--flag', '--option=2']
|
||||
# tel = ['/path/with spaces/in it/telephone']
|
||||
#
|
||||
# Note the use of single quotes, so that backslashes will not be escaped.
|
||||
# Using just a string will also work, but it is deprecated, and will degrade if
|
||||
# you use paths with spaces.
|
||||
|
||||
# This is a special key that defines the handler for all URL schemes for which
|
||||
# no handler is defined.
|
||||
other = 'off'
|
||||
# It uses the special value 'default', which will try and use the default
|
||||
# application on your computer for opening this kind of URI.
|
||||
other = 'default'
|
||||
|
||||
|
||||
# [[mediatype-handlers]] section
|
||||
|
@ -350,6 +376,8 @@ entries_per_page = 20
|
|||
# Colors can be set using a W3C color name, or a hex value such as "#ffffff".
|
||||
# Setting a background to "default" keeps the terminal default
|
||||
# If your terminal has transparency, set any background to "default" to keep it transparent
|
||||
# The key "bg" is already set to "default", but this can be used on other backgrounds,
|
||||
# like for modals.
|
||||
|
||||
# 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
|
||||
|
|
|
@ -29,7 +29,7 @@ var bkmkCh = make(chan bkmkAction)
|
|||
var bkmkModalText string // The current text of the input field in the modal
|
||||
|
||||
func bkmkInit() {
|
||||
panels.AddPanel("bkmk", bkmkModal, false, false)
|
||||
panels.AddPanel(PanelBookmarks, bkmkModal, false, false)
|
||||
|
||||
m := bkmkModal
|
||||
if viper.GetBool("a-general.color") {
|
||||
|
@ -111,13 +111,13 @@ func openBkmkModal(name string, exists bool) (string, bkmkAction) {
|
|||
bkmkModalText = text
|
||||
})
|
||||
|
||||
panels.ShowPanel("bkmk")
|
||||
panels.SendToFront("bkmk")
|
||||
panels.ShowPanel(PanelBookmarks)
|
||||
panels.SendToFront(PanelBookmarks)
|
||||
App.SetFocus(bkmkModal)
|
||||
App.Draw()
|
||||
|
||||
action := <-bkmkCh
|
||||
panels.HidePanel("bkmk")
|
||||
panels.HidePanel(PanelBookmarks)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ func Init(version, commit, builtBy string) {
|
|||
}(tabs[curTab])
|
||||
})
|
||||
|
||||
panels.AddPanel("browser", browser, true, true)
|
||||
panels.AddPanel(PanelBrowser, browser, true, true)
|
||||
|
||||
helpInit()
|
||||
|
||||
|
@ -96,8 +96,6 @@ func Init(version, commit, builtBy string) {
|
|||
layout.AddItem(bottomBar, 1, 1, false)
|
||||
|
||||
if viper.GetBool("a-general.color") {
|
||||
layout.SetBackgroundColor(config.GetColor("bg"))
|
||||
|
||||
bottomBar.SetBackgroundColor(config.GetColor("bottombar_bg"))
|
||||
bottomBar.SetLabelColor(config.GetColor("bottombar_label"))
|
||||
bottomBar.SetFieldBackgroundColor(config.GetColor("bottombar_bg"))
|
||||
|
@ -106,7 +104,7 @@ func Init(version, commit, builtBy string) {
|
|||
browser.SetTabBackgroundColor(config.GetColor("bg"))
|
||||
browser.SetTabBackgroundColorFocused(config.GetColor("tab_num"))
|
||||
browser.SetTabTextColor(config.GetColor("tab_num"))
|
||||
browser.SetTabTextColorFocused(config.GetTextColor("bg", "tab_num"))
|
||||
browser.SetTabTextColorFocused(config.GetColor("ColorBg"))
|
||||
browser.SetTabSwitcherDivider(
|
||||
"",
|
||||
fmt.Sprintf("[%s:%s]|[-]", config.GetColorString("tab_divider"), config.GetColorString("bg")),
|
||||
|
@ -189,7 +187,6 @@ func Init(version, commit, builtBy string) {
|
|||
if i <= len(tabs[tab].page.Links) && i > 0 {
|
||||
// Open new tab and load link
|
||||
oldTab := tab
|
||||
NewTab()
|
||||
// Resolve and follow link manually
|
||||
prevParsed, _ := url.Parse(tabs[oldTab].page.URL)
|
||||
nextParsed, err := url.Parse(tabs[oldTab].page.Links[i-1])
|
||||
|
@ -198,7 +195,7 @@ func Init(version, commit, builtBy string) {
|
|||
reset()
|
||||
return
|
||||
}
|
||||
URL(prevParsed.ResolveReference(nextParsed).String())
|
||||
NewTabWithURL(prevParsed.ResolveReference(nextParsed).String())
|
||||
return
|
||||
}
|
||||
} else {
|
||||
|
@ -279,9 +276,16 @@ func Init(version, commit, builtBy string) {
|
|||
// It's focused on a modal right now, nothing should interrupt
|
||||
return event
|
||||
}
|
||||
_, ok = App.GetFocus().(*cview.Table)
|
||||
if ok {
|
||||
frontPanelName, _ := panels.GetFrontPanel()
|
||||
if frontPanelName == PanelHelp {
|
||||
// It's focused on help right now
|
||||
if config.TranslateKeyEvent(event) == config.CmdQuit {
|
||||
// Allow quit key to work, but nothing else
|
||||
Stop()
|
||||
return nil
|
||||
}
|
||||
// Pass everything else directly, inhibiting other keybindings
|
||||
// like for editing the URL
|
||||
return event
|
||||
}
|
||||
|
||||
|
@ -330,8 +334,7 @@ func Init(version, commit, builtBy string) {
|
|||
Error("URL Error", err.Error())
|
||||
return nil
|
||||
}
|
||||
NewTab()
|
||||
URL(next)
|
||||
NewTabWithURL(next)
|
||||
} else {
|
||||
NewTab()
|
||||
}
|
||||
|
@ -379,6 +382,17 @@ func Stop() {
|
|||
// NewTab opens a new tab and switches to it, displaying the
|
||||
// the default empty content because there's no URL.
|
||||
func NewTab() {
|
||||
NewTabWithURL("about:newtab")
|
||||
|
||||
bottomBar.SetLabel("")
|
||||
bottomBar.SetText("")
|
||||
tabs[NumTabs()-1].saveBottomBar()
|
||||
|
||||
}
|
||||
|
||||
// NewTabWithURL opens a new tab and switches to it, displaying the
|
||||
// the URL provided.
|
||||
func NewTabWithURL(url string) {
|
||||
// Create TextView and change curTab
|
||||
// Set the TextView options, and the changed func to App.Draw()
|
||||
// SetDoneFunc to do link highlighting
|
||||
|
@ -395,8 +409,16 @@ func NewTab() {
|
|||
curTab = NumTabs()
|
||||
|
||||
tabs = append(tabs, makeNewTab())
|
||||
temp := newTabPage // Copy
|
||||
setPage(tabs[curTab], &temp)
|
||||
|
||||
var interstitial string
|
||||
if !strings.HasPrefix(url, "about:") {
|
||||
interstitial = "Loading " + url + "..."
|
||||
}
|
||||
|
||||
setPage(tabs[curTab], renderPageFromString(interstitial))
|
||||
|
||||
// Regardless of the starting URL, about:newtab will
|
||||
// be the history root.
|
||||
tabs[curTab].addToHistory("about:newtab")
|
||||
tabs[curTab].history.pos = 0 // Manually set as first page
|
||||
|
||||
|
@ -408,9 +430,7 @@ func NewTab() {
|
|||
browser.SetCurrentTab(strconv.Itoa(curTab))
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
|
||||
bottomBar.SetLabel("")
|
||||
bottomBar.SetText("")
|
||||
tabs[curTab].saveBottomBar()
|
||||
URL(url)
|
||||
|
||||
// Draw just in case
|
||||
App.Draw()
|
||||
|
@ -531,11 +551,11 @@ func URL(u string) {
|
|||
|
||||
func RenderFromString(str string) {
|
||||
t := tabs[curTab]
|
||||
page, _ := renderPageFromString(str)
|
||||
page := renderPageFromString(str)
|
||||
setPage(t, page)
|
||||
}
|
||||
|
||||
func renderPageFromString(str string) (*structs.Page, bool) {
|
||||
func renderPageFromString(str string) *structs.Page {
|
||||
rendered, links := renderer.RenderGemini(str, textWidth(), false)
|
||||
page := &structs.Page{
|
||||
Mediatype: structs.TextGemini,
|
||||
|
@ -545,7 +565,7 @@ func renderPageFromString(str string) (*structs.Page, bool) {
|
|||
TermWidth: termW,
|
||||
}
|
||||
|
||||
return page, true
|
||||
return page
|
||||
}
|
||||
|
||||
func NumTabs() int {
|
||||
|
|
|
@ -33,8 +33,8 @@ var dlChoiceCh = make(chan string)
|
|||
var dlModal = cview.NewModal()
|
||||
|
||||
func dlInit() {
|
||||
panels.AddPanel("dl", dlModal, false, false)
|
||||
panels.AddPanel("dlChoice", dlChoiceModal, false, false)
|
||||
panels.AddPanel(PanelDownload, dlModal, false, false)
|
||||
panels.AddPanel(PanelDownloadChoiceModal, dlChoiceModal, false, false)
|
||||
|
||||
dlm := dlModal
|
||||
chm := dlChoiceModal
|
||||
|
@ -96,7 +96,7 @@ func dlInit() {
|
|||
frame.SetTitle(" Download ")
|
||||
dlm.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
if buttonLabel == "Ok" {
|
||||
panels.HidePanel("dl")
|
||||
panels.HidePanel(PanelDownload)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
}
|
||||
|
@ -141,29 +141,29 @@ func dlChoice(text, u string, resp *gemini.Response) {
|
|||
choice = "Open"
|
||||
} else {
|
||||
dlChoiceModal.SetText(text)
|
||||
panels.ShowPanel("dlChoice")
|
||||
panels.SendToFront("dlChoice")
|
||||
panels.ShowPanel(PanelDownloadChoiceModal)
|
||||
panels.SendToFront(PanelDownloadChoiceModal)
|
||||
App.SetFocus(dlChoiceModal)
|
||||
App.Draw()
|
||||
choice = <-dlChoiceCh
|
||||
}
|
||||
|
||||
if choice == "Download" {
|
||||
panels.HidePanel("dlChoice")
|
||||
panels.HidePanel(PanelDownloadChoiceModal)
|
||||
App.Draw()
|
||||
downloadURL(config.DownloadsDir, u, resp)
|
||||
resp.Body.Close() // Only close when the file is downloaded
|
||||
return
|
||||
}
|
||||
if choice == "Open" {
|
||||
panels.HidePanel("dlChoice")
|
||||
panels.HidePanel(PanelDownloadChoiceModal)
|
||||
App.Draw()
|
||||
open(u, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// They chose the "Cancel" button
|
||||
panels.HidePanel("dlChoice")
|
||||
panels.HidePanel(PanelDownloadChoiceModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ func open(u string, resp *gemini.Response) {
|
|||
return
|
||||
}
|
||||
|
||||
panels.HidePanel("dl")
|
||||
panels.HidePanel(PanelDownload)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
|
||||
|
@ -267,15 +267,15 @@ func downloadURL(dir, u string, resp *gemini.Response) string {
|
|||
// Display
|
||||
dlModal.ClearButtons()
|
||||
dlModal.AddButtons([]string{"Downloading..."})
|
||||
panels.ShowPanel("dl")
|
||||
panels.SendToFront("dl")
|
||||
panels.ShowPanel(PanelDownload)
|
||||
panels.SendToFront(PanelDownload)
|
||||
App.SetFocus(dlModal)
|
||||
App.Draw()
|
||||
|
||||
_, err = io.Copy(io.MultiWriter(f, bar), resp.Body)
|
||||
done = true
|
||||
if err != nil {
|
||||
panels.HidePanel("dl")
|
||||
panels.HidePanel(PanelDownload)
|
||||
Error("Download Error", err.Error())
|
||||
f.Close()
|
||||
os.Remove(savePath) // Remove partial file
|
||||
|
|
|
@ -2,6 +2,7 @@ package display
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net"
|
||||
"net/url"
|
||||
|
@ -16,6 +17,7 @@ import (
|
|||
"github.com/makeworld-the-better-one/amfora/rr"
|
||||
"github.com/makeworld-the-better-one/amfora/structs"
|
||||
"github.com/makeworld-the-better-one/amfora/subscriptions"
|
||||
"github.com/makeworld-the-better-one/amfora/sysopen"
|
||||
"github.com/makeworld-the-better-one/amfora/webbrowser"
|
||||
"github.com/makeworld-the-better-one/go-gemini"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -46,7 +48,7 @@ func handleHTTP(u string, showInfo bool) bool {
|
|||
}
|
||||
|
||||
// Custom command
|
||||
var err error = nil
|
||||
var err error
|
||||
if len(config.HTTPCommand) > 1 {
|
||||
err = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...).Start()
|
||||
} else {
|
||||
|
@ -56,6 +58,7 @@ func handleHTTP(u string, showInfo bool) bool {
|
|||
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
||||
return false
|
||||
}
|
||||
Info("Opened with: " + config.HTTPCommand[0])
|
||||
|
||||
App.Draw()
|
||||
return true
|
||||
|
@ -68,21 +71,49 @@ func handleOther(u string) {
|
|||
parsed, _ := url.Parse(u)
|
||||
|
||||
// Search for a handler for the URL scheme
|
||||
handler := strings.TrimSpace(viper.GetString("url-handlers." + parsed.Scheme))
|
||||
handler := viper.GetStringSlice("url-handlers." + parsed.Scheme)
|
||||
if len(handler) == 0 {
|
||||
handler = strings.TrimSpace(viper.GetString("url-handlers.other"))
|
||||
}
|
||||
switch handler {
|
||||
case "", "off":
|
||||
Error("URL Error", "Opening "+parsed.Scheme+" URLs is turned off.")
|
||||
default:
|
||||
// The config has a custom command to execute for URLs
|
||||
fields := strings.Fields(handler)
|
||||
err := exec.Command(fields[0], append(fields[1:], u)...).Start()
|
||||
if err != nil {
|
||||
Error("URL Error", "Error executing custom command: "+err.Error())
|
||||
// A string and not a list of strings, use old method of parsing
|
||||
// #214
|
||||
handler = strings.Fields(viper.GetString("url-handlers." + parsed.Scheme))
|
||||
if len(handler) == 0 {
|
||||
handler = viper.GetStringSlice("url-handlers.other")
|
||||
if len(handler) == 0 {
|
||||
handler = strings.Fields(viper.GetString("url-handlers.other"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(handler) == 1 {
|
||||
// Maybe special key
|
||||
|
||||
switch strings.TrimSpace(handler[0]) {
|
||||
case "", "off":
|
||||
Error("URL Error", "Opening "+parsed.Scheme+" URLs is turned off.")
|
||||
return
|
||||
case "default":
|
||||
_, err := sysopen.Open(u)
|
||||
if err != nil {
|
||||
Error("Application Error", err.Error())
|
||||
return
|
||||
}
|
||||
Info("Opened in default application")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Custom application command
|
||||
|
||||
var err error
|
||||
if len(handler) > 1 {
|
||||
err = exec.Command(handler[0], append(handler[1:], u)...).Start()
|
||||
} else {
|
||||
err = exec.Command(handler[0], u).Start()
|
||||
}
|
||||
if err != nil {
|
||||
Error("URL Error", "Error executing custom command: "+err.Error())
|
||||
}
|
||||
Info("Opened with: " + handler[0])
|
||||
App.Draw()
|
||||
}
|
||||
|
||||
|
@ -351,12 +382,14 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
// Could be a non 20 status code, or a different kind of document
|
||||
|
||||
// Handle each status code
|
||||
switch res.Status {
|
||||
// Except 20, that's handled after the switch
|
||||
status := gemini.CleanStatus(res.Status)
|
||||
switch status {
|
||||
case 10, 11:
|
||||
var userInput string
|
||||
var ok bool
|
||||
|
||||
if res.Status == 10 {
|
||||
if status == 10 {
|
||||
// Regular input
|
||||
userInput, ok = Input(res.Meta, false)
|
||||
} else {
|
||||
|
@ -380,9 +413,10 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
return ret("", false)
|
||||
}
|
||||
redir := parsed.ResolveReference(parsedMeta).String()
|
||||
justAddsSlash := (redir == u+"/")
|
||||
// Prompt before redirecting to non-Gemini protocol
|
||||
redirect := false
|
||||
if !strings.HasPrefix(redir, "gemini") {
|
||||
if !justAddsSlash && !strings.HasPrefix(redir, "gemini") {
|
||||
if YesNo("Follow redirect to non-Gemini URL?\n" + redir) {
|
||||
redirect = true
|
||||
} else {
|
||||
|
@ -390,9 +424,9 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
}
|
||||
}
|
||||
// Prompt before redirecting
|
||||
autoRedirect := viper.GetBool("a-general.auto_redirect")
|
||||
autoRedirect := justAddsSlash || viper.GetBool("a-general.auto_redirect")
|
||||
if redirect || (autoRedirect && numRedirects < 5) || YesNo("Follow redirect?\n"+redir) {
|
||||
if res.Status == gemini.StatusRedirectPermanent {
|
||||
if status == gemini.StatusRedirectPermanent {
|
||||
go cache.AddRedir(u, redir)
|
||||
}
|
||||
return ret(handleURL(t, redir, numRedirects+1))
|
||||
|
@ -437,6 +471,12 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||
case 62:
|
||||
Error("Certificate Not Valid", escapeMeta(res.Meta))
|
||||
return ret("", false)
|
||||
default:
|
||||
if !gemini.StatusInRange(status) {
|
||||
// Status code not in a valid range
|
||||
Error("Status Code Error", fmt.Sprintf("Out of range status code: %d", status))
|
||||
return ret("", false)
|
||||
}
|
||||
}
|
||||
|
||||
// Status code 20, but not a document that can be displayed
|
||||
|
|
|
@ -59,8 +59,8 @@ var helpTable = cview.NewTextView()
|
|||
// Help displays the help and keybindings.
|
||||
func Help() {
|
||||
helpTable.ScrollToBeginning()
|
||||
panels.ShowPanel("help")
|
||||
panels.SendToFront("help")
|
||||
panels.ShowPanel(PanelHelp)
|
||||
panels.SendToFront(PanelHelp)
|
||||
App.SetFocus(helpTable)
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ func helpInit() {
|
|||
helpTable.SetPadding(0, 0, 1, 1)
|
||||
helpTable.SetDoneFunc(func(key tcell.Key) {
|
||||
if key == tcell.KeyEsc || key == tcell.KeyEnter {
|
||||
panels.HidePanel("help")
|
||||
panels.HidePanel(PanelHelp)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
}
|
||||
|
@ -132,5 +132,5 @@ func helpInit() {
|
|||
|
||||
w.Flush()
|
||||
|
||||
panels.AddPanel("help", helpTable, true, false)
|
||||
panels.AddPanel(PanelHelp, helpTable, true, false)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
var infoModal = cview.NewModal()
|
||||
|
||||
var errorModal = cview.NewModal()
|
||||
var errorModalDone = make(chan struct{})
|
||||
|
||||
var inputModal = cview.NewModal()
|
||||
var inputCh = make(chan string)
|
||||
|
@ -35,10 +36,10 @@ func modalInit() {
|
|||
|
||||
yesNoModal.AddButtons([]string{"Yes", "No"})
|
||||
|
||||
panels.AddPanel("info", infoModal, false, false)
|
||||
panels.AddPanel("error", errorModal, false, false)
|
||||
panels.AddPanel("input", inputModal, false, false)
|
||||
panels.AddPanel("yesno", yesNoModal, false, false)
|
||||
panels.AddPanel(PanelInfoModal, infoModal, false, false)
|
||||
panels.AddPanel(PanelErrorModal, errorModal, false, false)
|
||||
panels.AddPanel(PanelInputModal, inputModal, false, false)
|
||||
panels.AddPanel(PanelYesNoModal, yesNoModal, false, false)
|
||||
|
||||
// Color setup
|
||||
if viper.GetBool("a-general.color") {
|
||||
|
@ -141,7 +142,7 @@ func modalInit() {
|
|||
frame.SetTitleAlign(cview.AlignCenter)
|
||||
frame.SetTitle(" Info ")
|
||||
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
panels.HidePanel("info")
|
||||
panels.HidePanel(PanelInfoModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
})
|
||||
|
@ -149,9 +150,10 @@ func modalInit() {
|
|||
errorModal.SetBorder(true)
|
||||
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
|
||||
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
|
||||
panels.HidePanel("error")
|
||||
panels.HidePanel(PanelErrorModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
errorModalDone <- struct{}{}
|
||||
})
|
||||
|
||||
inputModal.SetBorder(true)
|
||||
|
@ -196,17 +198,19 @@ func Error(title, text string) {
|
|||
|
||||
errorModal.GetFrame().SetTitle(title)
|
||||
errorModal.SetText(text)
|
||||
panels.ShowPanel("error")
|
||||
panels.SendToFront("error")
|
||||
panels.ShowPanel(PanelErrorModal)
|
||||
panels.SendToFront(PanelErrorModal)
|
||||
App.SetFocus(errorModal)
|
||||
App.Draw()
|
||||
|
||||
<-errorModalDone
|
||||
}
|
||||
|
||||
// Info displays some info on the screen in a modal.
|
||||
func Info(s string) {
|
||||
infoModal.SetText(s)
|
||||
panels.ShowPanel("info")
|
||||
panels.SendToFront("info")
|
||||
panels.ShowPanel(PanelInfoModal)
|
||||
panels.SendToFront(PanelInfoModal)
|
||||
App.SetFocus(infoModal)
|
||||
App.Draw()
|
||||
}
|
||||
|
@ -236,14 +240,14 @@ func Input(prompt string, sensitive bool) (string, bool) {
|
|||
}
|
||||
|
||||
inputModal.SetText(prompt + " ")
|
||||
panels.ShowPanel("input")
|
||||
panels.SendToFront("input")
|
||||
panels.ShowPanel(PanelInputModal)
|
||||
panels.SendToFront(PanelInputModal)
|
||||
App.SetFocus(inputModal)
|
||||
App.Draw()
|
||||
|
||||
resp := <-inputCh
|
||||
|
||||
panels.HidePanel("input")
|
||||
panels.HidePanel(PanelInputModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
|
||||
|
@ -272,13 +276,13 @@ func YesNo(prompt string) bool {
|
|||
}
|
||||
yesNoModal.GetFrame().SetTitle("")
|
||||
yesNoModal.SetText(prompt)
|
||||
panels.ShowPanel("yesno")
|
||||
panels.SendToFront("yesno")
|
||||
panels.ShowPanel(PanelYesNoModal)
|
||||
panels.SendToFront(PanelYesNoModal)
|
||||
App.SetFocus(yesNoModal)
|
||||
App.Draw()
|
||||
|
||||
resp := <-yesNoCh
|
||||
panels.HidePanel("yesno")
|
||||
panels.HidePanel(PanelYesNoModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
return resp
|
||||
|
@ -305,18 +309,18 @@ func Tofu(host string, expiry time.Time) bool {
|
|||
frame.SetTitle(" TOFU ")
|
||||
m.SetText(
|
||||
//nolint:lll
|
||||
fmt.Sprintf("%s's certificate has changed, possibly indicating an security issue. The certificate would have expired %s. Are you sure you want to continue? ",
|
||||
fmt.Sprintf("%s's certificate has changed, possibly indicating a security issue. The certificate would have expired %s. Are you sure you want to continue? ",
|
||||
host,
|
||||
humanize.Time(expiry),
|
||||
),
|
||||
)
|
||||
panels.ShowPanel("yesno")
|
||||
panels.SendToFront("yesno")
|
||||
panels.ShowPanel(PanelYesNoModal)
|
||||
panels.SendToFront(PanelYesNoModal)
|
||||
App.SetFocus(yesNoModal)
|
||||
App.Draw()
|
||||
|
||||
resp := <-yesNoCh
|
||||
panels.HidePanel("yesno")
|
||||
panels.HidePanel(PanelYesNoModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
return resp
|
||||
|
|
14
display/panels.go
Normal file
14
display/panels.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package display
|
||||
|
||||
const (
|
||||
PanelBrowser = "browser"
|
||||
PanelBookmarks = "bkmk"
|
||||
PanelDownload = "dl"
|
||||
PanelDownloadChoiceModal = "dlChoice"
|
||||
PanelHelp = "help"
|
||||
|
||||
PanelYesNoModal = "yesno"
|
||||
PanelInfoModal = "info"
|
||||
PanelErrorModal = "error"
|
||||
PanelInputModal = "input"
|
||||
)
|
|
@ -134,6 +134,9 @@ func goURL(t *tab, u string) {
|
|||
final, displayed := handleURL(t, u, 0)
|
||||
if displayed {
|
||||
t.addToHistory(final)
|
||||
} else if t.page.URL == "" {
|
||||
// The tab is showing interstitial or no content. Let's go to about:newtab.
|
||||
handleAbout(t, "about:newtab")
|
||||
}
|
||||
if t == tabs[curTab] {
|
||||
// Display the bottomBar state that handleURL set
|
||||
|
|
|
@ -260,13 +260,13 @@ func openSubscriptionModal(validFeed, subscribed bool) bool {
|
|||
}
|
||||
}
|
||||
|
||||
panels.ShowPanel("yesno")
|
||||
panels.SendToFront("yesno")
|
||||
panels.ShowPanel(PanelYesNoModal)
|
||||
panels.SendToFront(PanelYesNoModal)
|
||||
App.SetFocus(yesNoModal)
|
||||
App.Draw()
|
||||
|
||||
resp := <-yesNoCh
|
||||
panels.HidePanel("yesno")
|
||||
panels.HidePanel(PanelYesNoModal)
|
||||
App.SetFocus(tabs[curTab].view)
|
||||
App.Draw()
|
||||
return resp
|
||||
|
|
|
@ -27,4 +27,6 @@ Thank you to the following contributors, who have helped make Amfora great. FOSS
|
|||
* Anas Mohamed (@amohamed11)
|
||||
* David Jimenez (@dvejmz)
|
||||
* Michael McDonagh (@m-mcdonagh)
|
||||
* mooff (@awfulcooking)
|
||||
* Josias (@justjosias)
|
||||
`)
|
||||
|
|
7
go.mod
7
go.mod
|
@ -1,18 +1,19 @@
|
|||
module github.com/makeworld-the-better-one/amfora
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/dustin/go-humanize v1.0.0
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gdamore/tcell/v2 v2.3.3
|
||||
github.com/google/go-cmp v0.5.0 // indirect
|
||||
github.com/makeworld-the-better-one/go-gemini v0.11.0
|
||||
github.com/makeworld-the-better-one/go-gemini v0.12.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mitchellh/mapstructure v1.3.1 // indirect
|
||||
github.com/mmcdole/gofeed v1.1.2
|
||||
github.com/muesli/termenv v0.9.0
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
github.com/rkoesters/xdg v0.0.0-20181125232953-edd15b846f9b
|
||||
github.com/schollz/progressbar/v3 v3.8.0
|
||||
|
|
12
go.sum
12
go.sum
|
@ -12,8 +12,8 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
|
|||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
code.rocketnine.space/tslocum/cbind v0.1.5 h1:i6NkeLLNPNMS4NWNi3302Ay3zSU6MrqOT+yJskiodxE=
|
||||
code.rocketnine.space/tslocum/cbind v0.1.5/go.mod h1:LtfqJTzM7qhg88nAvNhx+VnTjZ0SXBJtxBObbfBWo/M=
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283 h1:5KBGXdQdfV09eYXOZuFTxqDujndqtRraXj+lyFcxlPk=
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210525194531-92dca67ac283/go.mod h1:KBRxzIsj8bfgFpnMpkGVoxsrPUvnQsRnX29XJ2yzB6M=
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc h1:nAcBp7ZCWHpa8fHpynCbULDTAZgPQv28+Z+QnhnFG7E=
|
||||
code.rocketnine.space/tslocum/cview v1.5.6-0.20210530175404-7e8817f20bdc/go.mod h1:KBRxzIsj8bfgFpnMpkGVoxsrPUvnQsRnX29XJ2yzB6M=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
|
@ -138,11 +138,13 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
|||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/makeworld-the-better-one/go-gemini v0.11.0 h1:MNGiULJFvcqls9oCy40tE897hDeKvNmEK9i5kRucgQk=
|
||||
github.com/makeworld-the-better-one/go-gemini v0.11.0/go.mod h1:F+3x+R1xeYK90jMtBq+U+8Sh64r2dHleDZ/en3YgSmg=
|
||||
github.com/makeworld-the-better-one/go-gemini v0.12.1 h1:cWHvCHL31Caq3Rm9elCFFoQeyrn92Kv7KummsVxCOFg=
|
||||
github.com/makeworld-the-better-one/go-gemini v0.12.1/go.mod h1:F+3x+R1xeYK90jMtBq+U+8Sh64r2dHleDZ/en3YgSmg=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
|
@ -171,6 +173,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8=
|
||||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
|
|
|
@ -3,18 +3,42 @@ package logger
|
|||
// For debugging
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var Log *log.Logger
|
||||
var logger *log.Logger
|
||||
|
||||
func Init() error {
|
||||
f, err := os.Create("debug.log")
|
||||
if err != nil {
|
||||
return err
|
||||
func GetLogger() (*log.Logger, error) {
|
||||
if logger != nil {
|
||||
return logger, nil
|
||||
}
|
||||
Log = log.New(f, "", log.LstdFlags)
|
||||
Log.Println("Started Log")
|
||||
return nil
|
||||
|
||||
var writer io.Writer
|
||||
var err error
|
||||
|
||||
debugModeEnabled := os.Getenv("AMFORA_DEBUG") == "1"
|
||||
if debugModeEnabled {
|
||||
writer, err = os.Create("debug.log")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Suppress all logging output if debug mode is disabled
|
||||
writer = ioutil.Discard
|
||||
}
|
||||
|
||||
logger = log.New(writer, "", log.LstdFlags)
|
||||
|
||||
if !debugModeEnabled {
|
||||
// Clear all flags to skip log output formatting step to increase
|
||||
// performance somewhat if we're not logging anything
|
||||
logger.SetFlags(0)
|
||||
}
|
||||
|
||||
logger.Println("Started logger")
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
|
|
@ -159,6 +159,14 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
spacing = " "
|
||||
}
|
||||
|
||||
// Underline non-gemini links if enabled
|
||||
var linkTag string
|
||||
if viper.GetBool("a-general.underline") {
|
||||
linkTag = `[` + config.GetColorString("foreign_link") + `::u]`
|
||||
} else {
|
||||
linkTag = `[` + config.GetColorString("foreign_link") + `]`
|
||||
}
|
||||
|
||||
// Wrap and add link text
|
||||
// Wrap the link text, but add some spaces to indent the wrapped lines past the link number
|
||||
// Set the style tags
|
||||
|
@ -166,11 +174,12 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
|
||||
var wrappedLink []string
|
||||
|
||||
if viper.GetBool("a-general.color") {
|
||||
pU, err := urlPkg.Parse(url)
|
||||
if !proxied && err == nil &&
|
||||
(pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
||||
// A gemini link
|
||||
pU, err := urlPkg.Parse(url)
|
||||
if !proxied && err == nil &&
|
||||
(pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
||||
// A gemini link
|
||||
|
||||
if viper.GetBool("a-general.color") {
|
||||
// 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
|
||||
|
||||
|
@ -187,33 +196,50 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||
`["` + strconv.Itoa(num-1) + `"][` + config.GetColorString("amfora_link") + `]` +
|
||||
wrappedLink[0] + `[-][""]`
|
||||
} else {
|
||||
// Not a gemini link
|
||||
// No color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
strings.Repeat(" ", len(strconv.Itoa(num))+4)+ // +4 for spaces and brackets
|
||||
`["`+strconv.Itoa(num-1)+`"]`,
|
||||
`[""]`,
|
||||
false, // Don't indent the first line, it's the one with link number
|
||||
)
|
||||
|
||||
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-] " +
|
||||
`["` + strconv.Itoa(num-1) + `"]` +
|
||||
wrappedLink[0] + `[""]`
|
||||
}
|
||||
} else {
|
||||
// Not a gemini link
|
||||
|
||||
if viper.GetBool("a-general.color") {
|
||||
// Color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
strings.Repeat(" ", indent)+
|
||||
`["`+strconv.Itoa(num-1)+`"][`+config.GetColorString("foreign_link")+`]`,
|
||||
`[-][""]`,
|
||||
`["`+strconv.Itoa(num-1)+`"]`+linkTag,
|
||||
`[-::-][""]`,
|
||||
false, // Don't indent the first line, it's the one with link number
|
||||
)
|
||||
|
||||
wrappedLink[0] = fmt.Sprintf(`[%s::b][`, config.GetColorString("link_number")) +
|
||||
strconv.Itoa(num) + "[]" + "[-::-]" + spacing +
|
||||
`["` + strconv.Itoa(num-1) + `"][` + config.GetColorString("foreign_link") + `]` +
|
||||
wrappedLink[0] + `[-][""]`
|
||||
strconv.Itoa(num) + "[][-::-]" + spacing +
|
||||
`["` + strconv.Itoa(num-1) + `"]` + linkTag +
|
||||
wrappedLink[0] + `[-::-][""]`
|
||||
} else {
|
||||
// No color
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
strings.Repeat(" ", indent)+
|
||||
`["`+strconv.Itoa(num-1)+`"]`+linkTag,
|
||||
`[::-][""]`,
|
||||
false, // Don't indent the first line, it's the one with link number
|
||||
)
|
||||
|
||||
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-]" + spacing +
|
||||
`["` + strconv.Itoa(num-1) + `"]` + linkTag +
|
||||
wrappedLink[0] + `[::-][""]`
|
||||
}
|
||||
} else {
|
||||
// No colors allowed
|
||||
|
||||
wrappedLink = wrapLine(linkText, width,
|
||||
strings.Repeat(" ", len(strconv.Itoa(num))+4)+ // +4 for spaces and brackets
|
||||
`["`+strconv.Itoa(num-1)+`"]`,
|
||||
`[""]`,
|
||||
false, // Don't indent the first line, it's the one with link number
|
||||
)
|
||||
|
||||
wrappedLink[0] = `[::b][` + strconv.Itoa(num) + "[][::-] " +
|
||||
`["` + strconv.Itoa(num-1) + `"]` +
|
||||
wrappedLink[0] + `[""]`
|
||||
}
|
||||
|
||||
wrappedLines = append(wrappedLines, wrappedLink...)
|
||||
|
|
|
@ -251,7 +251,9 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||
return url, nil, err
|
||||
}
|
||||
|
||||
if res.Status == gemini.StatusSuccess {
|
||||
status := gemini.CleanStatus(res.Status)
|
||||
|
||||
if status == gemini.StatusSuccess {
|
||||
// No redirects
|
||||
return url, res, nil
|
||||
}
|
||||
|
@ -266,8 +268,8 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||
urls := make([]*urlPkg.URL, 0)
|
||||
|
||||
// Loop through redirects
|
||||
for (res.Status == gemini.StatusRedirectPermanent || res.Status == gemini.StatusRedirectTemporary) && i < 5 {
|
||||
redirs = append(redirs, res.Status)
|
||||
for (status == gemini.StatusRedirectPermanent || status == gemini.StatusRedirectTemporary) && i < 5 {
|
||||
redirs = append(redirs, status)
|
||||
urls = append(urls, parsed)
|
||||
|
||||
tmp, err := parsed.Parse(res.Meta)
|
||||
|
@ -302,7 +304,7 @@ func getResource(url string) (string, *gemini.Response, error) {
|
|||
if i < 5 {
|
||||
// The server stopped redirecting after <5 redirects
|
||||
|
||||
if res.Status == gemini.StatusSuccess {
|
||||
if status == gemini.StatusSuccess {
|
||||
// It ended by succeeding
|
||||
|
||||
for j := range redirs {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package sysopen
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux && !darwin && !windows && !freebsd && !netbsd && !openbsd
|
||||
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
||||
|
||||
package sysopen
|
||||
|
@ -7,5 +8,5 @@ import "fmt"
|
|||
// Open opens `path` in default system viewer, but not on this OS.
|
||||
func Open(path string) (string, error) {
|
||||
return "", fmt.Errorf("unsupported OS for default system viewer. " +
|
||||
"Set a catch-all [[mediatype-handlers]] command in the config")
|
||||
"Set a catch-all command in the config")
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux || freebsd || netbsd || openbsd
|
||||
// +build linux freebsd netbsd openbsd
|
||||
|
||||
//nolint:goerr113
|
||||
|
@ -20,7 +21,7 @@ func Open(path string) (string, error) {
|
|||
switch {
|
||||
case xorgDisplay == "" && waylandDisplay == "":
|
||||
return "", fmt.Errorf("no display server was found. " +
|
||||
"You may set a default [[mediatype-handlers]] command in the config")
|
||||
"You may set a default command in the config")
|
||||
case xdgOpenNotFoundErr == nil:
|
||||
// Use start rather than run or output in order
|
||||
// to make application run in background.
|
||||
|
@ -30,6 +31,6 @@ func Open(path string) (string, error) {
|
|||
return "Opened in default system viewer", nil
|
||||
default:
|
||||
return "", fmt.Errorf("could not determine default system viewer. " +
|
||||
"Set a catch-all [[mediatype-handlers]] command in the config")
|
||||
"Set a catch-all command in the config")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build windows && (!linux || !darwin || !freebsd || !netbsd || !openbsd)
|
||||
// +build windows
|
||||
// +build !linux !darwin !freebsd !netbsd !openbsd
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package webbrowser
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build !linux && !darwin && !windows && !freebsd && !netbsd && !openbsd
|
||||
// +build !linux,!darwin,!windows,!freebsd,!netbsd,!openbsd
|
||||
|
||||
package webbrowser
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build linux || freebsd || netbsd || openbsd
|
||||
// +build linux freebsd netbsd openbsd
|
||||
|
||||
//nolint:goerr113
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
//go:build windows && (!linux || !darwin || !freebsd || !netbsd || !openbsd)
|
||||
// +build windows
|
||||
// +build !linux !darwin !freebsd !netbsd !openbsd
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user