1
0
mirror of https://github.com/makew0rld/amfora.git synced 2024-12-04 14:46:29 -05:00

Open link in new tab - fixes #27

This commit is contained in:
makeworld 2020-07-01 13:39:13 -04:00
parent 8cfff2296f
commit 581b498a0f
5 changed files with 116 additions and 49 deletions

View File

@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Paging, using <kbd>d</kbd> and <kbd>u</kbd>, as well as <kbd>Page Up</kbd> and <kbd>Page Down</kbd> (#19) - Paging, using <kbd>d</kbd> and <kbd>u</kbd>, as well as <kbd>Page Up</kbd> and <kbd>Page Down</kbd> (#19)
- <kbd>Esc</kbd> can exit link highlighting mode (#24) - <kbd>Esc</kbd> can exit link highlighting mode (#24)
- Selected link URL is displayed in the bottom bar (#24) - Selected link URL is displayed in the bottom bar (#24)
- Pressing <kbd>Ctrl-T</kbd> with a link selected opens it in a new tab (#27)
- Writing `new:N` in the bottom bar will open link number N in a new tab (#27)
### Changed ### Changed
- Bottom bar now says `URL/Num./Search: ` when space is pressed - Bottom bar now says `URL/Num./Search: ` when space is pressed
@ -22,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Performance when loading very long cached pages improved (#26) - Performance when loading very long cached pages improved (#26)
- Doesn't crash when wrapping certain complex lines (#20) - Doesn't crash when wrapping certain complex lines (#20)
- Input fields are always in focus when they appear (#5) - Input fields are always in focus when they appear (#5)
- Reloading the new tab page doesn't cause an error popup
## [1.1.0] - 2020-06-24 ## [1.1.0] - 2020-06-24
### Added ### Added

View File

@ -61,6 +61,7 @@ Features in *italics* are in the master branch, but not in the latest release.
- [ ] Download pages and arbitrary data - [ ] Download pages and arbitrary data
- [ ] Emoji favicons - [ ] Emoji favicons
- See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details - See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details
- [ ] Stream support
- [ ] Full mouse support - [ ] Full mouse support
- [ ] Table of contents for pages - [ ] Table of contents for pages
- [ ] Full client certificate UX within the client - [ ] Full client certificate UX within the client

View File

@ -24,6 +24,10 @@ var tabViews = make(map[int]*cview.TextView)
var termW int var termW int
var termH int var termH int
// The link currently selected when in link selection mode
// Set to "" when not in that mode
var selectedLink string
// The user input and URL display bar at the bottom // The user input and URL display bar at the bottom
var bottomBar = cview.NewInputField(). var bottomBar = cview.NewInputField().
SetFieldBackgroundColor(tcell.ColorWhite). SetFieldBackgroundColor(tcell.ColorWhite).
@ -91,34 +95,7 @@ func Init() {
App.Draw() App.Draw()
}) })
// Populate help table helpInit()
helpTable.SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEsc {
tabPages.SwitchToPage(strconv.Itoa(curTab))
}
})
rows := strings.Count(helpCells, "\n") + 1
cells := strings.Split(
strings.ReplaceAll(helpCells, "\n", "|"),
"|")
cell := 0
for r := 0; r < rows; r++ {
for c := 0; c < 2; c++ {
var tableCell *cview.TableCell
if c == 0 {
tableCell = cview.NewTableCell(cells[cell]).
SetAttributes(tcell.AttrBold).
SetExpansion(1).
SetAlign(cview.AlignCenter)
} else {
tableCell = cview.NewTableCell(" " + cells[cell]).
SetExpansion(2)
}
helpTable.SetCell(r, c, tableCell)
cell++
}
}
tabPages.AddPage("help", helpTable, true, false)
if viper.GetBool("a-general.color") { if viper.GetBool("a-general.color") {
bottomBar.SetLabelColor(tcell.ColorGreen) bottomBar.SetLabelColor(tcell.ColorGreen)
@ -142,7 +119,7 @@ func Init() {
App.SetFocus(tabViews[curTab]) App.SetFocus(tabViews[curTab])
return return
} }
if query == ".." && !strings.HasPrefix(query, "about:") { if query == ".." && tabHasContent() {
// Go up a directory // Go up a directory
parsed, err := url.Parse(tabMap[curTab].Url) parsed, err := url.Parse(tabMap[curTab].Url)
if err != nil { if err != nil {
@ -168,22 +145,44 @@ func Init() {
i, err := strconv.Atoi(query) i, err := strconv.Atoi(query)
if err != nil { if err != nil {
// It's a full URL or search term if strings.HasPrefix(query, "new:") && len(query) > 4 {
// Detect if it's a search or URL // They're trying to open a link number in a new tab
if strings.Contains(query, " ") || (!strings.Contains(query, "//") && !strings.Contains(query, ".") && !strings.HasPrefix(query, "about:")) { i, err = strconv.Atoi(query[4:])
URL(viper.GetString("a-general.search") + "?" + pathEscape(query)) if err != nil {
return
}
if i <= len(tabMap[curTab].Links) && i > 0 {
// Open new tab and load link
oldTab := curTab
NewTab()
// Resolve and follow link manually
prevParsed, _ := url.Parse(tabMap[oldTab].Url)
nextParsed, err := url.Parse(tabMap[oldTab].Links[i-1])
if err != nil {
Error("URL Error", "link URL could not be parsed")
return
}
URL(prevParsed.ResolveReference(nextParsed).String())
return
}
} else { } else {
// Full URL // It's a full URL or search term
URL(query) // Detect if it's a search or URL
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
URL(query)
}
return
} }
return
} }
if i <= len(tabMap[curTab].Links) && i > 0 { if i <= len(tabMap[curTab].Links) && i > 0 {
// It's a valid link number // It's a valid link number
followLink(tabMap[curTab].Url, tabMap[curTab].Links[i-1]) followLink(tabMap[curTab].Url, tabMap[curTab].Links[i-1])
return return
} }
// Invalid link number // Invalid link number, don't do anything
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabMap[curTab].Url)
App.SetFocus(tabViews[curTab]) App.SetFocus(tabViews[curTab])
@ -229,7 +228,15 @@ func Init() {
switch event.Key() { switch event.Key() {
case tcell.KeyCtrlT: case tcell.KeyCtrlT:
NewTab() if selectedLink != "" {
next, err := resolveRelLink(tabMap[curTab].Url, selectedLink)
if err != nil {
Error("URL Error", err.Error())
return nil
}
NewTab()
URL(next)
}
return nil return nil
case tcell.KeyCtrlW: case tcell.KeyCtrlW:
CloseTab() CloseTab()
@ -360,6 +367,7 @@ func NewTab() {
tabViews[curTab].Highlight("") tabViews[curTab].Highlight("")
bottomBar.SetLabel("") bottomBar.SetLabel("")
bottomBar.SetText(tabMap[curTab].Url) bottomBar.SetText(tabMap[curTab].Url)
selectedLink = ""
} }
currentSelection := tabViews[curTab].GetHighlights() currentSelection := tabViews[curTab].GetHighlights()
@ -369,6 +377,7 @@ func NewTab() {
if len(currentSelection) > 0 && len(tabMap[curTab].Links) > 0 { if len(currentSelection) > 0 && len(tabMap[curTab].Links) > 0 {
// A link was selected, "click" it and load the page it's for // A link was selected, "click" it and load the page it's for
bottomBar.SetLabel("") bottomBar.SetLabel("")
selectedLink = ""
linkN, _ := strconv.Atoi(currentSelection[0]) linkN, _ := strconv.Atoi(currentSelection[0])
followLink(tabMap[curTab].Url, tabMap[curTab].Links[linkN]) followLink(tabMap[curTab].Url, tabMap[curTab].Links[linkN])
return return
@ -377,6 +386,7 @@ func NewTab() {
// Display link URL in bottomBar // Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]") bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabMap[curTab].Links[0]) bottomBar.SetText(tabMap[curTab].Links[0])
selectedLink = tabMap[curTab].Links[0]
} }
} else if len(currentSelection) > 0 { } else if len(currentSelection) > 0 {
// There's still a selection, but a different key was pressed, not Enter // There's still a selection, but a different key was pressed, not Enter
@ -393,6 +403,7 @@ func NewTab() {
// Display link URL in bottomBar // Display link URL in bottomBar
bottomBar.SetLabel("[::b]Link: [::-]") bottomBar.SetLabel("[::b]Link: [::-]")
bottomBar.SetText(tabMap[curTab].Links[index]) bottomBar.SetText(tabMap[curTab].Links[index])
selectedLink = tabMap[curTab].Links[index]
} }
}) })
@ -494,6 +505,10 @@ func SwitchTab(tab int) {
} }
func Reload() { func Reload() {
if !tabHasContent() {
return
}
cache.Remove(tabMap[curTab].Url) cache.Remove(tabMap[curTab].Url)
tabMap[curTab].LeftMargin = 0 // Redo left margin tabMap[curTab].LeftMargin = 0 // Redo left margin
go handleURL(tabMap[curTab].Url) go handleURL(tabMap[curTab].Url)

View File

@ -1,6 +1,7 @@
package display package display
import ( import (
"strconv"
"strings" "strings"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
@ -13,16 +14,16 @@ 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.
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 a page b, Alt-Left|Go back in the history
f, Alt-Right|Go forward a page f, Alt-Right|Go forward in the history
g|Go to top of document g|Go to top of document
G|Go to bottom of document G|Go to bottom of document
spacebar|Open bar at the bottom - type a URL, link number, or search term. You can also type two dots (..) to go up a directory in the URL. spacebar|Open bar at the bottom - type a URL, link number, or search term. You can also type two dots (..) to go up a directory in the URL, as well as new:N to open link number N in a new tab instead of the current one.
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, or Esc to stop. 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, or Esc to stop.
Shift-NUMBER|Go to a specific tab. Shift-NUMBER|Go to a specific tab.
Shift-0, )|Go to the last tab. Shift-0, )|Go to the last tab.
Ctrl-H|Go home Ctrl-H|Go home
Ctrl-T|New tab Ctrl-T|New tab, or if a link is selected, this will open the link in a new tab.
Ctrl-W|Close tab. For now, only the right-most tab can be closed. 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-R, R|Reload a page, discarding the cached version.
Ctrl-B|View bookmarks Ctrl-B|View bookmarks
@ -32,7 +33,6 @@ q, Ctrl-Q, Ctrl-C|Quit
var helpTable = cview.NewTable(). var helpTable = cview.NewTable().
SetSelectable(false, false). SetSelectable(false, false).
SetFixed(1, 2).
SetBorders(true). SetBorders(true).
SetBordersColor(tcell.ColorGray) SetBordersColor(tcell.ColorGray)
@ -42,3 +42,34 @@ func Help() {
tabPages.SwitchToPage("help") tabPages.SwitchToPage("help")
App.Draw() App.Draw()
} }
func helpInit() {
// Populate help table
helpTable.SetDoneFunc(func(key tcell.Key) {
if key == tcell.KeyEsc {
tabPages.SwitchToPage(strconv.Itoa(curTab))
}
})
rows := strings.Count(helpCells, "\n") + 1
cells := strings.Split(
strings.ReplaceAll(helpCells, "\n", "|"),
"|")
cell := 0
for r := 0; r < rows; r++ {
for c := 0; c < 2; c++ {
var tableCell *cview.TableCell
if c == 0 {
tableCell = cview.NewTableCell(cells[cell]).
SetAttributes(tcell.AttrBold).
SetAlign(cview.AlignCenter)
} else {
tableCell = cview.NewTableCell(" " + cells[cell])
}
helpTable.SetCell(r, c, tableCell)
cell++
}
}
tabPages.AddPage("help", helpTable, true, false)
}
// TODO: Wrap cell text so it's not offscreen

View File

@ -1,6 +1,7 @@
package display package display
import ( import (
"errors"
"net/url" "net/url"
"os/exec" "os/exec"
"strings" "strings"
@ -97,6 +98,22 @@ func applyScroll() {
tabViews[curTab].ScrollTo(tabMap[curTab].Row, tabMap[curTab].Column) tabViews[curTab].ScrollTo(tabMap[curTab].Row, tabMap[curTab].Column)
} }
// resolveRelLink returns an absolute link for the given absolute link and relative one.
// It also returns an error if it could not resolve the links, which should be displayed
// to the user.
func resolveRelLink(prev, next string) (string, error) {
if !tabHasContent() {
return next, nil
}
prevParsed, _ := url.Parse(prev)
nextParsed, err := url.Parse(next)
if err != nil {
return "", errors.New("link URL could not be parsed")
}
return prevParsed.ResolveReference(nextParsed).String(), nil
}
// followLink should be used when the user "clicks" a link on a page. // 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. // Not when a URL is opened on a new tab for the first time.
func followLink(prev, next string) { func followLink(prev, next string) {
@ -113,14 +130,12 @@ func followLink(prev, next string) {
} }
if tabHasContent() { if tabHasContent() {
saveScroll() // Likely called later on anyway, here just in case saveScroll() // Likely called later on, it's here just in case
prevParsed, _ := url.Parse(prev) nextURL, err := resolveRelLink(prev, next)
nextParsed, err := url.Parse(next)
if err != nil { if err != nil {
Error("URL Error", "Link URL could not be parsed") Error("URL Error", err.Error())
return return
} }
nextURL := prevParsed.ResolveReference(nextParsed).String()
go func() { go func() {
final, displayed := handleURL(nextURL) final, displayed := handleURL(nextURL)
if displayed { if displayed {
@ -161,7 +176,9 @@ func setLeftMargin(p *structs.Page) {
// Old margin needs to be removed, new one added // Old margin needs to be removed, new one added
lines := strings.Split(p.Content, "\n") lines := strings.Split(p.Content, "\n")
for i := range lines { for i := range lines {
shifted += strings.Repeat(" ", lM) + lines[i][p.LeftMargin:] + "\n" if lines[i] != "" {
shifted += strings.Repeat(" ", lM) + lines[i][p.LeftMargin:] + "\n"
}
} }
} }
p.Content = shifted p.Content = shifted