1
0
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:
makeworld 2020-09-01 22:26:34 -04:00
commit 0f5458c730
19 changed files with 326 additions and 80 deletions

26
.github/workflows/goreleaser.yml vendored Normal file
View 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
View File

@ -1,7 +1,8 @@
# Binaries # Binaries
amfora amfora
amfora-* amfora-*
build/ build
dist
# Recording # Recording
rec.yml rec.yml

View File

@ -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
View 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

View File

@ -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

View File

@ -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!

View File

@ -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" {

View File

@ -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
}

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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,

View File

@ -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
} }

View File

@ -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,

View File

@ -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()

View File

@ -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
}

View File

@ -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") {
handleHTTP(u, true) if proxy == "" || proxy == "off" {
return ret("", false) // No proxy available
handleHTTP(u, true)
return ret("", false)
}
usingProxy = true
} }
if !strings.HasPrefix(u, "gemini") {
handleOther(u) if !strings.HasPrefix(u, "http") && !strings.HasPrefix(u, "gemini") {
return ret("", false) // Not a Gemini URL
if proxy == "" || proxy == "off" {
// No proxy available
handleOther(u)
return ret("", false)
}
usingProxy = true
} }
// Gemini URL
// 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,20 +392,32 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
} }
if errors.Is(err, client.ErrTofu) { if errors.Is(err, client.ErrTofu) {
if Tofu(parsed.Host, client.GetExpiry(parsed.Hostname(), parsed.Port())) { if usingProxy {
// They want to continue anyway // They are using a proxy
client.ResetTofuEntry(parsed.Hostname(), parsed.Port(), res.Cert) if Tofu(proxy, client.GetExpiry(proxyHostname, proxyPort)) {
// Response can be used further down, no need to reload // 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 { } else {
// They don't want to continue if Tofu(parsed.Host, client.GetExpiry(parsed.Hostname(), parsed.Port())) {
return ret("", false) // They want to continue anyway
client.ResetTofuEntry(parsed.Hostname(), parsed.Port(), res.Cert)
// Response can be used further down, no need to reload
} else {
// They don't want to continue
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)

View File

@ -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,

View File

@ -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
} }