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:
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)
|
- 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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user