1
0
mirror of https://github.com/makew0rld/amfora.git synced 2024-06-15 19:15:24 +00:00

Add bookmarks! - fixes #10 fixes #13

This commit is contained in:
makeworld 2020-06-23 20:07:25 -04:00
parent 199d122990
commit cfe58cb5f3
16 changed files with 381 additions and 105 deletions

View File

@ -6,12 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Support over 55 charsets (#3)
- Add titles to error modals
- **Bookmarks** (#10)
- **Support over 55 charsets** (#3)
- **Search using the bottom bar**
- Add titles to all modals
- Store ports in TOFU database (#7)
- Search from bottom bar
- Wrapping based on terminal width (#1)
- `left_margin` config option
- `left_margin` config option (#1)
- Right margin for text (#1)
- Desktop entry file
- Option to continue anyway when cert doesn't match TOFU database
@ -21,7 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Connection timeout is 15 seconds (was 5s)
- Hash `SubjectPublicKeyInfo` for TOFU instead (#7)
- `wrap_width` config option became `max_width`
- `wrap_width` config option became `max_width` (#1)
- Make the help table look better
### Removed
- Opening multiple URLs from the command line (threading issues)
@ -30,7 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Reset bottom bar on error / invalid URL
- Side scrolling doesn't cut off text on the left side (#1)
- Mark status code 21 as invalid
- You can't type on the bottom bar as it's loading
- Bottom bar is not in focus after clicking Enter
- Badly formed links on pages can no longer crash the browser
## [1.0.0] - 2020-06-18

View File

@ -49,8 +49,8 @@ Features in *italics* are in the master branch, but not in the latest release.
- [x] Basic forward/backward history, for each tab
- [x] Input (Status Code 10 & 11)
- [x] *Multiple charset support (over 55)*
- [x] *Built-in search using GUS*
- [ ] Bookmarks
- [x] *Built-in search (uses GUS by default)*
- [x] *Bookmarks*
- [ ] Search in pages with <kbd>Ctrl-F</kbd>
- [ ] Download pages and arbitrary data
- [ ] Emoji favicons

60
bookmarks/bookmarks.go Normal file
View File

@ -0,0 +1,60 @@
package bookmarks
import (
"encoding/base32"
"strings"
"github.com/makeworld-the-better-one/amfora/config"
)
var bkmkStore = config.BkmkStore
// bkmkKey returns the viper key for the given bookmark URL.
// Note that URLs are the keys, NOT the bookmark name.
func bkmkKey(url string) string {
// Keys are base32 encoded URLs to prevent any bad chars like periods from being used
return "bookmarks." + base32.StdEncoding.EncodeToString([]byte(url))
}
func Set(url, name string) {
bkmkStore.Set(bkmkKey(url), name)
bkmkStore.WriteConfig()
}
// Get returns the NAME of the bookmark, given the URL.
// It also returns a bool indicating whether it exists.
func Get(url string) (string, bool) {
name := bkmkStore.GetString(bkmkKey(url))
return name, name != ""
}
func Remove(url string) {
// XXX: Viper can't actually delete keys, which means the bookmarks file might get clouded
// with non-entries over time.
bkmkStore.Set(bkmkKey(url), "")
bkmkStore.WriteConfig()
}
// All returns all the bookmarks in a map of URLs to names.
func All() map[string]string {
ret := make(map[string]string)
bkmksMap, ok := bkmkStore.AllSettings()["bookmarks"].(map[string]interface{})
if !ok {
// No bookmarks stored yet, return empty map
return ret
}
for b32Url, name := range bkmksMap {
if n, ok := name.(string); n == "" || !ok {
// name is not a string, or it's empty - ignore
continue
}
url, err := base32.StdEncoding.DecodeString(strings.ToUpper(b32Url))
if err != nil {
// This would only happen if a user messed around with the bookmarks file
continue
}
ret[string(url)] = name.(string)
}
return ret
}

5
cache/cache.go vendored
View File

@ -4,6 +4,7 @@ package cache
import (
"net/url"
"strings"
"sync"
"github.com/makeworld-the-better-one/amfora/structs"
@ -47,8 +48,8 @@ func removeUrl(url string) {
// If your page is larger than the max cache size, the provided page
// will silently not be added to the cache.
func Add(p *structs.Page) {
if p.Url == "" {
// Just in case, don't waste cache on new tab page
if p.Url == "" || strings.HasPrefix(p.Url, "about:") {
// Just in case, these pages shouldn't be cached
return
}
// Never cache pages with query strings, to reduce unexpected behaviour

View File

@ -19,12 +19,17 @@ var TofuStore = viper.New()
var tofuDBDir string
var tofuDBPath string
// Bookmarks
var BkmkStore = viper.New()
var bkmkDir string
var bkmkPath string
func Init() error {
home, err := homedir.Dir()
if err != nil {
panic(err)
}
// Cache AppData path
// Store AppData path
if runtime.GOOS == "windows" {
appdata, ok := os.LookupEnv("APPDATA")
if ok {
@ -33,7 +38,8 @@ func Init() error {
amforaAppData = filepath.Join(home, filepath.FromSlash("AppData/Roaming/amfora/"))
}
}
// Cache config directory and file paths
// Store config directory and file paths
if runtime.GOOS == "windows" {
configDir = amforaAppData
} else {
@ -48,7 +54,7 @@ func Init() error {
}
configPath = filepath.Join(configDir, "config.toml")
// Cache TOFU db directory and file paths
// Store TOFU db directory and file paths
if runtime.GOOS == "windows" {
// Windows just stores it in APPDATA along with other stuff
tofuDBDir = amforaAppData
@ -64,6 +70,25 @@ func Init() error {
}
tofuDBPath = filepath.Join(tofuDBDir, "tofu.toml")
// Store bookmarks dir and path
if runtime.GOOS == "windows" {
// Windows just keeps it in APPDATA along with other Amfora files
bkmkDir = amforaAppData
} else {
// XDG data dir on POSIX systems
xdg_data, ok := os.LookupEnv("XDG_DATA_HOME")
if ok && strings.TrimSpace(xdg_data) != "" {
bkmkDir = filepath.Join(xdg_data, "amfora")
} else {
// Default to ~/.local/share/amfora
bkmkDir = filepath.Join(home, ".local", "share", "amfora")
}
}
bkmkPath = filepath.Join(bkmkDir, "bookmarks.toml")
// Create necessary files and folders
// Config
err = os.MkdirAll(configDir, 0755)
if err != nil {
return err
@ -78,12 +103,20 @@ func Init() error {
}
f.Close()
}
// TOFU
err = os.MkdirAll(tofuDBDir, 0755)
if err != nil {
return err
}
os.OpenFile(tofuDBPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
// Bookmarks
err = os.MkdirAll(bkmkDir, 0755)
if err != nil {
return err
}
os.OpenFile(bkmkPath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
// Setup vipers
TofuStore.SetConfigFile(tofuDBPath)
TofuStore.SetConfigType("toml")
@ -92,6 +125,18 @@ func Init() error {
return err
}
BkmkStore.SetConfigFile(bkmkPath)
BkmkStore.SetConfigType("toml")
err = BkmkStore.ReadInConfig()
if err != nil {
return err
}
BkmkStore.Set("DO NOT TOUCH", true)
err = BkmkStore.WriteConfig()
if err != nil {
return err
}
viper.SetDefault("a-general.home", "gemini.circumlunar.space")
viper.SetDefault("a-general.http", "default")
viper.SetDefault("a-general.search", "gus.guru/search")

View File

@ -9,7 +9,7 @@ var defaultConf = []byte(`# This is the default config file.
# gemini://example.com
# //example.com
# example.com
# example.com:1901
# example.com:123
[a-general]
home = "gemini://gemini.circumlunar.space"
@ -23,16 +23,10 @@ http = "default"
search = "gemini://gus.guru/search" # Any URL that will accept a query string can be put here
color = true # Whether colors will be used in the terminal
bullets = true # Whether to replace list asterisks with unicode bullets
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
left_margin = 0.15
max_width = 100 # The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
[bookmarks]
# Make sure to quote the key names if you edit this part yourself
# Example:
# "CAPCOM" = "gemini://gemini.circumlunar.space/capcom/"
# Options for page cache - which is only for text/gemini pages
# Increase the cache size to speed up browsing at the expense of memory
[cache]

View File

@ -6,7 +6,7 @@
# gemini://example.com
# //example.com
# example.com
# example.com:1901
# example.com:123
[a-general]
home = "gemini://gemini.circumlunar.space"
@ -20,16 +20,10 @@ http = "default"
search = "gemini://gus.guru/search" # Any URL that will accept a query string can be put here
color = true # Whether colors will be used in the terminal
bullets = true # Whether to replace list asterisks with unicode bullets
# A number from 0 to 1, indicating what percentage of the terminal width the left margin should take up.
left_margin = 0.15
max_width = 100 # The max number of columns to wrap a page's text to. Preformatted blocks are not wrapped.
[bookmarks]
# Make sure to quote the key names if you edit this part yourself
# Example:
# "CAPCOM" = "gemini://gemini.circumlunar.space/capcom/"
# Options for page cache - which is only for text/gemini pages
# Increase the cache size to speed up browsing at the expense of memory
[cache]

120
display/bookmarks.go Normal file
View File

@ -0,0 +1,120 @@
package display
import (
"fmt"
"strconv"
"strings"
"github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/bookmarks"
"gitlab.com/tslocum/cview"
)
// For adding and removing bookmarks, basically a clone of the input modal.
var bkmkModal = cview.NewModal().
SetBackgroundColor(tcell.ColorTeal).
SetButtonBackgroundColor(tcell.ColorNavy).
SetButtonTextColor(tcell.ColorWhite).
SetTextColor(tcell.ColorWhite)
// bkmkCh is for the user action
var bkmkCh = make(chan int) // 1, 0, -1 for add/update, cancel, and remove
var bkmkModalText string // The current text of the input field in the modal
func bkmkInit() {
bkmkModal.SetBorder(true)
bkmkModal.SetBorderColor(tcell.ColorWhite)
bkmkModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
switch buttonLabel {
case "Add":
bkmkCh <- 1
case "Change":
bkmkCh <- 1
case "Remove":
bkmkCh <- -1
case "Cancel":
bkmkCh <- 0
}
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - handled in bkmk()
})
bkmkModal.GetFrame().SetTitleColor(tcell.ColorWhite)
bkmkModal.GetFrame().SetTitleAlign(cview.AlignCenter)
bkmkModal.GetFrame().SetTitle(" Add Bookmark ")
}
// Bkmk displays the "Add a bookmark" modal.
// It accepts the default value for the bookmark name that will be displayed, but can be changed by the user.
// It also accepts a bool indicating whether this page already has a bookmark.
// It returns the bookmark name and the bookmark action:
// 1, 0, -1 for add/update, cancel, and remove
func openBkmkModal(name string, exists bool) (string, int) {
// Basically a copy of Input()
// Remove and re-add input field - to clear the old text
if bkmkModal.GetForm().GetFormItemCount() > 0 {
bkmkModal.GetForm().RemoveFormItem(0)
}
bkmkModalText = ""
bkmkModal.GetForm().AddInputField("Name: ", name, 0, nil,
func(text string) {
// Store for use later
bkmkModalText = text
})
bkmkModal.ClearButtons()
if exists {
bkmkModal.SetText("Change or remove the bookmark for the current page?")
bkmkModal.AddButtons([]string{"Change", "Remove", "Cancel"})
} else {
bkmkModal.SetText("Create a bookmark for the current page?")
bkmkModal.AddButtons([]string{"Add", "Cancel"})
}
tabPages.ShowPage("bkmk")
tabPages.SendToFront("bkmk")
App.SetFocus(bkmkModal)
App.Draw()
action := <-bkmkCh
tabPages.SwitchToPage(strconv.Itoa(curTab))
return bkmkModalText, action
}
// Bookmarks displays the bookmarks page on the current tab.
func Bookmarks() {
// Gather bookmarks
rawContent := "# Bookmarks\r\n\r\n"
for url, name := range bookmarks.All() {
rawContent += fmt.Sprintf("=> %s %s\r\n", url, name)
}
// Render and display
content, links := renderer.RenderGemini(rawContent, textWidth())
page := structs.Page{Content: content, Links: links, Url: "about:bookmarks"}
setPage(&page)
}
// addBookmark goes through the process of adding a bookmark for the current page.
// It is the high-level way of doing it. It should be called in a goroutine.
// It can also be called to edit an existing bookmark.
func addBookmark() {
if !strings.HasPrefix(tabMap[curTab].Url, "gemini://") {
// Can't make bookmarks for other kinds of URLs
return
}
name, exists := bookmarks.Get(tabMap[curTab].Url)
// Open a bookmark modal with the current name of the bookmark, if it exists
newName, action := openBkmkModal(name, exists)
switch action {
case 1:
// Add/change the bookmark
bookmarks.Set(tabMap[curTab].Url, newName)
case -1:
bookmarks.Remove(tabMap[curTab].Url)
}
// Other case is action = 0, meaning "Cancel", so nothing needs to happen
}

View File

@ -5,13 +5,11 @@ import (
"strconv"
"strings"
"github.com/spf13/viper"
"github.com/makeworld-the-better-one/amfora/renderer"
"github.com/gdamore/tcell"
"github.com/makeworld-the-better-one/amfora/cache"
"github.com/makeworld-the-better-one/amfora/renderer"
"github.com/makeworld-the-better-one/amfora/structs"
"github.com/spf13/viper"
"gitlab.com/tslocum/cview"
)
@ -28,24 +26,13 @@ var bottomBar = cview.NewInputField().
SetFieldTextColor(tcell.ColorBlack).
SetLabelColor(tcell.ColorGreen)
var helpTable = cview.NewTable().
SetSelectable(false, false).
SetFixed(1, 2).
SetBorders(true).
SetBordersColor(tcell.ColorGray)
// Viewer for the tab primitives
// Pages are named as strings of tab numbers - so the textview for the first tab
// is held in the page named "0".
// The only pages that don't confine to this scheme named after the modals above,
// which is used to draw modals on top the current tab.
// The only pages that don't confine to this scheme are those named after modals,
// which are used to draw modals on top the current tab.
// Ex: "info", "error", "input", "yesno"
var tabPages = cview.NewPages().
AddPage("help", helpTable, true, false).
AddPage("info", infoModal, false, false).
AddPage("error", errorModal, false, false).
AddPage("input", inputModal, false, false).
AddPage("yesno", yesNoModal, false, false)
var tabPages = cview.NewPages()
// The tabs at the top with titles
var tabRow = cview.NewTextView().
@ -116,15 +103,17 @@ func Init() {
if c == 0 {
tableCell = cview.NewTableCell(cells[cell]).
SetAttributes(tcell.AttrBold).
SetExpansion(1)
SetExpansion(1).
SetAlign(cview.AlignCenter)
} else {
tableCell = cview.NewTableCell(cells[cell]).
tableCell = cview.NewTableCell(" " + cells[cell]).
SetExpansion(2)
}
helpTable.SetCell(r, c, tableCell)
cell++
}
}
tabPages.AddPage("help", helpTable, true, false)
bottomBar.SetBackgroundColor(tcell.ColorWhite)
bottomBar.SetDoneFunc(func(key tcell.Key) {
@ -147,7 +136,7 @@ func Init() {
if err != nil {
// It's a full URL or search term
// Detect if it's a search or URL
if strings.Contains(query, " ") || (!strings.Contains(query, "//") && !strings.Contains(query, ".")) {
if strings.Contains(query, " ") || (!strings.Contains(query, "//") && !strings.Contains(query, ".") && !strings.HasPrefix(query, "about:")) {
URL(viper.GetString("a-general.search") + "?" + pathEscape(query))
} else {
// Full URL
@ -178,7 +167,7 @@ func Init() {
// Render the default new tab content ONCE and store it for later
renderedNewTabContent, newTabLinks = renderer.RenderGemini(newTabContent, textWidth())
newTabPage = structs.Page{Content: renderedNewTabContent, Links: newTabLinks}
newTabPage = structs.Page{Content: renderedNewTabContent, Links: newTabLinks, Url: "about:newtab"}
modalInit()
@ -212,6 +201,13 @@ func Init() {
case tcell.KeyCtrlQ:
Stop()
return nil
case tcell.KeyCtrlB:
Bookmarks()
addToHist("about:bookmarks")
return nil
case tcell.KeyCtrlD:
go addBookmark()
return nil
case tcell.KeyRune:
// Regular key was sent
switch string(event.Rune()) {
@ -422,6 +418,22 @@ func Reload() {
// URL loads and handles the provided URL for the current tab.
// It should be an absolute URL.
func URL(u string) {
// Some code is copied in followLink()
if u == "about:bookmarks" {
Bookmarks()
addToHist("about:bookmarks")
return
}
if u == "about:newtab" {
setPage(&newTabPage)
return
}
if strings.HasPrefix(u, "about:") {
Error("Error", "Not a valid 'about:' URL.")
return
}
go func() {
final, displayed := handleURL(u)
if displayed {
@ -433,10 +445,3 @@ func URL(u string) {
func NumTabs() int {
return len(tabViews)
}
// Help displays the help and keybindings.
func Help() {
helpTable.ScrollToBeginning()
tabPages.SwitchToPage("help")
App.Draw()
}

View File

@ -1,6 +1,11 @@
package display
import "strings"
import (
"strings"
"github.com/gdamore/tcell"
"gitlab.com/tslocum/cview"
)
var helpCells = strings.TrimSpace(`
?|Bring up this help.
@ -8,17 +13,32 @@ Esc|Leave the help
Arrow keys, h/j/k/l|Scroll and move a page.
Tab|Navigate to the next item in a popup.
Shift-Tab|Navigate to the previous item in a popup.
Ctrl-H|Go home
Ctrl-T|New tab
Ctrl-W|Close tab. For now, only the right-most tab can be closed.
b|Go back a page
f|Go forward a page
g|Go to top of document
G|Go to bottom of document
spacebar|Open bar at the bottom - type a URL or link number
Enter|On a page this will start link highlighting. Press Tab and Shift-Tab to pick different links. Press enter again to go to one.
Ctrl-R, R|Reload a page. This also clears the cache.
q, Ctrl-Q|Quit
Shift-NUMBER|Go to a specific tab.
Shift-0, )|Go to the last tab.
Ctrl-H|Go home
Ctrl-T|New tab
Ctrl-W|Close tab. For now, only the right-most tab can be closed.
Ctrl-R, R|Reload a page, discarding the cached version.
Ctrl-B|View bookmarks
Ctrl-D|Add, change, or remove a bookmark for the current page.
q, Ctrl-Q|Quit
`)
var helpTable = cview.NewTable().
SetSelectable(false, false).
SetFixed(1, 2).
SetBorders(true).
SetBordersColor(tcell.ColorGray)
// Help displays the help and keybindings.
func Help() {
helpTable.ScrollToBeginning()
tabPages.SwitchToPage("help")
App.Draw()
}

View File

@ -9,7 +9,8 @@ import (
"gitlab.com/tslocum/cview"
)
// This file contains code for all the popups / modals used in the display
// This file contains code for the popups / modals used in the display.
// The bookmark modal is in bookmarks.go
var infoModal = cview.NewModal().
SetBackgroundColor(tcell.ColorGray).
@ -45,20 +46,30 @@ var yesNoModal = cview.NewModal().
var yesNoCh = make(chan bool)
func modalInit() {
tabPages.AddPage("info", infoModal, false, false).
AddPage("error", errorModal, false, false).
AddPage("input", inputModal, false, false).
AddPage("yesno", yesNoModal, false, false).
AddPage("bkmk", bkmkModal, false, false)
// Modal functions that can't be added up above, because they return the wrong type
infoModal.SetBorder(true)
infoModal.SetBorderColor(tcell.ColorWhite)
infoModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
})
infoModal.GetFrame().SetTitleColor(tcell.ColorWhite)
infoModal.GetFrame().SetTitleAlign(cview.AlignCenter)
infoModal.GetFrame().SetTitle(" Info ")
errorModal.SetBorder(true)
errorModal.SetBorderColor(tcell.ColorWhite)
errorModal.GetFrame().SetTitleColor(tcell.ColorWhite)
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
errorModal.SetDoneFunc(func(buttonIndex int, buttonLabel string) {
tabPages.SwitchToPage(strconv.Itoa(curTab))
})
errorModal.GetFrame().SetTitleColor(tcell.ColorWhite)
errorModal.GetFrame().SetTitleAlign(cview.AlignCenter)
inputModal.SetBorder(true)
inputModal.SetBorderColor(tcell.ColorWhite)
@ -72,6 +83,9 @@ func modalInit() {
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - handled in Input()
})
inputModal.GetFrame().SetTitleColor(tcell.ColorWhite)
inputModal.GetFrame().SetTitleAlign(cview.AlignCenter)
inputModal.GetFrame().SetTitle(" Input ")
yesNoModal.SetBorder(true)
yesNoModal.SetBorderColor(tcell.ColorWhite)
@ -84,6 +98,10 @@ func modalInit() {
//tabPages.SwitchToPage(strconv.Itoa(curTab)) - Handled in YesNo()
})
yesNoModal.GetFrame().SetTitleColor(tcell.ColorWhite)
yesNoModal.GetFrame().SetTitleAlign(cview.AlignCenter)
bkmkInit()
}
// Error displays an error on the screen in a modal.
@ -157,7 +175,7 @@ func YesNo(prompt string) bool {
// Tofu displays the TOFU warning modal.
// It returns a bool indicating whether the user wants to continue.
func Tofu(host string) bool {
// Reuses yesno modal, with error colour
// Reuses yesNoModal, with error colour
yesNoModal.SetBackgroundColor(tcell.ColorMaroon)
yesNoModal.SetText(

View File

@ -8,6 +8,8 @@ Press the ? key at any time to bring up the help, and see other keybindings. Mos
Happy browsing!
=> //gemini.circumlunar.space Gemini homepage
=> about:bookmarks Bookmarks
=> //gemini.circumlunar.space Project Gemini
=> https://github.com/makeworld-the-better-one/amfora Amfora homepage [HTTPS]
`

View File

@ -61,6 +61,10 @@ func tabHasContent() bool {
// Likely the default content page
return false
}
if strings.HasPrefix(tabMap[curTab].Url, "about:") {
return false
}
_, ok := tabMap[curTab]
return ok // If there's a page, return true
}
@ -84,16 +88,44 @@ func applyScroll() {
// followLink should be used when the user "clicks" a link on a page.
// Not when a URL is opened on a new tab for the first time.
func followLink(prev, next string) {
saveScroll() // Likely called later on anyway, here just in case
prevParsed, _ := url.Parse(prev)
nextParsed, err := url.Parse(next)
// Copied from URL()
if next == "about:bookmarks" {
Bookmarks()
addToHist("about:bookmarks")
return
}
if strings.HasPrefix(next, "about:") {
Error("Error", "Not a valid 'about:' URL for linking")
return
}
if tabHasContent() {
saveScroll() // Likely called later on anyway, here just in case
prevParsed, _ := url.Parse(prev)
nextParsed, err := url.Parse(next)
if err != nil {
Error("URL Error", "Link URL could not be parsed")
return
}
nextURL := prevParsed.ResolveReference(nextParsed).String()
go func() {
final, displayed := handleURL(nextURL)
if displayed {
addToHist(final)
}
}()
return
}
// No content on current tab, so the "prev" URL is not valid.
// An example is the about:newtab page
_, err := url.Parse(next)
if err != nil {
Error("URL Error", "Link URL could not be parsed")
return
}
nextURL := prevParsed.ResolveReference(nextParsed).String()
go func() {
final, displayed := handleURL(nextURL)
final, displayed := handleURL(next)
if displayed {
addToHist(final)
}
@ -112,15 +144,9 @@ func addLeftMargin(text string) string {
func setPage(p *structs.Page) {
saveScroll() // Save the scroll of the previous page
if !p.Displayable {
// Add margin to page based on terminal width
p.Content = addLeftMargin(p.Content)
p.Displayable = true
}
// Change page on screen
tabMap[curTab] = p
tabViews[curTab].SetText(p.Content)
tabViews[curTab].SetText(addLeftMargin(p.Content))
tabViews[curTab].Highlight("") // Turn off highlights
tabViews[curTab].ScrollToBeginning()
@ -144,12 +170,14 @@ func handleURL(u string) (string, bool) {
App.SetFocus(tabViews[curTab])
//logger.Log.Printf("Sent: %s", u)
// To allow linking to the bookmarks page, and history browsing
if u == "about:bookmarks" {
Bookmarks()
return "about:bookmarks", true
}
u = normalizeURL(u)
//logger.Log.Printf("Normalized: %s", u)
parsed, err := url.Parse(u)
if err != nil {
Error("URL Error", err.Error())

16
go.sum
View File

@ -32,20 +32,17 @@ github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606 h1:Y00kKKKYVyn7InlCMRcnZbwcjHFIsgkjU0Bn1F5re4o=
github.com/gdamore/tcell v1.3.1-0.20200608133353-cb1e5d6fa606/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
@ -143,7 +140,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -152,7 +148,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
@ -183,25 +178,20 @@ github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIK
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -279,7 +269,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -288,7 +277,6 @@ golang.org/x/sys v0.0.0-20200610111108-226ff32320da h1:bGb80FudwxpeucJUjPYJXuJ8H
golang.org/x/sys v0.0.0-20200610111108-226ff32320da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
@ -334,23 +322,19 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8 h1:jL/vaozO53FMfZLySWM+4nulF3gQEC6q5jH90LPomDo=
gopkg.in/yaml.v3 v3.0.0-20200603094226-e3079894b1e8/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -71,7 +71,8 @@ func convertRegularGemini(s string, numLinks int, width int) (string, []string)
links = append(links, url)
if viper.GetBool("a-general.color") {
if pU, _ := urlPkg.Parse(url); pU.Scheme == "" || pU.Scheme == "gemini" {
pU, err := urlPkg.Parse(url)
if err == nil && (pU.Scheme == "" || pU.Scheme == "gemini" || pU.Scheme == "about") {
// A gemini link
// Add the link text in blue (in a region), and a gray link number to the left of it
lines[i] = `[silver::b][` + strconv.Itoa(numLinks+len(links)) + "[]" + "[-::-] " +

View File

@ -2,12 +2,12 @@ package structs
// Page is for storing UTF-8 text/gemini pages, as well as text/plain pages.
type Page struct {
Url string
Content string // The processed content, NOT raw. Uses cview colour tags. All link/link texts must have region tags.
Links []string // URLs, for each region in the content.
Row int // Scroll position
Column int // ditto
Displayable bool // Set to true once the content has been modified to display nicely on the screen - margins added
Url string
Content string // The processed content, NOT raw. Uses cview colour tags. All link/link texts must have region tags.
Links []string // URLs, for each region in the content.
Row int // Scroll position
Column int // ditto
//Displayable bool // Set to true once the content has been modified to display nicely on the screen - margins added
}
// Size returns an approx. size of a Page in bytes.