mirror of
https://github.com/makew0rld/amfora.git
synced 2025-06-30 22:18:12 -04:00
Add pagination
This commit is contained in:
parent
1a2fba92c2
commit
dd7550dffb
2
NOTES.md
2
NOTES.md
@ -1,6 +1,6 @@
|
|||||||
# Notes
|
# Notes
|
||||||
|
|
||||||
## Subscriptions (temp)
|
## Subscriptions
|
||||||
- TODO: remove all logger lines
|
- TODO: remove all logger lines
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
|
@ -130,7 +130,7 @@ Features in *italics* are in the master branch, but not in the latest release.
|
|||||||
- https://lists.orbitalfox.eu/archives/gemini/2020/001400.html
|
- https://lists.orbitalfox.eu/archives/gemini/2020/001400.html
|
||||||
- [x] Subscriptions
|
- [x] Subscriptions
|
||||||
- RSS, Atom, and [JSON Feeds](https://jsonfeed.org/) are all supported
|
- RSS, Atom, and [JSON Feeds](https://jsonfeed.org/) are all supported
|
||||||
- So is tracking any page to be notified when it changes
|
- So is tracking a page, to know when its content changes
|
||||||
- [ ] Stream support
|
- [ ] Stream support
|
||||||
- [ ] Table of contents for pages
|
- [ ] Table of contents for pages
|
||||||
- [ ] History browser
|
- [ ] History browser
|
||||||
|
@ -237,6 +237,7 @@ func Init() error {
|
|||||||
viper.SetDefault("subscriptions.popup", true)
|
viper.SetDefault("subscriptions.popup", true)
|
||||||
viper.SetDefault("subscriptions.update_interval", 1800)
|
viper.SetDefault("subscriptions.update_interval", 1800)
|
||||||
viper.SetDefault("subscriptions.workers", 3)
|
viper.SetDefault("subscriptions.workers", 3)
|
||||||
|
viper.SetDefault("subscriptions.entries_per_page", 20)
|
||||||
|
|
||||||
viper.SetConfigFile(configPath)
|
viper.SetConfigFile(configPath)
|
||||||
viper.SetConfigType("toml")
|
viper.SetConfigType("toml")
|
||||||
|
@ -153,6 +153,9 @@ update_interval = 1800 # 30 mins
|
|||||||
# update times. Any value below 1 will be corrected to 1.
|
# update times. Any value below 1 will be corrected to 1.
|
||||||
workers = 3
|
workers = 3
|
||||||
|
|
||||||
|
# The number of subscription updates displayed per page.
|
||||||
|
entries_per_page = 20
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
|
@ -150,6 +150,9 @@ update_interval = 1800 # 30 mins
|
|||||||
# update times. Any value below 1 will be corrected to 1.
|
# update times. Any value below 1 will be corrected to 1.
|
||||||
workers = 3
|
workers = 3
|
||||||
|
|
||||||
|
# The number of subscription updates displayed per page.
|
||||||
|
entries_per_page = 20
|
||||||
|
|
||||||
|
|
||||||
[theme]
|
[theme]
|
||||||
# This section is for changing the COLORS used in Amfora.
|
# This section is for changing the COLORS used in Amfora.
|
||||||
|
@ -294,7 +294,7 @@ func Init() {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
case tcell.KeyCtrlA:
|
case tcell.KeyCtrlA:
|
||||||
Subscriptions(tabs[curTab])
|
Subscriptions(tabs[curTab], "about:subscriptions")
|
||||||
tabs[curTab].addToHistory("about:subscriptions")
|
tabs[curTab].addToHistory("about:subscriptions")
|
||||||
return nil
|
return nil
|
||||||
case tcell.KeyCtrlX:
|
case tcell.KeyCtrlX:
|
||||||
@ -578,8 +578,8 @@ func Reload() {
|
|||||||
func URL(u string) {
|
func URL(u string) {
|
||||||
t := tabs[curTab]
|
t := tabs[curTab]
|
||||||
if strings.HasPrefix(u, "about:") {
|
if strings.HasPrefix(u, "about:") {
|
||||||
if ok := handleAbout(t, u); ok {
|
if final, ok := handleAbout(t, u); ok {
|
||||||
t.addToHistory(u)
|
t.addToHistory(final)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -169,34 +169,40 @@ func handleFavicon(t *tab, host, old string) {
|
|||||||
//
|
//
|
||||||
// It does not add the displayed page to history.
|
// It does not add the displayed page to history.
|
||||||
//
|
//
|
||||||
// It returns a bool indicating if the provided URL could be handled.
|
// It returns the URL displayed, and a bool indicating if the provided
|
||||||
func handleAbout(t *tab, u string) bool {
|
// URL could be handled. The string returned will always be empty
|
||||||
|
// if the bool is false.
|
||||||
|
func handleAbout(t *tab, u string) (string, bool) {
|
||||||
if !strings.HasPrefix(u, "about:") {
|
if !strings.HasPrefix(u, "about:") {
|
||||||
return false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
switch u {
|
switch u {
|
||||||
case "about:bookmarks":
|
case "about:bookmarks":
|
||||||
Bookmarks(t)
|
Bookmarks(t)
|
||||||
return true
|
return u, true
|
||||||
case "about:subscriptions":
|
|
||||||
Subscriptions(t)
|
|
||||||
return true
|
|
||||||
case "about:newtab":
|
case "about:newtab":
|
||||||
temp := newTabPage // Copy
|
temp := newTabPage // Copy
|
||||||
setPage(t, &temp)
|
setPage(t, &temp)
|
||||||
t.applyBottomBar()
|
t.applyBottomBar()
|
||||||
return true
|
return u, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u == "about:subscriptions" || (len(u) > 20 && u[:20] == "about:subscriptions?") {
|
||||||
|
// about:subscriptions?2 views page 2
|
||||||
|
return Subscriptions(t, u), true
|
||||||
|
}
|
||||||
if u == "about:manage-subscriptions" || (len(u) > 27 && u[:27] == "about:manage-subscriptions?") {
|
if u == "about:manage-subscriptions" || (len(u) > 27 && u[:27] == "about:manage-subscriptions?") {
|
||||||
ManageSubscriptions(t, u)
|
ManageSubscriptions(t, u)
|
||||||
// Don't count remove command in history
|
// Don't count remove command in history
|
||||||
return u == "about:manage-subscriptions"
|
if u == "about:manage-subscriptions" {
|
||||||
|
return u, true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
Error("Error", "Not a valid 'about:' URL.")
|
Error("Error", "Not a valid 'about:' URL.")
|
||||||
return false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleURL displays whatever action is needed for the provided URL,
|
// handleURL displays whatever action is needed for the provided URL,
|
||||||
@ -252,7 +258,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
|
|||||||
App.SetFocus(t.view)
|
App.SetFocus(t.view)
|
||||||
|
|
||||||
if strings.HasPrefix(u, "about:") {
|
if strings.HasPrefix(u, "about:") {
|
||||||
return ret(u, handleAbout(t, u))
|
return ret(handleAbout(t, u))
|
||||||
}
|
}
|
||||||
|
|
||||||
u = normalizeURL(u)
|
u = normalizeURL(u)
|
||||||
|
@ -20,8 +20,8 @@ import (
|
|||||||
// It will handle setting the bottomBar.
|
// It will handle setting the bottomBar.
|
||||||
func followLink(t *tab, prev, next string) {
|
func followLink(t *tab, prev, next string) {
|
||||||
if strings.HasPrefix(next, "about:") {
|
if strings.HasPrefix(next, "about:") {
|
||||||
if ok := handleAbout(t, next); ok {
|
if final, ok := handleAbout(t, next); ok {
|
||||||
t.addToHistory(next)
|
t.addToHistory(final)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,7 +21,9 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var subscriptionPageUpdated time.Time
|
// Map page number (zero-indexed) to the time it was made at.
|
||||||
|
// This allows for caching the pages until there's an update.
|
||||||
|
var subscriptionPageUpdated = make(map[int]time.Time)
|
||||||
|
|
||||||
// toLocalDay truncates the provided time to a date only,
|
// toLocalDay truncates the provided time to a date only,
|
||||||
// but converts to the local time first.
|
// but converts to the local time first.
|
||||||
@ -30,24 +33,79 @@ func toLocalDay(t time.Time) time.Time {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subscriptions displays the subscriptions page on the current tab.
|
// Subscriptions displays the subscriptions page on the current tab.
|
||||||
func Subscriptions(t *tab) {
|
func Subscriptions(t *tab, u string) string {
|
||||||
logger.Log.Println("display.Subscriptions called")
|
logger.Log.Println("display.Subscriptions called")
|
||||||
|
|
||||||
|
pageN := 0 // Pages are zero-indexed internally
|
||||||
|
|
||||||
|
// Correct URL if query string exists
|
||||||
|
// The only valid query string is an int above 1.
|
||||||
|
// Anything "redirects" to the first page, with no query string.
|
||||||
|
// This is done over just serving the first page content for
|
||||||
|
// invalid query strings so that there won't be duplicate caches.
|
||||||
|
correctURL := func(u2 string) string {
|
||||||
|
if len(u2) > 20 && u2[:20] == "about:subscriptions?" {
|
||||||
|
query, err := gemini.QueryUnescape(u2[20:])
|
||||||
|
if err != nil {
|
||||||
|
return "about:subscriptions"
|
||||||
|
}
|
||||||
|
// Valid query string
|
||||||
|
i, err := strconv.Atoi(query)
|
||||||
|
if err != nil {
|
||||||
|
// Not an int
|
||||||
|
return "about:subscriptions"
|
||||||
|
}
|
||||||
|
if i < 2 {
|
||||||
|
return "about:subscriptions"
|
||||||
|
}
|
||||||
|
// Valid int above 1
|
||||||
|
pageN = i - 1 // Pages are zero-indexed internally
|
||||||
|
return u2
|
||||||
|
}
|
||||||
|
return u2
|
||||||
|
}
|
||||||
|
u = correctURL(u)
|
||||||
|
|
||||||
// Retrieve cached version if there hasn't been any updates
|
// Retrieve cached version if there hasn't been any updates
|
||||||
p, ok := cache.GetPage("about:subscriptions")
|
p, ok := cache.GetPage(u)
|
||||||
if subscriptionPageUpdated.After(subscriptions.LastUpdated) && ok {
|
if subscriptionPageUpdated[pageN].After(subscriptions.LastUpdated) && ok {
|
||||||
logger.Log.Println("using cached subscriptions page")
|
logger.Log.Println("using cached subscriptions page")
|
||||||
setPage(t, p)
|
setPage(t, p)
|
||||||
t.applyBottomBar()
|
t.applyBottomBar()
|
||||||
return
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Log.Println("started rendering subscriptions page")
|
logger.Log.Println("started rendering subscriptions page")
|
||||||
|
|
||||||
rawPage := "# Subscriptions\n\n" +
|
pe := subscriptions.GetPageEntries()
|
||||||
"See the help (by pressing ?) for details on how to use this page.\n\n" +
|
|
||||||
"If you just opened Amfora then updates will appear incrementally. Reload the page to see them.\n\n" +
|
// Figure out where the entries for this page start, if at all.
|
||||||
"=> about:manage-subscriptions Manage subscriptions\n"
|
epp := viper.GetInt("subscriptions.entries_per_page")
|
||||||
|
if epp <= 0 {
|
||||||
|
epp = 1
|
||||||
|
}
|
||||||
|
start := pageN * epp // Index of the first page entry to be displayed
|
||||||
|
end := start + epp
|
||||||
|
if end > len(pe.Entries) {
|
||||||
|
end = len(pe.Entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawPage string
|
||||||
|
if pageN == 0 {
|
||||||
|
rawPage = "# Subscriptions\n\n" + rawPage
|
||||||
|
} else {
|
||||||
|
rawPage = fmt.Sprintf("# Subscriptions (page %d)\n\n", pageN+1) + rawPage
|
||||||
|
}
|
||||||
|
|
||||||
|
if start > len(pe.Entries)-1 && len(pe.Entries) != 0 {
|
||||||
|
// The page is out of range, doesn't exist
|
||||||
|
rawPage += "This page does not exist.\n\n=> about:subscriptions Subscriptions\n"
|
||||||
|
} else {
|
||||||
|
// Render page
|
||||||
|
|
||||||
|
rawPage += "You can use Ctrl-X to subscribe to a page, or to an Atom/RSS/JSON feed. See the online wiki for more.\n" +
|
||||||
|
"If you just opened Amfora then updates may appear incrementally. Reload the page to see them.\n\n" +
|
||||||
|
"=> about:manage-subscriptions Manage subscriptions\n\n"
|
||||||
|
|
||||||
// curDay represents what day of posts the loop is on.
|
// curDay represents what day of posts the loop is on.
|
||||||
// It only goes backwards in time.
|
// It only goes backwards in time.
|
||||||
@ -57,12 +115,12 @@ func Subscriptions(t *tab) {
|
|||||||
// 26 hours was chosen because it is the largest timezone difference
|
// 26 hours was chosen because it is the largest timezone difference
|
||||||
// currently in the world. Posts may be dated in the future
|
// currently in the world. Posts may be dated in the future
|
||||||
// due to software bugs, where the local user's date is used, but
|
// due to software bugs, where the local user's date is used, but
|
||||||
// the UTC timezone is specified. I believe gemfeed does this.
|
// the UTC timezone is specified. Gemfeed does this at the time of
|
||||||
|
// writing, but will not after #3 gets merged on its repo. Still,
|
||||||
|
// the older version will be used for a while.
|
||||||
curDay := toLocalDay(time.Now()).Add(26 * time.Hour)
|
curDay := toLocalDay(time.Now()).Add(26 * time.Hour)
|
||||||
|
|
||||||
pe := subscriptions.GetPageEntries()
|
for _, entry := range pe.Entries[start:end] { // From new to old
|
||||||
|
|
||||||
for _, entry := range pe.Entries { // From new to old
|
|
||||||
// Convert to local time, remove sub-day info
|
// Convert to local time, remove sub-day info
|
||||||
pub := toLocalDay(entry.Published)
|
pub := toLocalDay(entry.Published)
|
||||||
|
|
||||||
@ -81,12 +139,28 @@ func Subscriptions(t *tab) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pageN == 0 && len(pe.Entries) > epp {
|
||||||
|
// First page, and there's more than can fit
|
||||||
|
rawPage += "\n\n=> about:subscriptions?2 Next Page\n"
|
||||||
|
} else if pageN > 0 {
|
||||||
|
// A later page
|
||||||
|
rawPage += fmt.Sprintf(
|
||||||
|
"\n\n=> about:subscriptions?%d Previous Page\n",
|
||||||
|
pageN, // pageN is zero-indexed but the query string is one-indexed
|
||||||
|
)
|
||||||
|
if end != len(pe.Entries)-1 {
|
||||||
|
// There's more
|
||||||
|
rawPage += fmt.Sprintf("=> about:subscriptions?%d Next Page\n", pageN+2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
content, links := renderer.RenderGemini(rawPage, textWidth(), leftMargin(), false)
|
content, links := renderer.RenderGemini(rawPage, textWidth(), leftMargin(), false)
|
||||||
page := structs.Page{
|
page := structs.Page{
|
||||||
Raw: rawPage,
|
Raw: rawPage,
|
||||||
Content: content,
|
Content: content,
|
||||||
Links: links,
|
Links: links,
|
||||||
URL: "about:subscriptions",
|
URL: u,
|
||||||
Width: termW,
|
Width: termW,
|
||||||
Mediatype: structs.TextGemini,
|
Mediatype: structs.TextGemini,
|
||||||
}
|
}
|
||||||
@ -94,9 +168,11 @@ func Subscriptions(t *tab) {
|
|||||||
setPage(t, &page)
|
setPage(t, &page)
|
||||||
t.applyBottomBar()
|
t.applyBottomBar()
|
||||||
|
|
||||||
subscriptionPageUpdated = time.Now()
|
subscriptionPageUpdated[pageN] = time.Now()
|
||||||
|
|
||||||
logger.Log.Println("done rendering subscriptions page")
|
logger.Log.Println("done rendering subscriptions page")
|
||||||
|
|
||||||
|
return u
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManageSubscriptions displays the subscription managing page in
|
// ManageSubscriptions displays the subscription managing page in
|
||||||
@ -109,9 +185,13 @@ func ManageSubscriptions(t *tab, u string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rawPage := "# Manage Subscriptions\n\n" +
|
rawPage := "# Manage Subscriptions\n\n" +
|
||||||
"Below is list of URLs, both feeds and pages. Navigate to the link to unsubscribe from that feed or page.\n\n"
|
"Below is list of URLs you are subscribed to, both feeds and pages. " +
|
||||||
|
"Navigate to the link to unsubscribe from that feed or page.\n\n"
|
||||||
|
|
||||||
for _, u2 := range subscriptions.AllURLS() {
|
urls := subscriptions.AllURLS()
|
||||||
|
sort.Strings(urls)
|
||||||
|
|
||||||
|
for _, u2 := range urls {
|
||||||
rawPage += fmt.Sprintf(
|
rawPage += fmt.Sprintf(
|
||||||
"=>%s %s\n",
|
"=>%s %s\n",
|
||||||
"about:manage-subscriptions?"+gemini.QueryEscape(u2),
|
"about:manage-subscriptions?"+gemini.QueryEscape(u2),
|
||||||
|
@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -370,7 +369,7 @@ func updateAll() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
// AllURLs returns all the subscribed-to URLS, sorted alphabetically.
|
// AllURLs returns all the subscribed-to URLS.
|
||||||
func AllURLS() []string {
|
func AllURLS() []string {
|
||||||
data.RLock()
|
data.RLock()
|
||||||
defer data.RUnlock()
|
defer data.RUnlock()
|
||||||
@ -386,7 +385,6 @@ func AllURLS() []string {
|
|||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(urls)
|
|
||||||
return urls
|
return urls
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -394,7 +392,7 @@ func AllURLS() []string {
|
|||||||
// The URL must be provided. It will do nothing if the URL is
|
// The URL must be provided. It will do nothing if the URL is
|
||||||
// not an actual subscription.
|
// not an actual subscription.
|
||||||
//
|
//
|
||||||
// It returns any errors that occured when saving to disk.
|
// It returns any errors that occurred when saving to disk.
|
||||||
func Remove(u string) error {
|
func Remove(u string) error {
|
||||||
data.Lock()
|
data.Lock()
|
||||||
// Just delete from both instead of using a loop to find it
|
// Just delete from both instead of using a loop to find it
|
||||||
|
Loading…
x
Reference in New Issue
Block a user