mirror of
https://github.com/makew0rld/amfora.git
synced 2024-12-04 14:46:29 -05:00
🔀 Merge branch 'master' into feeds
This commit is contained in:
commit
0f5458c730
26
.github/workflows/goreleaser.yml
vendored
Normal file
26
.github/workflows/goreleaser.yml
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
name: goreleaser
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- v*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
goreleaser:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: 1.15
|
||||||
|
- name: Run GoReleaser
|
||||||
|
uses: goreleaser/goreleaser-action@v2
|
||||||
|
with:
|
||||||
|
version: 0.x
|
||||||
|
args: release --rm-dist
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,7 +1,8 @@
|
|||||||
# Binaries
|
# Binaries
|
||||||
amfora
|
amfora
|
||||||
amfora-*
|
amfora-*
|
||||||
build/
|
build
|
||||||
|
dist
|
||||||
|
|
||||||
# Recording
|
# Recording
|
||||||
rec.yml
|
rec.yml
|
||||||
|
@ -41,3 +41,6 @@ linters-settings:
|
|||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
goconst:
|
||||||
|
# minimal length of string constant, 3 by default
|
||||||
|
min-len: 5
|
||||||
|
59
.goreleaser.yml
Normal file
59
.goreleaser.yml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
project_name: amfora
|
||||||
|
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
|
||||||
|
before:
|
||||||
|
hooks:
|
||||||
|
- go mod download
|
||||||
|
- go generate ./...
|
||||||
|
|
||||||
|
builds:
|
||||||
|
- env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- windows
|
||||||
|
- darwin
|
||||||
|
- freebsd
|
||||||
|
- netbsd
|
||||||
|
- openbsd
|
||||||
|
goarch:
|
||||||
|
- 386
|
||||||
|
- amd64
|
||||||
|
- arm64
|
||||||
|
- arm
|
||||||
|
goarm:
|
||||||
|
- 6
|
||||||
|
- 7
|
||||||
|
|
||||||
|
ignore:
|
||||||
|
- goos: darwin
|
||||||
|
goarch: 386
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm
|
||||||
|
- goos: freebsd
|
||||||
|
goarch: arm64
|
||||||
|
- goos: netbsd
|
||||||
|
goarch: arm
|
||||||
|
- goos: netbsd
|
||||||
|
goarch: arm64
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm
|
||||||
|
- goos: openbsd
|
||||||
|
goarch: arm64
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- format: binary
|
||||||
|
|
||||||
|
replacements:
|
||||||
|
darwin: macOS
|
||||||
|
386: 32-bit
|
||||||
|
amd64: 64-bit
|
||||||
|
|
||||||
|
milestones:
|
||||||
|
- close: true
|
||||||
|
|
||||||
|
changelog:
|
||||||
|
skip: true
|
17
CHANGELOG.md
17
CHANGELOG.md
@ -4,25 +4,30 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
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]
|
||||||
|
|
||||||
|
|
||||||
|
## [v1.5.0] - 2020-09-01
|
||||||
### Added
|
### Added
|
||||||
|
- **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)
|
||||||
- **Proxy support** - specify a proxy in the config for all requests to go through it (#66)
|
- `shift_numbers` key in the config was added, so that non US keyboard users can navigate tabs (#64)
|
||||||
- The `shift_numbers` key in the config was added, so that non US keyboard users can navigate tabs (#64)
|
|
||||||
- <kbd>F1</kbd> and <kbd>F2</kbd> keys for navigating to the previous and next tabs (#64)
|
- <kbd>F1</kbd> and <kbd>F2</kbd> keys for navigating to the previous and next tabs (#64)
|
||||||
- Resolving any relative path (starting with a `.`) in the bottom bar is supported, not just `..` (#71)
|
- Resolving any relative path (starts with a `.`) in the bottom bar is supported, not just `..` (#71)
|
||||||
- Set programs in config to open other schemes like `gopher://` or `magnet:` (#74)
|
- You can now set external programs in the config to open other schemes, like `gopher://` or `magnet:` (#74)
|
||||||
- Auto-redirecting can be enabled - redirect within Gemini up to 5 times automatically (#75)
|
- Auto-redirecting can be enabled - redirect within Gemini up to 5 times automatically (#75)
|
||||||
|
- Help page now documents paging keys (#78)
|
||||||
|
- The new tab page can be customized by creating a gemtext file called `newtab.gmi` in the config directory (#67, #83)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update to [go-gemini](https://github.com/makeworld-the-better-one/go-gemini) v0.8.4
|
- Update to [go-gemini](https://github.com/makeworld-the-better-one/go-gemini) v0.8.4
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Two digit (and higher) link texts are now in line with one digit ones (#60)
|
- Two digit (and higher) link texts are now in line with one digit ones (#60)
|
||||||
- Race condition when reloading pages, could have caused the cache to still be used
|
- Race condition when reloading pages that could have caused the cache to still be used
|
||||||
- Prevent panic (crash) when the server sends an error with an empty meta string (#73)
|
- Prevent panic (crash) when the server sends an error with an empty meta string (#73)
|
||||||
- URLs with with colon-only schemes (like `mailto:`) are properly recognized
|
- URLs with with colon-only schemes (like `mailto:`) are properly recognized
|
||||||
|
- You can no longer navigate through the history when the help page is open (#55, #78)
|
||||||
|
|
||||||
|
|
||||||
## [1.4.0] - 2020-07-28
|
## [1.4.0] - 2020-07-28
|
||||||
|
27
README.md
27
README.md
@ -61,12 +61,14 @@ This section is for programmers who want to install from source. Make sure you'r
|
|||||||
|
|
||||||
Install latest release:
|
Install latest release:
|
||||||
```
|
```
|
||||||
GO111MODULE=on go get github.com/makeworld-the-better-one/amfora
|
go env -w GO111MODULE=on
|
||||||
|
go get github.com/makeworld-the-better-one/amfora
|
||||||
```
|
```
|
||||||
|
|
||||||
Install latest commit:
|
Install latest commit:
|
||||||
```
|
```
|
||||||
GO111MODULE=on go get github.com/makeworld-the-better-one/amfora@master
|
go env -w GO111MODULE=on
|
||||||
|
go get github.com/makeworld-the-better-one/amfora@master
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@ -95,15 +97,14 @@ Features in *italics* are in the master branch, but not in the latest release.
|
|||||||
- [x] Bookmarks
|
- [x] Bookmarks
|
||||||
- [x] Download pages and arbitrary data
|
- [x] Download pages and arbitrary data
|
||||||
- [x] Theming
|
- [x] Theming
|
||||||
- [x] *Emoji favicons*
|
- [x] Emoji favicons
|
||||||
- See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details
|
- See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details
|
||||||
- [x] *Proxying*
|
- Disabled by default, enable in config
|
||||||
- All requests can optionally be sent through another server
|
- [x] Proxying
|
||||||
- A gemini proxy server implementation currently does not exist, but Amfora will support it when it does!
|
- Schemes like Gopher or HTTP can be proxied through a Gemini server
|
||||||
- [x] *Subscribe to RSS and Atom feeds and display them*
|
- [x] *Subscribe to RSS and Atom feeds and display them*
|
||||||
- Subscribing to page changes, similar to how Spacewalk works, is also supported
|
- Subscribing to page changes, similar to how Spacewalk works, will also be supported
|
||||||
- [ ] Support Markdown rendering
|
- [ ] Stream support
|
||||||
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
|
|
||||||
- [ ] 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
|
||||||
@ -118,6 +119,14 @@ 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`.
|
||||||
|
|
||||||
|
## 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))
|
||||||
|
- 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).
|
||||||
|
|
||||||
|
|
||||||
## Libraries
|
## Libraries
|
||||||
Amfora ❤️ open source!
|
Amfora ❤️ open source!
|
||||||
|
|
||||||
|
10
amfora.go
10
amfora.go
@ -9,7 +9,11 @@ import (
|
|||||||
"github.com/makeworld-the-better-one/amfora/feeds"
|
"github.com/makeworld-the-better-one/amfora/feeds"
|
||||||
)
|
)
|
||||||
|
|
||||||
var version = "1.5.0-unreleased"
|
var (
|
||||||
|
version = "1.5.0"
|
||||||
|
commit = "unknown"
|
||||||
|
builtBy = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// err := logger.Init()
|
// err := logger.Init()
|
||||||
@ -19,7 +23,9 @@ func main() {
|
|||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
if os.Args[1] == "--version" || os.Args[1] == "-v" {
|
if os.Args[1] == "--version" || os.Args[1] == "-v" {
|
||||||
fmt.Println("amfora v" + version)
|
fmt.Println("Amfora", version)
|
||||||
|
fmt.Println("Commit:", commit)
|
||||||
|
fmt.Println("Built by:", builtBy)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if os.Args[1] == "--help" || os.Args[1] == "-h" {
|
if os.Args[1] == "--help" || os.Args[1] == "-h" {
|
||||||
|
@ -2,31 +2,43 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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) {
|
||||||
var res *gemini.Response
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if viper.GetString("a-general.proxy") == "" {
|
res, err := gemini.Fetch(u)
|
||||||
res, err = gemini.Fetch(u)
|
|
||||||
} else {
|
|
||||||
res, err = gemini.FetchWithHost(viper.GetString("a-general.proxy"), u)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsed, _ := url.Parse(u)
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FetchWithProxy is the same as Fetch, but uses a proxy.
|
||||||
|
func FetchWithProxy(proxyHostname, proxyPort, u string) (*gemini.Response, error) {
|
||||||
|
res, err := gemini.FetchWithHost(net.JoinHostPort(proxyHostname, proxyPort), u)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only associate the returned cert with the proxy
|
||||||
|
ok := handleTofu(proxyHostname, proxyPort, res.Cert)
|
||||||
|
if !ok {
|
||||||
|
return res, ErrTofu
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
@ -22,6 +22,9 @@ var amforaAppData string // Where amfora files are stored on Windows - cached he
|
|||||||
var configDir string
|
var configDir string
|
||||||
var configPath string
|
var configPath string
|
||||||
|
|
||||||
|
var NewTabPath string
|
||||||
|
var CustomNewTab bool
|
||||||
|
|
||||||
var TofuStore = viper.New()
|
var TofuStore = viper.New()
|
||||||
var tofuDBDir string
|
var tofuDBDir string
|
||||||
var tofuDBPath string
|
var tofuDBPath string
|
||||||
@ -74,6 +77,13 @@ func Init() error {
|
|||||||
}
|
}
|
||||||
configPath = filepath.Join(configDir, "config.toml")
|
configPath = filepath.Join(configDir, "config.toml")
|
||||||
|
|
||||||
|
// Search for a custom new tab
|
||||||
|
NewTabPath = filepath.Join(configDir, "newtab.gmi")
|
||||||
|
CustomNewTab = false
|
||||||
|
if _, err := os.Stat(NewTabPath); err == nil {
|
||||||
|
CustomNewTab = true
|
||||||
|
}
|
||||||
|
|
||||||
// Store TOFU db directory and file paths
|
// Store TOFU db directory and file paths
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
// Windows just stores it in APPDATA along with other stuff
|
// Windows just stores it in APPDATA along with other stuff
|
||||||
|
@ -21,8 +21,8 @@ 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 URL. Set to "default" to try to guess the browser,
|
# What command to run to open a HTTP(S) URL. Set to "default" to try to guess the browser,
|
||||||
# or set to "off" to not open HTTP URLs.
|
# 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 if necessary.
|
||||||
http = "default"
|
http = "default"
|
||||||
@ -55,12 +55,6 @@ page_max_time = 10
|
|||||||
# Whether to replace tab numbers with emoji favicons, which are cached.
|
# Whether to replace tab numbers with emoji favicons, which are cached.
|
||||||
emoji_favicons = false
|
emoji_favicons = false
|
||||||
|
|
||||||
# Proxy server, through which all requests would be sent.
|
|
||||||
# String should be a host: a domain/IP with an optional port. Port 1965 is assumed otherwise.
|
|
||||||
# The proxy server needs to be a Gemini server that supports proxying.
|
|
||||||
# By default it is empty, which disables the proxy.
|
|
||||||
proxy = ""
|
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
# In the future there will be more settings here.
|
# In the future there will be more settings here.
|
||||||
@ -74,10 +68,13 @@ shift_numbers = "!@#$%^&*()"
|
|||||||
# Allows setting the commands to run for various URL schemes.
|
# Allows setting the commands to run for various URL schemes.
|
||||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||||
# ftp = "filezilla"
|
# ftp = "filezilla"
|
||||||
# You can set any scheme to "off" to disable handling it.
|
# 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.
|
# DO NOT use this for setting the HTTP command.
|
||||||
# Use the http setting in the "a-general" section above
|
# Use the http setting in the "a-general" section above.
|
||||||
|
#
|
||||||
|
# NOTE: These settings are override by the ones in the proxies section.
|
||||||
|
|
||||||
# This is a special key that defines the handler for all URL schemes for which
|
# This is a special key that defines the handler for all URL schemes for which
|
||||||
# no handler is defined.
|
# no handler is defined.
|
||||||
@ -93,6 +90,20 @@ max_size = 0 # Size in bytes
|
|||||||
max_pages = 30 # The maximum number of pages the cache will store
|
max_pages = 30 # The maximum number of pages the cache will store
|
||||||
|
|
||||||
|
|
||||||
|
[proxies]
|
||||||
|
# Allows setting a Gemini proxy for different schemes.
|
||||||
|
# The settings are similar to the url-handlers section above.
|
||||||
|
# E.g. to open a gopher page by connecting to a Gemini proxy server:
|
||||||
|
# gopher = "example.com:123"
|
||||||
|
#
|
||||||
|
# Port 1965 is assumed if no port is specified.
|
||||||
|
#
|
||||||
|
# NOTE: These settings override any external handlers specified in
|
||||||
|
# the url-handlers section.
|
||||||
|
#
|
||||||
|
# Note that HTTP and HTTPS are treated as separate protocols here.
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
|
@ -18,8 +18,8 @@ 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 URL. Set to "default" to try to guess the browser,
|
# What command to run to open a HTTP(S) URL. Set to "default" to try to guess the browser,
|
||||||
# or set to "off" to not open HTTP URLs.
|
# 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 if necessary.
|
||||||
http = "default"
|
http = "default"
|
||||||
@ -52,12 +52,6 @@ page_max_time = 10
|
|||||||
# Whether to replace tab numbers with emoji favicons, which are cached.
|
# Whether to replace tab numbers with emoji favicons, which are cached.
|
||||||
emoji_favicons = false
|
emoji_favicons = false
|
||||||
|
|
||||||
# Proxy server, through which all requests would be sent.
|
|
||||||
# String should be a host: a domain/IP with an optional port. Port 1965 is assumed otherwise.
|
|
||||||
# The proxy server needs to be a Gemini server that supports proxying.
|
|
||||||
# By default it is empty, which disables the proxy.
|
|
||||||
proxy = ""
|
|
||||||
|
|
||||||
|
|
||||||
[keybindings]
|
[keybindings]
|
||||||
# In the future there will be more settings here.
|
# In the future there will be more settings here.
|
||||||
@ -71,10 +65,13 @@ shift_numbers = "!@#$%^&*()"
|
|||||||
# Allows setting the commands to run for various URL schemes.
|
# Allows setting the commands to run for various URL schemes.
|
||||||
# E.g. to open FTP URLs with FileZilla set the following key:
|
# E.g. to open FTP URLs with FileZilla set the following key:
|
||||||
# ftp = "filezilla"
|
# ftp = "filezilla"
|
||||||
# You can set any scheme to "off" to disable handling it.
|
# 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.
|
# DO NOT use this for setting the HTTP command.
|
||||||
# Use the http setting in the "a-general" section above
|
# Use the http setting in the "a-general" section above.
|
||||||
|
#
|
||||||
|
# NOTE: These settings are override by the ones in the proxies section.
|
||||||
|
|
||||||
# This is a special key that defines the handler for all URL schemes for which
|
# This is a special key that defines the handler for all URL schemes for which
|
||||||
# no handler is defined.
|
# no handler is defined.
|
||||||
@ -90,6 +87,20 @@ max_size = 0 # Size in bytes
|
|||||||
max_pages = 30 # The maximum number of pages the cache will store
|
max_pages = 30 # The maximum number of pages the cache will store
|
||||||
|
|
||||||
|
|
||||||
|
[proxies]
|
||||||
|
# Allows setting a Gemini proxy for different schemes.
|
||||||
|
# The settings are similar to the url-handlers section above.
|
||||||
|
# E.g. to open a gopher page by connecting to a Gemini proxy server:
|
||||||
|
# gopher = "example.com:123"
|
||||||
|
#
|
||||||
|
# Port 1965 is assumed if no port is specified.
|
||||||
|
#
|
||||||
|
# NOTE: These settings override any external handlers specified in
|
||||||
|
# the url-handlers section.
|
||||||
|
#
|
||||||
|
# Note that HTTP and HTTPS are treated as separate protocols here.
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
# These colors only apply if 'color' is enabled above.
|
# These colors only apply if 'color' is enabled above.
|
||||||
|
@ -116,7 +116,7 @@ func Bookmarks(t *tab) {
|
|||||||
bkmkPageRaw += fmt.Sprintf("=> %s %s\r\n", keys[i], m[keys[i]])
|
bkmkPageRaw += fmt.Sprintf("=> %s %s\r\n", keys[i], m[keys[i]])
|
||||||
}
|
}
|
||||||
// Render and display
|
// Render and display
|
||||||
content, links := renderer.RenderGemini(bkmkPageRaw, textWidth(), leftMargin())
|
content, links := renderer.RenderGemini(bkmkPageRaw, textWidth(), leftMargin(), false)
|
||||||
page := structs.Page{
|
page := structs.Page{
|
||||||
Raw: bkmkPageRaw,
|
Raw: bkmkPageRaw,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
@ -51,8 +51,6 @@ var tabRow = cview.NewTextView().
|
|||||||
var layout = cview.NewFlex().
|
var layout = cview.NewFlex().
|
||||||
SetDirection(cview.FlexRow)
|
SetDirection(cview.FlexRow)
|
||||||
|
|
||||||
var renderedNewTabContent string
|
|
||||||
var newTabLinks []string
|
|
||||||
var newTabPage structs.Page
|
var newTabPage structs.Page
|
||||||
|
|
||||||
var App = cview.NewApplication().
|
var App = cview.NewApplication().
|
||||||
@ -202,7 +200,8 @@ func Init() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Render the default new tab content ONCE and store it for later
|
// Render the default new tab content ONCE and store it for later
|
||||||
renderedNewTabContent, newTabLinks = renderer.RenderGemini(newTabContent, textWidth(), leftMargin())
|
newTabContent := getNewTabContent()
|
||||||
|
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), leftMargin(), false)
|
||||||
newTabPage = structs.Page{
|
newTabPage = structs.Page{
|
||||||
Raw: newTabContent,
|
Raw: newTabContent,
|
||||||
Content: renderedNewTabContent,
|
Content: renderedNewTabContent,
|
||||||
@ -232,6 +231,11 @@ func Init() {
|
|||||||
// It's focused on a modal right now, nothing should interrupt
|
// It's focused on a modal right now, nothing should interrupt
|
||||||
return event
|
return event
|
||||||
}
|
}
|
||||||
|
_, ok = App.GetFocus().(*cview.Table)
|
||||||
|
if ok {
|
||||||
|
// It's focused on help right now
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
if tabs[curTab].mode == tabModeDone {
|
if tabs[curTab].mode == tabModeDone {
|
||||||
// All the keys and operations that can only work while NOT loading
|
// All the keys and operations that can only work while NOT loading
|
||||||
@ -514,6 +518,24 @@ func SwitchTab(tab int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Reload() {
|
func Reload() {
|
||||||
|
if tabs[curTab].page.URL == "about:newtab" && config.CustomNewTab {
|
||||||
|
// Re-render new tab, similar to Init()
|
||||||
|
newTabContent := getNewTabContent()
|
||||||
|
tmpTermW := termW
|
||||||
|
renderedNewTabContent, newTabLinks := renderer.RenderGemini(newTabContent, textWidth(), leftMargin(), false)
|
||||||
|
newTabPage = structs.Page{
|
||||||
|
Raw: newTabContent,
|
||||||
|
Content: renderedNewTabContent,
|
||||||
|
Links: newTabLinks,
|
||||||
|
URL: "about:newtab",
|
||||||
|
Width: tmpTermW,
|
||||||
|
Mediatype: structs.TextGemini,
|
||||||
|
}
|
||||||
|
temp := newTabPage // Copy
|
||||||
|
setPage(tabs[curTab], &temp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if !tabs[curTab].hasContent() {
|
if !tabs[curTab].hasContent() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ func Feeds(t *tab) {
|
|||||||
feedPageRaw += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Author, entry.Title)
|
feedPageRaw += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Author, entry.Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
content, links := renderer.RenderGemini(feedPageRaw, textWidth(), leftMargin())
|
content, links := renderer.RenderGemini(feedPageRaw, textWidth(), leftMargin(), false)
|
||||||
page := structs.Page{
|
page := structs.Page{
|
||||||
Raw: feedPageRaw,
|
Raw: feedPageRaw,
|
||||||
Content: content,
|
Content: content,
|
||||||
|
@ -12,12 +12,14 @@ var helpCells = strings.TrimSpace(`
|
|||||||
?|Bring up this help. You can scroll!
|
?|Bring up this help. You can scroll!
|
||||||
Esc|Leave the help
|
Esc|Leave the help
|
||||||
Arrow keys, h/j/k/l|Scroll and move a page.
|
Arrow keys, h/j/k/l|Scroll and move a page.
|
||||||
|
PgUp, u|Go up a page in document
|
||||||
|
PgDn, d|Go down a page in document
|
||||||
|
g|Go to top of document
|
||||||
|
G|Go to bottom of document
|
||||||
Tab|Navigate to the next item in a popup.
|
Tab|Navigate to the next item in a popup.
|
||||||
Shift-Tab|Navigate to the previous item in a popup.
|
Shift-Tab|Navigate to the previous item in a popup.
|
||||||
b, Alt-Left|Go back in the history
|
b, Alt-Left|Go back in the history
|
||||||
f, Alt-Right|Go forward in the history
|
f, Alt-Right|Go forward in the history
|
||||||
g|Go to top of document
|
|
||||||
G|Go to bottom of document
|
|
||||||
spacebar|Open bar at the bottom - type a URL, link number, search term.
|
spacebar|Open bar at the bottom - type a URL, link number, search term.
|
||||||
|You can also type two dots (..) to go up a directory in the URL.
|
|You can also type two dots (..) to go up a directory in the URL.
|
||||||
|Typing new:N will open link number N in a new tab
|
|Typing new:N will open link number N in a new tab
|
||||||
@ -60,7 +62,7 @@ func Help() {
|
|||||||
func helpInit() {
|
func helpInit() {
|
||||||
// Populate help table
|
// Populate help table
|
||||||
helpTable.SetDoneFunc(func(key tcell.Key) {
|
helpTable.SetDoneFunc(func(key tcell.Key) {
|
||||||
if key == tcell.KeyEsc {
|
if key == tcell.KeyEsc || key == tcell.KeyEnter {
|
||||||
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
tabPages.SwitchToPage(strconv.Itoa(curTab))
|
||||||
App.SetFocus(tabs[curTab].view)
|
App.SetFocus(tabs[curTab].view)
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
//nolint
|
|
||||||
package display
|
package display
|
||||||
|
|
||||||
var newTabContent = `# New Tab
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/makeworld-the-better-one/amfora/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint
|
||||||
|
var defaultNewTabContent = `# New Tab
|
||||||
|
|
||||||
You've opened a new tab. Use the bar at the bottom to browse around. You can start typing in it by pressing the space key.
|
You've opened a new tab. Use the bar at the bottom to browse around. You can start typing in it by pressing the space key.
|
||||||
|
|
||||||
Press the ? key at any time to bring up the help, and see other keybindings. Most are what you expect.
|
Press the ? key at any time to bring up the help, and see other keybindings. Most are what you expect.
|
||||||
|
|
||||||
|
You can customize this page by creating a gemtext file called newtab.gmi, in Amfora's configuration folder.
|
||||||
|
|
||||||
Happy browsing!
|
Happy browsing!
|
||||||
|
|
||||||
=> about:bookmarks Bookmarks
|
=> about:bookmarks Bookmarks
|
||||||
@ -14,3 +22,12 @@ Happy browsing!
|
|||||||
=> //gemini.circumlunar.space Project Gemini
|
=> //gemini.circumlunar.space Project Gemini
|
||||||
=> https://github.com/makeworld-the-better-one/amfora Amfora homepage [HTTPS]
|
=> https://github.com/makeworld-the-better-one/amfora Amfora homepage [HTTPS]
|
||||||
`
|
`
|
||||||
|
|
||||||
|
// Read the new tab content from a file if it exists or fallback to a default page.
|
||||||
|
func getNewTabContent() string {
|
||||||
|
data, err := ioutil.ReadFile(config.NewTabPath)
|
||||||
|
if err == nil {
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
|
return defaultNewTabContent
|
||||||
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -77,7 +78,11 @@ func reformatPage(p *structs.Page) {
|
|||||||
switch p.Mediatype {
|
switch p.Mediatype {
|
||||||
case structs.TextGemini:
|
case structs.TextGemini:
|
||||||
// Links are not recorded because they won't change
|
// Links are not recorded because they won't change
|
||||||
rendered, _ = renderer.RenderGemini(p.Raw, textWidth(), leftMargin())
|
proxied := true
|
||||||
|
if strings.HasPrefix(p.URL, "gemini") || strings.HasPrefix(p.URL, "about") {
|
||||||
|
proxied = false
|
||||||
|
}
|
||||||
|
rendered, _ = renderer.RenderGemini(p.Raw, textWidth(), leftMargin(), proxied)
|
||||||
case structs.TextPlain:
|
case structs.TextPlain:
|
||||||
rendered = renderer.RenderPlainText(p.Raw, leftMargin())
|
rendered = renderer.RenderPlainText(p.Raw, leftMargin())
|
||||||
case structs.TextAnsi:
|
case structs.TextAnsi:
|
||||||
@ -167,6 +172,7 @@ func handleHTTP(u string, showInfo bool) {
|
|||||||
func handleOther(u string) {
|
func handleOther(u string) {
|
||||||
// The URL should have a scheme due to a previous call to normalizeURL
|
// The URL should have a scheme due to a previous call to normalizeURL
|
||||||
parsed, _ := url.Parse(u)
|
parsed, _ := url.Parse(u)
|
||||||
|
|
||||||
// Search for a handler for the URL scheme
|
// Search for a handler for the URL scheme
|
||||||
handler := strings.TrimSpace(viper.GetString("url-handlers." + parsed.Scheme))
|
handler := strings.TrimSpace(viper.GetString("url-handlers." + parsed.Scheme))
|
||||||
if len(handler) == 0 {
|
if len(handler) == 0 {
|
||||||
@ -330,15 +336,36 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxy := strings.TrimSpace(viper.GetString("proxies." + parsed.Scheme))
|
||||||
|
usingProxy := false
|
||||||
|
|
||||||
|
proxyHostname, proxyPort, err := net.SplitHostPort(proxy)
|
||||||
|
if err != nil {
|
||||||
|
// Error likely means there's no port in the host
|
||||||
|
proxyHostname = proxy
|
||||||
|
proxyPort = "1965"
|
||||||
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(u, "http") {
|
if strings.HasPrefix(u, "http") {
|
||||||
|
if proxy == "" || proxy == "off" {
|
||||||
|
// No proxy available
|
||||||
handleHTTP(u, true)
|
handleHTTP(u, true)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(u, "gemini") {
|
usingProxy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(u, "http") && !strings.HasPrefix(u, "gemini") {
|
||||||
|
// Not a Gemini URL
|
||||||
|
if proxy == "" || proxy == "off" {
|
||||||
|
// No proxy available
|
||||||
handleOther(u)
|
handleOther(u)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
// Gemini URL
|
usingProxy = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gemini URL, or one with a Gemini proxy available
|
||||||
|
|
||||||
// Load page from cache if possible
|
// Load page from cache if possible
|
||||||
page, ok := cache.GetPage(u)
|
page, ok := cache.GetPage(u)
|
||||||
@ -352,7 +379,12 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
t.mode = tabModeLoading
|
t.mode = tabModeLoading
|
||||||
App.Draw()
|
App.Draw()
|
||||||
|
|
||||||
res, err := client.Fetch(u)
|
var res *gemini.Response
|
||||||
|
if usingProxy {
|
||||||
|
res, err = client.FetchWithProxy(proxyHostname, proxyPort, u)
|
||||||
|
} else {
|
||||||
|
res, err = client.Fetch(u)
|
||||||
|
}
|
||||||
|
|
||||||
// Loading may have taken a while, make sure tab is still valid
|
// Loading may have taken a while, make sure tab is still valid
|
||||||
if !isValidTab(t) {
|
if !isValidTab(t) {
|
||||||
@ -360,6 +392,17 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if errors.Is(err, client.ErrTofu) {
|
if errors.Is(err, client.ErrTofu) {
|
||||||
|
if usingProxy {
|
||||||
|
// They are using a proxy
|
||||||
|
if Tofu(proxy, client.GetExpiry(proxyHostname, proxyPort)) {
|
||||||
|
// They want to continue anyway
|
||||||
|
client.ResetTofuEntry(proxyHostname, proxyPort, res.Cert)
|
||||||
|
// Response can be used further down, no need to reload
|
||||||
|
} else {
|
||||||
|
// They don't want to continue
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if Tofu(parsed.Host, client.GetExpiry(parsed.Hostname(), parsed.Port())) {
|
if Tofu(parsed.Host, client.GetExpiry(parsed.Hostname(), parsed.Port())) {
|
||||||
// They want to continue anyway
|
// They want to continue anyway
|
||||||
client.ResetTofuEntry(parsed.Hostname(), parsed.Port(), res.Cert)
|
client.ResetTofuEntry(parsed.Hostname(), parsed.Port(), res.Cert)
|
||||||
@ -368,12 +411,13 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
// They don't want to continue
|
// They don't want to continue
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
Error("URL Fetch Error", err.Error())
|
Error("URL Fetch Error", err.Error())
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
if renderer.CanDisplay(res) {
|
if renderer.CanDisplay(res) {
|
||||||
page, err := renderer.MakePage(u, res, textWidth(), leftMargin())
|
page, err := renderer.MakePage(u, res, textWidth(), leftMargin(), usingProxy)
|
||||||
// Rendering may have taken a while, make sure tab is still valid
|
// Rendering may have taken a while, make sure tab is still valid
|
||||||
if !isValidTab(t) {
|
if !isValidTab(t) {
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
|
@ -57,7 +57,7 @@ func CanDisplay(res *gemini.Response) bool {
|
|||||||
|
|
||||||
// MakePage creates a formatted, rendered Page from the given network response and params.
|
// MakePage creates a formatted, rendered Page from the given network response and params.
|
||||||
// You must set the Page.Width value yourself.
|
// You must set the Page.Width value yourself.
|
||||||
func MakePage(url string, res *gemini.Response, width, leftMargin int) (*structs.Page, error) {
|
func MakePage(url string, res *gemini.Response, width, leftMargin int, proxied bool) (*structs.Page, error) {
|
||||||
if !CanDisplay(res) {
|
if !CanDisplay(res) {
|
||||||
return nil, ErrCantDisplay
|
return nil, ErrCantDisplay
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int) (*structs
|
|||||||
}
|
}
|
||||||
|
|
||||||
if mediatype == "text/gemini" {
|
if mediatype == "text/gemini" {
|
||||||
rendered, links := RenderGemini(utfText, width, leftMargin)
|
rendered, links := RenderGemini(utfText, width, leftMargin, proxied)
|
||||||
return &structs.Page{
|
return &structs.Page{
|
||||||
Mediatype: structs.TextGemini,
|
Mediatype: structs.TextGemini,
|
||||||
URL: url,
|
URL: url,
|
||||||
|
@ -87,13 +87,17 @@ func tagLines(s, start, end string) string {
|
|||||||
|
|
||||||
// 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
|
||||||
|
// should always be used instead.
|
||||||
|
//
|
||||||
// It also returns a slice of link URLs.
|
// It also returns a slice of link URLs.
|
||||||
// numLinks is the number of links that exist so far.
|
// numLinks is the number of links that exist so far.
|
||||||
// width is the number of columns to wrap to.
|
// width is the number of columns to wrap to.
|
||||||
//
|
//
|
||||||
// Since this only works on non-preformatted blocks, RenderGemini
|
//
|
||||||
// should always be used instead.
|
// proxied is whether the request is through the gemini:// scheme.
|
||||||
func convertRegularGemini(s string, numLinks, width int) (string, []string) {
|
// If it's not a gemini:// page, set this to true.
|
||||||
|
func convertRegularGemini(s string, numLinks, width int, proxied bool) (string, []string) {
|
||||||
links := make([]string, 0)
|
links := make([]string, 0)
|
||||||
lines := strings.Split(s, "\n")
|
lines := strings.Split(s, "\n")
|
||||||
wrappedLines := make([]string, 0) // Final result
|
wrappedLines := make([]string, 0) // Final result
|
||||||
@ -175,7 +179,8 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
|
|||||||
|
|
||||||
if viper.GetBool("a-general.color") {
|
if viper.GetBool("a-general.color") {
|
||||||
pU, err := urlPkg.Parse(url)
|
pU, err := urlPkg.Parse(url)
|
||||||
if err == nil && (pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
if !proxied && err == nil &&
|
||||||
|
(pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
|
||||||
// A gemini link
|
// A gemini link
|
||||||
// Add the link text in blue (in a region), and a gray link number to the left of it
|
// 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
|
// Those are the default colors, anyway
|
||||||
@ -267,7 +272,10 @@ func convertRegularGemini(s string, numLinks, width int) (string, []string) {
|
|||||||
//
|
//
|
||||||
// width is the number of columns to wrap to.
|
// width is the number of columns to wrap to.
|
||||||
// leftMargin is the number of blank spaces to prepend to each line.
|
// leftMargin is the number of blank spaces to prepend to each line.
|
||||||
func RenderGemini(s string, width, leftMargin int) (string, []string) {
|
//
|
||||||
|
// proxied is whether the request is through the gemini:// scheme.
|
||||||
|
// If it's not a gemini:// page, set this to true.
|
||||||
|
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") {
|
if viper.GetBool("a-general.color") {
|
||||||
s = cview.TranslateANSI(s)
|
s = cview.TranslateANSI(s)
|
||||||
@ -292,7 +300,7 @@ func RenderGemini(s string, width, leftMargin int) (string, []string) {
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Not preformatted, regular text
|
// Not preformatted, regular text
|
||||||
ren, lks := convertRegularGemini(buf, len(links), width)
|
ren, lks := convertRegularGemini(buf, len(links), width, proxied)
|
||||||
links = append(links, lks...)
|
links = append(links, lks...)
|
||||||
rendered += ren
|
rendered += ren
|
||||||
}
|
}
|
||||||
@ -310,7 +318,7 @@ func RenderGemini(s string, width, leftMargin int) (string, []string) {
|
|||||||
} else {
|
} else {
|
||||||
// Not preformatted, regular text
|
// Not preformatted, regular text
|
||||||
// Same code as in the loop above
|
// Same code as in the loop above
|
||||||
ren, lks := convertRegularGemini(buf, len(links), width)
|
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