mirror of
https://github.com/makeworld-the-better-one/amfora.git
synced 2024-09-11 14:55:51 +03:00
✨ Open link in new tab - fixes #27
This commit is contained in:
parent
8cfff2296f
commit
581b498a0f
@ -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)
|
||||
- <kbd>Esc</kbd> can exit link highlighting mode (#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
|
||||
- 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)
|
||||
- Doesn't crash when wrapping certain complex lines (#20)
|
||||
- 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
|
||||
### Added
|
||||
|
@ -61,6 +61,7 @@ Features in *italics* are in the master branch, but not in the latest release.
|
||||
- [ ] Download pages and arbitrary data
|
||||
- [ ] Emoji favicons
|
||||
- See `gemini://mozz.us/files/rfc_gemini_favicon.gmi` for details
|
||||
- [ ] Stream support
|
||||
- [ ] Full mouse support
|
||||
- [ ] Table of contents for pages
|
||||
- [ ] Full client certificate UX within the client
|
||||
|
@ -24,6 +24,10 @@ var tabViews = make(map[int]*cview.TextView)
|
||||
var termW 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
|
||||
var bottomBar = cview.NewInputField().
|
||||
SetFieldBackgroundColor(tcell.ColorWhite).
|
||||
@ -91,34 +95,7 @@ func Init() {
|
||||
App.Draw()
|
||||
})
|
||||
|
||||
// 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).
|
||||
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)
|
||||
helpInit()
|
||||
|
||||
if viper.GetBool("a-general.color") {
|
||||
bottomBar.SetLabelColor(tcell.ColorGreen)
|
||||
@ -142,7 +119,7 @@ func Init() {
|
||||
App.SetFocus(tabViews[curTab])
|
||||
return
|
||||
}
|
||||
if query == ".." && !strings.HasPrefix(query, "about:") {
|
||||
if query == ".." && tabHasContent() {
|
||||
// Go up a directory
|
||||
parsed, err := url.Parse(tabMap[curTab].Url)
|
||||
if err != nil {
|
||||
@ -168,22 +145,44 @@ func Init() {
|
||||
|
||||
i, err := strconv.Atoi(query)
|
||||
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, ".") && !strings.HasPrefix(query, "about:")) {
|
||||
URL(viper.GetString("a-general.search") + "?" + pathEscape(query))
|
||||
if strings.HasPrefix(query, "new:") && len(query) > 4 {
|
||||
// They're trying to open a link number in a new tab
|
||||
i, err = strconv.Atoi(query[4:])
|
||||
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 {
|
||||
// Full URL
|
||||
URL(query)
|
||||
// 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, ".") && !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 {
|
||||
// It's a valid link number
|
||||
followLink(tabMap[curTab].Url, tabMap[curTab].Links[i-1])
|
||||
return
|
||||
}
|
||||
// Invalid link number
|
||||
// Invalid link number, don't do anything
|
||||
bottomBar.SetText(tabMap[curTab].Url)
|
||||
App.SetFocus(tabViews[curTab])
|
||||
|
||||
@ -229,7 +228,15 @@ func Init() {
|
||||
|
||||
switch event.Key() {
|
||||
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
|
||||
case tcell.KeyCtrlW:
|
||||
CloseTab()
|
||||
@ -360,6 +367,7 @@ func NewTab() {
|
||||
tabViews[curTab].Highlight("")
|
||||
bottomBar.SetLabel("")
|
||||
bottomBar.SetText(tabMap[curTab].Url)
|
||||
selectedLink = ""
|
||||
}
|
||||
|
||||
currentSelection := tabViews[curTab].GetHighlights()
|
||||
@ -369,6 +377,7 @@ func NewTab() {
|
||||
if len(currentSelection) > 0 && len(tabMap[curTab].Links) > 0 {
|
||||
// A link was selected, "click" it and load the page it's for
|
||||
bottomBar.SetLabel("")
|
||||
selectedLink = ""
|
||||
linkN, _ := strconv.Atoi(currentSelection[0])
|
||||
followLink(tabMap[curTab].Url, tabMap[curTab].Links[linkN])
|
||||
return
|
||||
@ -377,6 +386,7 @@ func NewTab() {
|
||||
// Display link URL in bottomBar
|
||||
bottomBar.SetLabel("[::b]Link: [::-]")
|
||||
bottomBar.SetText(tabMap[curTab].Links[0])
|
||||
selectedLink = tabMap[curTab].Links[0]
|
||||
}
|
||||
} else if len(currentSelection) > 0 {
|
||||
// There's still a selection, but a different key was pressed, not Enter
|
||||
@ -393,6 +403,7 @@ func NewTab() {
|
||||
// Display link URL in bottomBar
|
||||
bottomBar.SetLabel("[::b]Link: [::-]")
|
||||
bottomBar.SetText(tabMap[curTab].Links[index])
|
||||
selectedLink = tabMap[curTab].Links[index]
|
||||
}
|
||||
})
|
||||
|
||||
@ -494,6 +505,10 @@ func SwitchTab(tab int) {
|
||||
}
|
||||
|
||||
func Reload() {
|
||||
if !tabHasContent() {
|
||||
return
|
||||
}
|
||||
|
||||
cache.Remove(tabMap[curTab].Url)
|
||||
tabMap[curTab].LeftMargin = 0 // Redo left margin
|
||||
go handleURL(tabMap[curTab].Url)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gdamore/tcell"
|
||||
@ -13,16 +14,16 @@ 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.
|
||||
b, Alt-Left|Go back a page
|
||||
f, Alt-Right|Go forward a page
|
||||
b, Alt-Left|Go back 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, 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.
|
||||
Shift-NUMBER|Go to a specific tab.
|
||||
Shift-0, )|Go to the last tab.
|
||||
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-R, R|Reload a page, discarding the cached version.
|
||||
Ctrl-B|View bookmarks
|
||||
@ -32,7 +33,6 @@ q, Ctrl-Q, Ctrl-C|Quit
|
||||
|
||||
var helpTable = cview.NewTable().
|
||||
SetSelectable(false, false).
|
||||
SetFixed(1, 2).
|
||||
SetBorders(true).
|
||||
SetBordersColor(tcell.ColorGray)
|
||||
|
||||
@ -42,3 +42,34 @@ func Help() {
|
||||
tabPages.SwitchToPage("help")
|
||||
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
|
||||
|
@ -1,6 +1,7 @@
|
||||
package display
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strings"
|
||||
@ -97,6 +98,22 @@ func applyScroll() {
|
||||
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.
|
||||
// Not when a URL is opened on a new tab for the first time.
|
||||
func followLink(prev, next string) {
|
||||
@ -113,14 +130,12 @@ func followLink(prev, next string) {
|
||||
}
|
||||
|
||||
if tabHasContent() {
|
||||
saveScroll() // Likely called later on anyway, here just in case
|
||||
prevParsed, _ := url.Parse(prev)
|
||||
nextParsed, err := url.Parse(next)
|
||||
saveScroll() // Likely called later on, it's here just in case
|
||||
nextURL, err := resolveRelLink(prev, next)
|
||||
if err != nil {
|
||||
Error("URL Error", "Link URL could not be parsed")
|
||||
Error("URL Error", err.Error())
|
||||
return
|
||||
}
|
||||
nextURL := prevParsed.ResolveReference(nextParsed).String()
|
||||
go func() {
|
||||
final, displayed := handleURL(nextURL)
|
||||
if displayed {
|
||||
@ -161,7 +176,9 @@ func setLeftMargin(p *structs.Page) {
|
||||
// Old margin needs to be removed, new one added
|
||||
lines := strings.Split(p.Content, "\n")
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user