mirror of
https://github.com/makew0rld/amfora.git
synced 2025-01-03 14:56:27 -05:00
Merge branch 'master' into feeds
This commit is contained in:
commit
bc4c72bba9
30
.github/workflows/test.yml
vendored
Normal file
30
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
on: [push, pull_request]
|
||||||
|
name: Test
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version: ['1.13', '1.14', '1.15']
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
- name: Install make on Windows
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: choco install make
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
go test -race ./...
|
||||||
|
make
|
35
.travis.yml
35
.travis.yml
@ -1,35 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
#- "1.11" # Debian Stable golang version, fails - see below
|
|
||||||
#- "1.12" # Also fails due to progressbar Millisecond requirement
|
|
||||||
- "1.13"
|
|
||||||
- "1.14"
|
|
||||||
- "1.15"
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
- windows
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- if [ "$TRAVIS_OS_NAME" = "windows" ]; then choco install make; fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -race ./...
|
|
||||||
- make
|
|
||||||
|
|
||||||
env:
|
|
||||||
GO111MODULE=on
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $GOCACHE
|
|
||||||
- $GOPATH/pkg/mod
|
|
||||||
|
|
||||||
# TODO: GitHub Releases deploy
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email:
|
|
||||||
on_success: never
|
|
||||||
on_failure: always
|
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -5,23 +5,38 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Changed
|
||||||
|
- Updated [go-gemini](https://github.com/makeworld-the-better-one/go-gemini) to v0.9.1 to support CN-only wildcard certs
|
||||||
|
- Preformatted text is now grey by default
|
||||||
|
|
||||||
|
|
||||||
|
## [1.6.0] - 2020-11-04
|
||||||
### Added
|
### Added
|
||||||
|
- **Support client certificates** through config (#112)
|
||||||
- `ansi` config setting, to disable ANSI colors in pages (#79, #86)
|
- `ansi` config setting, to disable ANSI colors in pages (#79, #86)
|
||||||
- Edit current URL with <kbd>e</kbd> (#87)
|
- Edit current URL with <kbd>e</kbd> (#87)
|
||||||
- If `emoji_favicons` is enabled, new bookmarks will have the domain's favicon prepended (#69, #90)
|
- If `emoji_favicons` is enabled, new bookmarks will have the domain's favicon prepended (#69, #90)
|
||||||
- The `BROWSER` env var is now also checked when opening web links on Unix (#93)
|
- The `BROWSER` env var is now also checked when opening web links on Unix (#93)
|
||||||
|
- More accurate error messages based on server response code
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Disabling the `color` config setting also disables ANSI colors in pages (#79, #86)
|
- Disabling the `color` config setting also disables ANSI colors in pages (#79, #86)
|
||||||
- Updated [go-isemoji](https://github.com/makeworld-the-better-one/go-isemoji) to v1.1.0 to support Emoji 13.1 for favicons
|
- Updated [go-isemoji](https://github.com/makeworld-the-better-one/go-isemoji) to v1.1.0 to support Emoji 13.1 for favicons
|
||||||
- The web browser code doesn't check for Xorg anymore, just display variables (#93)
|
- The web browser code doesn't check for Xorg anymore, just display variables (#93)
|
||||||
- Bookmarks can be made to non-gemini URLs (#94)
|
- Bookmarks can be made to non-gemini URLs (#94)
|
||||||
|
- Remove pointless directory fallbacks (#101)
|
||||||
|
- Don't load page from cache when redirected to it (#114)
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- XDG user dir file is parsed instead of looking for XDG env vars (#97, #100)
|
- XDG user dir file is parsed instead of looking for XDG env vars (#97, #100)
|
||||||
|
- Support paths with spaces in HTTP browser config setting (#77)
|
||||||
|
- Clicking "Change" on an existing bookmark without changing the text no longer removes it (#91)
|
||||||
|
- Display HTTP Error if "Open In Portal" fails (#81)
|
||||||
|
- Support ANSI color codes again, but only in preformatted blocks (#59)
|
||||||
|
- Make the `..` command work lke it used to in v1.4.0
|
||||||
|
|
||||||
|
|
||||||
## [v1.5.0] - 2020-09-01
|
## [1.5.0] - 2020-09-01
|
||||||
### Added
|
### Added
|
||||||
- **Proxy support** - see the `[proxies]` section in the config (#66, #80)
|
- **Proxy support** - see the `[proxies]` section in the config (#66, #80)
|
||||||
- **Emoji favicons** can now be seen if `emoji_favicons` is enabled in the config (#62)
|
- **Emoji favicons** can now be seen if `emoji_favicons` is enabled in the config (#62)
|
||||||
|
23
README.md
23
README.md
@ -5,7 +5,7 @@
|
|||||||
<h6>Image modified from: amphora by Alvaro Cabrera from the Noun Project</h6>
|
<h6>Image modified from: amphora by Alvaro Cabrera from the Noun Project</h6>
|
||||||
</center>
|
</center>
|
||||||
|
|
||||||
[![travis build status](https://img.shields.io/travis/com/makeworld-the-better-one/amfora)](https://https://travis-ci.com/github/makeworld-the-better-one/amfora)
|
[![travis build status](https://img.shields.io/travis/com/makeworld-the-better-one/amfora/master?label=master)](https://travis-ci.com/github/makeworld-the-better-one/amfora)
|
||||||
[![go reportcard](https://goreportcard.com/badge/github.com/makeworld-the-better-one/amfora)](https://goreportcard.com/report/github.com/makeworld-the-better-one/amfora)
|
[![go reportcard](https://goreportcard.com/badge/github.com/makeworld-the-better-one/amfora)](https://goreportcard.com/report/github.com/makeworld-the-better-one/amfora)
|
||||||
[![license GPLv3](https://img.shields.io/github/license/makeworld-the-better-one/amfora)](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
[![license GPLv3](https://img.shields.io/github/license/makeworld-the-better-one/amfora)](https://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||||
|
|
||||||
@ -42,10 +42,10 @@ Make sure to click "Watch" > "Releases only" in the top right to get notified ab
|
|||||||
|
|
||||||
### Arch Linux
|
### Arch Linux
|
||||||
|
|
||||||
Arch Linux users can install Amfora from AUR. It has the package name `amfora`, and is maintained by @pboyd.
|
Arch Linux users can install Amfora using pacman.
|
||||||
|
|
||||||
```
|
```
|
||||||
yay -S amfora
|
sudo pacman -S amfora
|
||||||
```
|
```
|
||||||
|
|
||||||
### Homebrew
|
### Homebrew
|
||||||
@ -121,14 +121,14 @@ Features in *italics* are in the master branch, but not in the latest release.
|
|||||||
- Disabled by default, enable in config
|
- Disabled by default, enable in config
|
||||||
- [x] Proxying
|
- [x] Proxying
|
||||||
- Schemes like Gopher or HTTP can be proxied through a Gemini server
|
- Schemes like Gopher or HTTP can be proxied through a Gemini server
|
||||||
- [x] *Subscribe to RSS and Atom feeds and display them*
|
- [x] Client certificate support
|
||||||
- Subscribing to page changes, similar to how Spacewalk works, will also be supported
|
|
||||||
- [ ] Stream support
|
|
||||||
- [ ] Full client certificate UX within the client
|
- [ ] Full client certificate UX within the client
|
||||||
- Create transient and permanent certs within the client, per domain
|
- Create transient and permanent certs within the client, per domain
|
||||||
- Manage and browse them
|
- Manage and browse them
|
||||||
- Similar to [Kristall](https://github.com/MasterQ32/kristall)
|
- Similar to [Kristall](https://github.com/MasterQ32/kristall)
|
||||||
- https://lists.orbitalfox.eu/archives/gemini/2020/001400.html
|
- https://lists.orbitalfox.eu/archives/gemini/2020/001400.html
|
||||||
|
- [x] *Subscribe to RSS and Atom feeds and display them*
|
||||||
|
- Subscribing to page changes, similar to how Spacewalk works, will also be supported
|
||||||
- [ ] Stream support
|
- [ ] Stream support
|
||||||
- [ ] Table of contents for pages
|
- [ ] Table of contents for pages
|
||||||
- [ ] History browser
|
- [ ] History browser
|
||||||
@ -138,10 +138,19 @@ The config file is written in the intuitive [TOML](https://github.com/toml-lang/
|
|||||||
|
|
||||||
On Windows, the file is in `%APPDATA%\amfora\config.toml`, which usually expands to `C:\Users\<username>\AppData\Roaming\amfora\config.toml`.
|
On Windows, the file is in `%APPDATA%\amfora\config.toml`, which usually expands to `C:\Users\<username>\AppData\Roaming\amfora\config.toml`.
|
||||||
|
|
||||||
|
## Client Certificates
|
||||||
|
|
||||||
|
Amfora has early support for client certs. Eventually Amfora will be able to generate them itself, but for you can do it by using OpenSSL:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
openssl req -new -subj "/CN=username" -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -days 1825 -nodes -out cert.pem -keyout key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create a certificate and key file, that can be renamed and moved as you like. See the configuration section above for how to edit your config file to tell Amfora about them.
|
||||||
|
|
||||||
## Known Bugs
|
## Known Bugs
|
||||||
|
|
||||||
- Pasting on Windows is truncated, the full paste content won't be added. ([#43](https://github.com/makeworld-the-better-one/amfora/issues/43))
|
- Pasting on Windows is truncated, the full paste content won't be added. ([#43](https://github.com/makeworld-the-better-one/amfora/issues/43))
|
||||||
- ANSI codes aren't displaying properly ([#59](https://github.com/makeworld-the-better-one/amfora/issues/59))
|
|
||||||
|
|
||||||
You can also check out [all the issues with the bug label](https://github.com/makeworld-the-better-one/amfora/issues?q=is%3Aopen+is%3Aissue+label%3Abug).
|
You can also check out [all the issues with the bug label](https://github.com/makeworld-the-better-one/amfora/issues?q=is%3Aopen+is%3Aissue+label%3Abug).
|
||||||
|
|
||||||
|
13
THANKS.md
Normal file
13
THANKS.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# THANKS
|
||||||
|
|
||||||
|
Thank you to the following contributors, who have helped make Amfora great. FOSS projects are a community effort, and we would be worse off without you.
|
||||||
|
|
||||||
|
- Sotiris Papatheodorou (@sotpapathe)
|
||||||
|
- Chloe Kudryavtsev (@CosmicToast)
|
||||||
|
- Adrian Hesketh (@a-h)
|
||||||
|
- Jansen Price (@sumpygump)
|
||||||
|
- Alex Wennerberg (@alexwennerberg)
|
||||||
|
- Timur Ismagilov (@bouncepaw)
|
||||||
|
- Matt Caroll (@ohiolab)
|
||||||
|
- Patryk Niedźwiedziński (@pniedzwiedzinski)
|
||||||
|
- Trevor Slocum (@tsclocum)
|
@ -10,7 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version = "1.5.0"
|
version = "v1.6.0"
|
||||||
commit = "unknown"
|
commit = "unknown"
|
||||||
builtBy = "unknown"
|
builtBy = "unknown"
|
||||||
)
|
)
|
||||||
|
@ -2,23 +2,74 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var certCache = make(map[string][][]byte)
|
||||||
|
|
||||||
|
func clientCert(host string) ([]byte, []byte) {
|
||||||
|
if cert := certCache[host]; cert != nil {
|
||||||
|
return cert[0], cert[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expand paths starting with ~/
|
||||||
|
certPath, err := homedir.Expand(viper.GetString("auth.certs." + host))
|
||||||
|
if err != nil {
|
||||||
|
certPath = viper.GetString("auth.certs." + host)
|
||||||
|
}
|
||||||
|
keyPath, err := homedir.Expand(viper.GetString("auth.keys." + host))
|
||||||
|
if err != nil {
|
||||||
|
keyPath = viper.GetString("auth.keys." + host)
|
||||||
|
}
|
||||||
|
if certPath == "" && keyPath == "" {
|
||||||
|
certCache[host] = [][]byte{nil, nil}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := ioutil.ReadFile(certPath)
|
||||||
|
if err != nil {
|
||||||
|
certCache[host] = [][]byte{nil, nil}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
key, err := ioutil.ReadFile(keyPath)
|
||||||
|
if err != nil {
|
||||||
|
certCache[host] = [][]byte{nil, nil}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
certCache[host] = [][]byte{cert, key}
|
||||||
|
return cert, key
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasClientCert returns whether or not a client certificate exists for a host.
|
||||||
|
func HasClientCert(host string) bool {
|
||||||
|
cert, _ := clientCert(host)
|
||||||
|
return cert != nil
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch returns response data and an error.
|
// Fetch returns response data and an error.
|
||||||
// The error text is human friendly and should be displayed.
|
// The error text is human friendly and should be displayed.
|
||||||
func Fetch(u string) (*gemini.Response, error) {
|
func Fetch(u string) (*gemini.Response, error) {
|
||||||
|
parsed, _ := url.Parse(u)
|
||||||
|
cert, key := clientCert(parsed.Host)
|
||||||
|
|
||||||
res, err := gemini.Fetch(u)
|
var res *gemini.Response
|
||||||
|
var err error
|
||||||
|
if cert != nil {
|
||||||
|
res, err = gemini.FetchWithCert(u, cert, key)
|
||||||
|
} else {
|
||||||
|
res, err = gemini.Fetch(u)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, _ := url.Parse(u)
|
|
||||||
|
|
||||||
ok := handleTofu(parsed.Hostname(), parsed.Port(), res.Cert)
|
ok := handleTofu(parsed.Hostname(), parsed.Port(), res.Cert)
|
||||||
if !ok {
|
if !ok {
|
||||||
return res, ErrTofu
|
return res, ErrTofu
|
||||||
@ -29,7 +80,16 @@ func Fetch(u string) (*gemini.Response, error) {
|
|||||||
|
|
||||||
// FetchWithProxy is the same as Fetch, but uses a proxy.
|
// FetchWithProxy is the same as Fetch, but uses a proxy.
|
||||||
func FetchWithProxy(proxyHostname, proxyPort, u string) (*gemini.Response, error) {
|
func FetchWithProxy(proxyHostname, proxyPort, u string) (*gemini.Response, error) {
|
||||||
res, err := gemini.FetchWithHost(net.JoinHostPort(proxyHostname, proxyPort), u)
|
parsed, _ := url.Parse(u)
|
||||||
|
cert, key := clientCert(parsed.Host)
|
||||||
|
|
||||||
|
var res *gemini.Response
|
||||||
|
var err error
|
||||||
|
if cert != nil {
|
||||||
|
res, err = gemini.FetchWithHostAndCert(net.JoinHostPort(proxyHostname, proxyPort), u, cert, key)
|
||||||
|
} else {
|
||||||
|
res, err = gemini.FetchWithHost(net.JoinHostPort(proxyHostname, proxyPort), u)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Package config initializes all files required for Amfora, even those used by
|
// Package config initializes all files required for Amfora, even those used by
|
||||||
// other packages. It also reads in the config file and initializes a Viper and
|
// other packages. It also reads in the config file and initializes a Viper and
|
||||||
// the theme
|
// the theme
|
||||||
|
//nolint:golint,goerr113
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -40,12 +41,13 @@ var bkmkPath string
|
|||||||
var DownloadsDir string
|
var DownloadsDir string
|
||||||
|
|
||||||
// Feeds
|
// Feeds
|
||||||
|
|
||||||
var FeedJSON io.ReadCloser
|
var FeedJSON io.ReadCloser
|
||||||
var feedDir string
|
var feedDir string
|
||||||
var FeedPath string
|
var FeedPath string
|
||||||
|
|
||||||
//nolint:golint,goerr113
|
// Command for opening HTTP(S) URLs in the browser, from "a-general.http" in config.
|
||||||
|
var HTTPCommand []string
|
||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
|
|
||||||
// *** Set paths ***
|
// *** Set paths ***
|
||||||
@ -69,13 +71,8 @@ func Init() error {
|
|||||||
configDir = amforaAppData
|
configDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
// Unix / POSIX system
|
// Unix / POSIX system
|
||||||
if basedir.ConfigHome == "" {
|
|
||||||
// Default to ~/.config/amfora
|
|
||||||
configDir = filepath.Join(home, ".config", "amfora")
|
|
||||||
} else {
|
|
||||||
configDir = filepath.Join(basedir.ConfigHome, "amfora")
|
configDir = filepath.Join(basedir.ConfigHome, "amfora")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
configPath = filepath.Join(configDir, "config.toml")
|
configPath = filepath.Join(configDir, "config.toml")
|
||||||
|
|
||||||
// Search for a custom new tab
|
// Search for a custom new tab
|
||||||
@ -91,13 +88,8 @@ func Init() error {
|
|||||||
tofuDBDir = amforaAppData
|
tofuDBDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
// XDG cache dir on POSIX systems
|
// XDG cache dir on POSIX systems
|
||||||
if basedir.CacheHome == "" {
|
|
||||||
// Default to ~/.cache/amfora
|
|
||||||
tofuDBDir = filepath.Join(home, ".cache", "amfora")
|
|
||||||
} else {
|
|
||||||
tofuDBDir = filepath.Join(basedir.CacheHome, "amfora")
|
tofuDBDir = filepath.Join(basedir.CacheHome, "amfora")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
|
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
|
||||||
|
|
||||||
// Store bookmarks dir and path
|
// Store bookmarks dir and path
|
||||||
@ -106,13 +98,8 @@ func Init() error {
|
|||||||
bkmkDir = amforaAppData
|
bkmkDir = amforaAppData
|
||||||
} else {
|
} else {
|
||||||
// XDG data dir on POSIX systems
|
// XDG data dir on POSIX systems
|
||||||
if basedir.DataHome == "" {
|
|
||||||
// Default to ~/.local/share/amfora
|
|
||||||
bkmkDir = filepath.Join(home, ".local", "share", "amfora")
|
|
||||||
} else {
|
|
||||||
bkmkDir = filepath.Join(basedir.DataHome, "amfora")
|
bkmkDir = filepath.Join(basedir.DataHome, "amfora")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
bkmkPath = filepath.Join(bkmkDir, "bookmarks.toml")
|
bkmkPath = filepath.Join(bkmkDir, "bookmarks.toml")
|
||||||
|
|
||||||
// Feeds dir and path
|
// Feeds dir and path
|
||||||
@ -282,5 +269,14 @@ func Init() error {
|
|||||||
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
|
cview.Styles.PrimitiveBackgroundColor = GetColor("bg")
|
||||||
} // Otherwise it's black by default
|
} // Otherwise it's black by default
|
||||||
|
|
||||||
|
// Parse HTTP command
|
||||||
|
HTTPCommand = viper.GetStringSlice("a-general.http")
|
||||||
|
if len(HTTPCommand) == 0 {
|
||||||
|
// Not a string array, interpret as a string instead
|
||||||
|
// Split on spaces to maintain compatibility with old versions
|
||||||
|
// The new better way to is to just define a string array in config
|
||||||
|
HTTPCommand = strings.Fields(viper.GetString("a-general.http"))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,20 @@ home = "gemini://gemini.circumlunar.space"
|
|||||||
# If set to false, a prompt will be shown before following redirects.
|
# If set to false, a prompt will be shown before following redirects.
|
||||||
auto_redirect = false
|
auto_redirect = false
|
||||||
|
|
||||||
# What command to run to open a HTTP(S) URL. Set to "default" to try to guess the browser,
|
# What command to run to open a HTTP(S) URL.
|
||||||
# or set to "off" to not open HTTP(S) URLs.
|
# Set to "default" to try to guess the browser, or set to "off" to not open HTTP(S) URLs.
|
||||||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||||
# A space will be prepended if necessary.
|
# A space will be prepended to the URL.
|
||||||
|
#
|
||||||
|
# The best to define a command is using a string array.
|
||||||
|
# Examples:
|
||||||
|
# http = ["firefox"]
|
||||||
|
# http = ["custom-browser", "--flag", "--option=2"]
|
||||||
|
# http = ["/path/with spaces/in it/firefox"]
|
||||||
|
#
|
||||||
|
# Using just a string will also work, but it is deprecated,
|
||||||
|
# and will degrade if you use paths with spaces.
|
||||||
|
|
||||||
http = "default"
|
http = "default"
|
||||||
|
|
||||||
# Any URL that will accept a query string can be put here
|
# Any URL that will accept a query string can be put here
|
||||||
@ -33,7 +43,7 @@ search = "gemini://gus.guru/search"
|
|||||||
# Whether colors will be used in the terminal
|
# Whether colors will be used in the terminal
|
||||||
color = true
|
color = true
|
||||||
|
|
||||||
# Whether ANSI codes from the page content should be rendered
|
# Whether ANSI color codes from the page content should be rendered
|
||||||
ansi = true
|
ansi = true
|
||||||
|
|
||||||
# Whether to replace list asterisks with unicode bullets
|
# Whether to replace list asterisks with unicode bullets
|
||||||
@ -59,6 +69,20 @@ page_max_time = 10
|
|||||||
emoji_favicons = false
|
emoji_favicons = false
|
||||||
|
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
# Authentication settings
|
||||||
|
|
||||||
|
[auth.certs]
|
||||||
|
# Client certificates
|
||||||
|
# Set domain name equal to path to client cert
|
||||||
|
# "example.com" = "mycert.crt"
|
||||||
|
|
||||||
|
[auth.keys]
|
||||||
|
# Client certificate keys
|
||||||
|
# Set domain name equal to path to key for the client cert above
|
||||||
|
# "example.com" = "mycert.key"
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
# In the future there will be more settings here.
|
# In the future there will be more settings here.
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ var theme = map[string]tcell.Color{
|
|||||||
"link_number": tcell.ColorSilver,
|
"link_number": tcell.ColorSilver,
|
||||||
"regular_text": tcell.ColorWhite,
|
"regular_text": tcell.ColorWhite,
|
||||||
"quote_text": tcell.ColorWhite,
|
"quote_text": tcell.ColorWhite,
|
||||||
"preformatted_text": tcell.ColorWhite,
|
"preformatted_text": tcell.ColorGrey,
|
||||||
"list_text": tcell.ColorWhite,
|
"list_text": tcell.ColorWhite,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
111
contrib/themes/nord.toml
Normal file
111
contrib/themes/nord.toml
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
[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
|
||||||
|
bg = "#2e3440"
|
||||||
|
fg = "#eceff4"
|
||||||
|
tab_num = "#88c0d0"
|
||||||
|
tab_divider = "#eceff4"
|
||||||
|
bottombar_bg = "#3b4252"
|
||||||
|
bottombar_text = "#eceff4"
|
||||||
|
bottombar_label = "#88c0d0"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
hdg_1 = "#5e81ac"
|
||||||
|
hdg_2 = "#81a1c1"
|
||||||
|
hdg_3 = "#8fbcbb"
|
||||||
|
amfora_link = "#88c0d0"
|
||||||
|
foreign_link = "#b48ead"
|
||||||
|
link_number = "#a3be8c"
|
||||||
|
regular_text = "#eceff4"
|
||||||
|
quote_text = "#8fbcbb"
|
||||||
|
preformatted_text = "#eceff4"
|
||||||
|
list_text = "#eceff4"
|
||||||
|
|
||||||
|
# btn_bg: The bg color for all modal buttons
|
||||||
|
# btn_text: The text color for all modal buttons
|
||||||
|
btn_bg = "#4c566a"
|
||||||
|
btn_text = "#eceff4"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
dl_choice_modal_bg = "#3b4252"
|
||||||
|
dl_choice_modal_text = "#eceff4"
|
||||||
|
dl_modal_bg = "#3b4252"
|
||||||
|
dl_modal_text = "#eceff4"
|
||||||
|
info_modal_bg = "#3b4252"
|
||||||
|
info_modal_text = "#eceff4"
|
||||||
|
error_modal_bg = "#bf616a"
|
||||||
|
error_modal_text = "#2e3440"
|
||||||
|
yesno_modal_bg = "#3b4252"
|
||||||
|
yesno_modal_text = "#eceff4"
|
||||||
|
tofu_modal_bg = "#3b4252"
|
||||||
|
tofu_modal_text = "#eceff4"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
input_modal_bg = "#3b4252"
|
||||||
|
input_modal_text = "#eceff4"
|
||||||
|
input_modal_field_bg = "#4c566a"
|
||||||
|
input_modal_field_text ="#eceff4"
|
||||||
|
|
||||||
|
# bkmk_modal_bg
|
||||||
|
# bkmk_modal_text
|
||||||
|
# bkmk_modal_label
|
||||||
|
# bkmk_modal_field_bg
|
||||||
|
# bkmk_modal_field_text
|
||||||
|
|
||||||
|
bkmk_modal_bg = "#3b4252"
|
||||||
|
bkmk_modal_text = "#eceff4"
|
||||||
|
bkmk_modal_label = "#88c0d0"
|
||||||
|
bkmk_modal_field_bg = "#4c566a"
|
||||||
|
bkmk_modal_field_text = "#eceff4"
|
@ -18,10 +18,20 @@ home = "gemini://gemini.circumlunar.space"
|
|||||||
# If set to false, a prompt will be shown before following redirects.
|
# If set to false, a prompt will be shown before following redirects.
|
||||||
auto_redirect = false
|
auto_redirect = false
|
||||||
|
|
||||||
# What command to run to open a HTTP(S) URL. Set to "default" to try to guess the browser,
|
# What command to run to open a HTTP(S) URL.
|
||||||
# or set to "off" to not open HTTP(S) URLs.
|
# Set to "default" to try to guess the browser, or set to "off" to not open HTTP(S) URLs.
|
||||||
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
# If a command is set, than the URL will be added (in quotes) to the end of the command.
|
||||||
# A space will be prepended if necessary.
|
# A space will be prepended to the URL.
|
||||||
|
#
|
||||||
|
# The best to define a command is using a string array.
|
||||||
|
# Examples:
|
||||||
|
# http = ["firefox"]
|
||||||
|
# http = ["custom-browser", "--flag", "--option=2"]
|
||||||
|
# http = ["/path/with spaces/in it/firefox"]
|
||||||
|
#
|
||||||
|
# Using just a string will also work, but it is deprecated,
|
||||||
|
# and will degrade if you use paths with spaces.
|
||||||
|
|
||||||
http = "default"
|
http = "default"
|
||||||
|
|
||||||
# Any URL that will accept a query string can be put here
|
# Any URL that will accept a query string can be put here
|
||||||
@ -30,7 +40,7 @@ search = "gemini://gus.guru/search"
|
|||||||
# Whether colors will be used in the terminal
|
# Whether colors will be used in the terminal
|
||||||
color = true
|
color = true
|
||||||
|
|
||||||
# Whether ANSI codes from the page content should be rendered
|
# Whether ANSI color codes from the page content should be rendered
|
||||||
ansi = true
|
ansi = true
|
||||||
|
|
||||||
# Whether to replace list asterisks with unicode bullets
|
# Whether to replace list asterisks with unicode bullets
|
||||||
@ -56,6 +66,20 @@ page_max_time = 10
|
|||||||
emoji_favicons = false
|
emoji_favicons = false
|
||||||
|
|
||||||
|
|
||||||
|
[auth]
|
||||||
|
# Authentication settings
|
||||||
|
|
||||||
|
[auth.certs]
|
||||||
|
# Client certificates
|
||||||
|
# Set domain name equal to path to client cert
|
||||||
|
# "example.com" = "mycert.crt"
|
||||||
|
|
||||||
|
[auth.keys]
|
||||||
|
# Client certificate keys
|
||||||
|
# Set domain name equal to path to key for the client cert above
|
||||||
|
# "example.com" = "mycert.key"
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
# In the future there will be more settings here.
|
# In the future there will be more settings here.
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ func openBkmkModal(name string, exists bool, favicon string) (string, int) {
|
|||||||
if favicon != "" && !exists {
|
if favicon != "" && !exists {
|
||||||
name = favicon + " " + name
|
name = favicon + " " + name
|
||||||
}
|
}
|
||||||
bkmkModalText = ""
|
bkmkModalText = name
|
||||||
bkmkModal.GetForm().AddInputField("Name: ", name, 0, nil,
|
bkmkModal.GetForm().AddInputField("Name: ", name, 0, nil,
|
||||||
func(text string) {
|
func(text string) {
|
||||||
// Store for use later
|
// Store for use later
|
||||||
|
@ -133,6 +133,13 @@ func Init() {
|
|||||||
// This shouldn't occur
|
// This shouldn't occur
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if query == ".." && tabs[tab].page.URL[len(tabs[tab].page.URL)-1] != '/' {
|
||||||
|
// Support what ".." used to work like
|
||||||
|
// If on /dir/doc.gmi, got to /dir/
|
||||||
|
query = "./"
|
||||||
|
}
|
||||||
|
|
||||||
target, err := current.Parse(query)
|
target, err := current.Parse(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Invalid relative url
|
// Invalid relative url
|
||||||
|
@ -117,10 +117,12 @@ func dlChoice(text, u string, resp *gemini.Response) {
|
|||||||
portalURL = parsed.String() + "%3F" + query
|
portalURL = parsed.String() + "%3F" + query
|
||||||
}
|
}
|
||||||
portalURL = strings.TrimPrefix(portalURL, "gemini://") + "?raw=1"
|
portalURL = strings.TrimPrefix(portalURL, "gemini://") + "?raw=1"
|
||||||
handleHTTP("https://portal.mozz.us/gemini/"+portalURL, false)
|
ok := handleHTTP("https://portal.mozz.us/gemini/"+portalURL, false)
|
||||||
|
if ok {
|
||||||
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
"github.com/makeworld-the-better-one/go-isemoji"
|
"github.com/makeworld-the-better-one/go-isemoji"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gitlab.com/tslocum/cview"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains the functions that aren't part of the public API.
|
// This file contains the functions that aren't part of the public API.
|
||||||
@ -145,26 +144,42 @@ func setPage(t *tab, p *structs.Page) {
|
|||||||
|
|
||||||
// handleHTTP is used by handleURL.
|
// handleHTTP is used by handleURL.
|
||||||
// It opens HTTP links and displays Info and Error modals.
|
// It opens HTTP links and displays Info and Error modals.
|
||||||
func handleHTTP(u string, showInfo bool) {
|
// Returns false if there was an error.
|
||||||
switch strings.TrimSpace(viper.GetString("a-general.http")) {
|
func handleHTTP(u string, showInfo bool) bool {
|
||||||
|
if len(config.HTTPCommand) == 1 {
|
||||||
|
// Possibly a non-command
|
||||||
|
|
||||||
|
switch strings.TrimSpace(config.HTTPCommand[0]) {
|
||||||
case "", "off":
|
case "", "off":
|
||||||
Error("HTTP Error", "Opening HTTP URLs is turned off.")
|
Error("HTTP Error", "Opening HTTP URLs is turned off.")
|
||||||
|
return false
|
||||||
case "default":
|
case "default":
|
||||||
s, err := webbrowser.Open(u)
|
s, err := webbrowser.Open(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Webbrowser Error", err.Error())
|
Error("Webbrowser Error", err.Error())
|
||||||
} else if showInfo {
|
return false
|
||||||
|
}
|
||||||
|
if showInfo {
|
||||||
Info(s)
|
Info(s)
|
||||||
}
|
}
|
||||||
default:
|
return true
|
||||||
// The config has a custom command to execute for HTTP URLs
|
}
|
||||||
fields := strings.Fields(viper.GetString("a-general.http"))
|
}
|
||||||
err := exec.Command(fields[0], append(fields[1:], u)...).Start()
|
|
||||||
|
// Custom command
|
||||||
|
var err error = nil
|
||||||
|
if len(config.HTTPCommand) > 1 {
|
||||||
|
err = exec.Command(config.HTTPCommand[0], append(config.HTTPCommand[1:], u)...).Start()
|
||||||
|
} else {
|
||||||
|
err = exec.Command(config.HTTPCommand[0], u).Start()
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
Error("HTTP Error", "Error executing custom browser command: "+err.Error())
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleOther is used by handleURL.
|
// handleOther is used by handleURL.
|
||||||
@ -367,12 +382,15 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
|
|
||||||
// Gemini URL, or one with a Gemini proxy available
|
// Gemini URL, or one with a Gemini proxy available
|
||||||
|
|
||||||
// Load page from cache if possible
|
// Load page from cache if it exists,
|
||||||
|
// and this isn't a page that was redirected to by the server (indicates dynamic content)
|
||||||
|
if numRedirects == 0 {
|
||||||
page, ok := cache.GetPage(u)
|
page, ok := cache.GetPage(u)
|
||||||
if ok {
|
if ok {
|
||||||
setPage(t, page)
|
setPage(t, page)
|
||||||
return ret(u, true)
|
return ret(u, true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Otherwise download it
|
// Otherwise download it
|
||||||
bottomBar.SetText("Loading...")
|
bottomBar.SetText("Loading...")
|
||||||
t.barText = "Loading..." // Save it too, in case the tab switches during loading
|
t.barText = "Loading..." // Save it too, in case the tab switches during loading
|
||||||
@ -449,7 +467,12 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
page.Width = termW
|
page.Width = termW
|
||||||
|
|
||||||
|
if !client.HasClientCert(parsed.Host) {
|
||||||
|
// Don't cache pages with client certs
|
||||||
go cache.AddPage(page)
|
go cache.AddPage(page)
|
||||||
|
}
|
||||||
|
|
||||||
setPage(t, page)
|
setPage(t, page)
|
||||||
return ret(u, true)
|
return ret(u, true)
|
||||||
}
|
}
|
||||||
@ -457,8 +480,8 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// Could be a non 20 (or 21) status code, or a different kind of document
|
// Could be a non 20 (or 21) status code, or a different kind of document
|
||||||
|
|
||||||
// Handle each status code
|
// Handle each status code
|
||||||
switch gemini.SimplifyStatus(res.Status) {
|
switch res.Status {
|
||||||
case 10:
|
case 10, 11:
|
||||||
userInput, ok := Input(res.Meta)
|
userInput, ok := Input(res.Meta)
|
||||||
if ok {
|
if ok {
|
||||||
// Make another request with the query string added
|
// Make another request with the query string added
|
||||||
@ -471,7 +494,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
return ret(handleURL(t, parsed.String(), 0))
|
return ret(handleURL(t, parsed.String(), 0))
|
||||||
}
|
}
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
case 30:
|
case 30, 31:
|
||||||
parsedMeta, err := url.Parse(res.Meta)
|
parsedMeta, err := url.Parse(res.Meta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Redirect Error", "Invalid URL: "+err.Error())
|
Error("Redirect Error", "Invalid URL: "+err.Error())
|
||||||
@ -497,15 +520,46 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
case 40:
|
case 40:
|
||||||
Error("Temporary Failure", cview.Escape(res.Meta))
|
Error("Temporary Failure", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 41:
|
||||||
|
Error("Server Unavailable", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 42:
|
||||||
|
Error("CGI Error", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 43:
|
||||||
|
Error("Proxy Failure", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 44:
|
||||||
|
Error("Slow Down", "You should wait "+escapeMeta(res.Meta)+" seconds before making another request.")
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
case 50:
|
case 50:
|
||||||
Error("Permanent Failure", cview.Escape(res.Meta))
|
Error("Permanent Failure", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 51:
|
||||||
|
Error("Not Found", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 52:
|
||||||
|
Error("Gone", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 53:
|
||||||
|
Error("Proxy Request Refused", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 59:
|
||||||
|
Error("Bad Request", escapeMeta(res.Meta))
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
case 60:
|
case 60:
|
||||||
Info("The server requested a certificate. Cert handling is coming to Amfora soon!")
|
Error("Client Certificate Required", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 61:
|
||||||
|
Error("Certificate Not Authorised", escapeMeta(res.Meta))
|
||||||
|
return ret("", false)
|
||||||
|
case 62:
|
||||||
|
Error("Certificate Not Valid", escapeMeta(res.Meta))
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status code 20, but not a document that can be displayed
|
// Status code 20, but not a document that can be displayed
|
||||||
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
|
@ -3,12 +3,19 @@ package display
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"gitlab.com/tslocum/cview"
|
||||||
)
|
)
|
||||||
|
|
||||||
// This file contains funcs that are small, self-contained utilities.
|
// This file contains funcs that are small, self-contained utilities.
|
||||||
|
|
||||||
|
// escapeMeta santizes a META string for use within a cview modal.
|
||||||
|
func escapeMeta(meta string) string {
|
||||||
|
return cview.Escape(strings.ReplaceAll(meta, "\n", ""))
|
||||||
|
}
|
||||||
|
|
||||||
// isValidTab indicates whether the passed tab is still being used, even if it's not currently displayed.
|
// isValidTab indicates whether the passed tab is still being used, even if it's not currently displayed.
|
||||||
func isValidTab(t *tab) bool {
|
func isValidTab(t *tab) bool {
|
||||||
tempTabs := tabs
|
tempTabs := tabs
|
||||||
|
2
go.mod
2
go.mod
@ -7,7 +7,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||||
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606
|
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606
|
||||||
github.com/google/go-cmp v0.5.0 // indirect
|
github.com/google/go-cmp v0.5.0 // indirect
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.8.4
|
github.com/makeworld-the-better-one/go-gemini v0.9.1
|
||||||
github.com/makeworld-the-better-one/go-isemoji v1.1.0
|
github.com/makeworld-the-better-one/go-isemoji v1.1.0
|
||||||
github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f
|
github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f
|
||||||
github.com/mitchellh/go-homedir v1.1.0
|
github.com/mitchellh/go-homedir v1.1.0
|
||||||
|
4
go.sum
4
go.sum
@ -130,8 +130,8 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW
|
|||||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
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/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.8.4 h1:ntsQ9HnlJCmC9PDqXp/f1SCALjBMwh69BbT4BhFRFaw=
|
github.com/makeworld-the-better-one/go-gemini v0.9.1 h1:/Vc6Y4Y1aOi4lZIBA1wDe+4N2xAI8EQ0CIjip2NUQkk=
|
||||||
github.com/makeworld-the-better-one/go-gemini v0.8.4/go.mod h1:P7/FbZ+IEIbA/d+A0Y3w2GNgD8SA2AcNv7aDGJbaWG4=
|
github.com/makeworld-the-better-one/go-gemini v0.9.1/go.mod h1:P7/FbZ+IEIbA/d+A0Y3w2GNgD8SA2AcNv7aDGJbaWG4=
|
||||||
github.com/makeworld-the-better-one/go-isemoji v1.1.0 h1:wZBHOKB5zAIgaU2vaWnXFDDhatebB8TySrNVxjVV84g=
|
github.com/makeworld-the-better-one/go-isemoji v1.1.0 h1:wZBHOKB5zAIgaU2vaWnXFDDhatebB8TySrNVxjVV84g=
|
||||||
github.com/makeworld-the-better-one/go-isemoji v1.1.0/go.mod h1:FBjkPl9rr0G4vlZCc+Mr+QcnOfGCTbGWYW8/1sp06I0=
|
github.com/makeworld-the-better-one/go-isemoji v1.1.0/go.mod h1:FBjkPl9rr0G4vlZCc+Mr+QcnOfGCTbGWYW8/1sp06I0=
|
||||||
github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f h1:YEUlTs5gb35UlBLTgqrub9axWTYB3d7/8TxrkJDZpRI=
|
github.com/makeworld-the-better-one/progressbar/v3 v3.3.5-0.20200710151429-125743e22b4f h1:YEUlTs5gb35UlBLTgqrub9axWTYB3d7/8TxrkJDZpRI=
|
||||||
|
@ -80,17 +80,6 @@ func wrapLine(line string, width int, prefix, suffix string, includeFirst bool)
|
|||||||
return ret
|
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
|
// convertRegularGemini converts non-preformatted blocks of text/gemini
|
||||||
// into a cview-compatible format.
|
// into a cview-compatible format.
|
||||||
// Since this only works on non-preformatted blocks, RenderGemini
|
// Since this only works on non-preformatted blocks, RenderGemini
|
||||||
@ -283,11 +272,6 @@ func convertRegularGemini(s string, numLinks, width int, proxied bool) (string,
|
|||||||
// If it's not a gemini:// page, set this to true.
|
// If it's not a gemini:// page, set this to true.
|
||||||
func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []string) {
|
func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []string) {
|
||||||
s = cview.Escape(s)
|
s = cview.Escape(s)
|
||||||
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
|
|
||||||
s = cview.TranslateANSI(s)
|
|
||||||
} else {
|
|
||||||
s = ansiRegex.ReplaceAllString(s, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
lines := strings.Split(s, "\n")
|
lines := strings.Split(s, "\n")
|
||||||
|
|
||||||
@ -302,13 +286,22 @@ func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []stri
|
|||||||
if pre {
|
if pre {
|
||||||
// In a preformatted block, so add the text as is
|
// In a preformatted block, so add the text as is
|
||||||
// Don't add the current line with backticks
|
// Don't add the current line with backticks
|
||||||
rendered += tagLines(
|
|
||||||
buf,
|
// Support ANSI color codes in preformatted blocks - see #59
|
||||||
fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")),
|
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
|
||||||
"[-]",
|
buf = cview.TranslateANSI(buf)
|
||||||
)
|
} else {
|
||||||
|
buf = ansiRegex.ReplaceAllString(buf, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
|
||||||
|
buf + "[-]"
|
||||||
} else {
|
} else {
|
||||||
// Not preformatted, regular text
|
// Not preformatted, regular text
|
||||||
|
|
||||||
|
// ANSI not allowed in regular text - see #59
|
||||||
|
buf = ansiRegex.ReplaceAllString(buf, "")
|
||||||
|
|
||||||
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
|
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
|
||||||
links = append(links, lks...)
|
links = append(links, lks...)
|
||||||
rendered += ren
|
rendered += ren
|
||||||
@ -323,10 +316,21 @@ func RenderGemini(s string, width, leftMargin int, proxied bool) (string, []stri
|
|||||||
// Gone through all the lines, but there still is likely a block in the buffer
|
// Gone through all the lines, but there still is likely a block in the buffer
|
||||||
if pre {
|
if pre {
|
||||||
// File ended without closing the preformatted block
|
// File ended without closing the preformatted block
|
||||||
rendered += buf
|
// Same code as in the loop above
|
||||||
|
|
||||||
|
if viper.GetBool("a-general.color") && viper.GetBool("a-general.ansi") {
|
||||||
|
buf = cview.TranslateANSI(buf)
|
||||||
|
} else {
|
||||||
|
buf = ansiRegex.ReplaceAllString(buf, "")
|
||||||
|
}
|
||||||
|
rendered += fmt.Sprintf("[%s]", config.GetColorString("preformatted_text")) +
|
||||||
|
buf + "[-]"
|
||||||
} else {
|
} else {
|
||||||
// Not preformatted, regular text
|
// Not preformatted, regular text
|
||||||
// Same code as in the loop above
|
// Same code as in the loop above
|
||||||
|
|
||||||
|
buf = ansiRegex.ReplaceAllString(buf, "")
|
||||||
|
|
||||||
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
|
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
|
||||||
links = append(links, lks...)
|
links = append(links, lks...)
|
||||||
rendered += ren
|
rendered += ren
|
||||||
|
Loading…
Reference in New Issue
Block a user