Add pagination

This commit is contained in:
makeworld 2020-12-06 20:57:57 -05:00
parent 1a2fba92c2
commit dd7550dffb
10 changed files with 152 additions and 61 deletions

View File

@ -1,6 +1,6 @@
# Notes
## Subscriptions (temp)
## Subscriptions
- TODO: remove all logger lines
## Issues

View File

@ -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
- [x] Subscriptions
- 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
- [ ] Table of contents for pages
- [ ] History browser

View File

@ -237,6 +237,7 @@ func Init() error {
viper.SetDefault("subscriptions.popup", true)
viper.SetDefault("subscriptions.update_interval", 1800)
viper.SetDefault("subscriptions.workers", 3)
viper.SetDefault("subscriptions.entries_per_page", 20)
viper.SetConfigFile(configPath)
viper.SetConfigType("toml")

View File

@ -153,6 +153,9 @@ update_interval = 1800 # 30 mins
# update times. Any value below 1 will be corrected to 1.
workers = 3
# The number of subscription updates displayed per page.
entries_per_page = 20
[theme]
# This section is for changing the COLORS used in Amfora.

View File

@ -150,6 +150,9 @@ update_interval = 1800 # 30 mins
# update times. Any value below 1 will be corrected to 1.
workers = 3
# The number of subscription updates displayed per page.
entries_per_page = 20
[theme]
# This section is for changing the COLORS used in Amfora.

View File

@ -294,7 +294,7 @@ func Init() {
}
return nil
case tcell.KeyCtrlA:
Subscriptions(tabs[curTab])
Subscriptions(tabs[curTab], "about:subscriptions")
tabs[curTab].addToHistory("about:subscriptions")
return nil
case tcell.KeyCtrlX:
@ -578,8 +578,8 @@ func Reload() {
func URL(u string) {
t := tabs[curTab]
if strings.HasPrefix(u, "about:") {
if ok := handleAbout(t, u); ok {
t.addToHistory(u)
if final, ok := handleAbout(t, u); ok {
t.addToHistory(final)
}
return
}

View File

@ -169,34 +169,40 @@ func handleFavicon(t *tab, host, old string) {
//
// It does not add the displayed page to history.
//
// It returns a bool indicating if the provided URL could be handled.
func handleAbout(t *tab, u string) bool {
// It returns the URL displayed, and a bool indicating if the provided
// 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:") {
return false
return "", false
}
switch u {
case "about:bookmarks":
Bookmarks(t)
return true
case "about:subscriptions":
Subscriptions(t)
return true
return u, true
case "about:newtab":
temp := newTabPage // Copy
setPage(t, &temp)
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?") {
ManageSubscriptions(t, u)
// 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.")
return false
return "", false
}
// 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)
if strings.HasPrefix(u, "about:") {
return ret(u, handleAbout(t, u))
return ret(handleAbout(t, u))
}
u = normalizeURL(u)

View File

@ -20,8 +20,8 @@ import (
// It will handle setting the bottomBar.
func followLink(t *tab, prev, next string) {
if strings.HasPrefix(next, "about:") {
if ok := handleAbout(t, next); ok {
t.addToHistory(next)
if final, ok := handleAbout(t, next); ok {
t.addToHistory(final)
}
return
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"net/url"
"path"
"sort"
"strconv"
"strings"
"time"
@ -20,7 +21,9 @@ import (
"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,
// but converts to the local time first.
@ -30,54 +33,125 @@ func toLocalDay(t time.Time) time.Time {
}
// 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")
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
p, ok := cache.GetPage("about:subscriptions")
if subscriptionPageUpdated.After(subscriptions.LastUpdated) && ok {
p, ok := cache.GetPage(u)
if subscriptionPageUpdated[pageN].After(subscriptions.LastUpdated) && ok {
logger.Log.Println("using cached subscriptions page")
setPage(t, p)
t.applyBottomBar()
return
return u
}
logger.Log.Println("started rendering subscriptions page")
rawPage := "# Subscriptions\n\n" +
"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" +
"=> about:manage-subscriptions Manage subscriptions\n"
// curDay represents what day of posts the loop is on.
// It only goes backwards in time.
// Its initial setting means:
// Only display posts older than 26 hours in the future, nothing further in the future.
//
// 26 hours was chosen because it is the largest timezone difference
// currently in the world. Posts may be dated in the future
// due to software bugs, where the local user's date is used, but
// the UTC timezone is specified. I believe gemfeed does this.
curDay := toLocalDay(time.Now()).Add(26 * time.Hour)
pe := subscriptions.GetPageEntries()
for _, entry := range pe.Entries { // From new to old
// Convert to local time, remove sub-day info
pub := toLocalDay(entry.Published)
// Figure out where the entries for this page start, if at all.
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)
}
if pub.Before(curDay) {
// This post is on a new day, add a day header
curDay = pub
rawPage += fmt.Sprintf("\n## %s\n\n", curDay.Format("Jan 02, 2006"))
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.
// It only goes backwards in time.
// Its initial setting means:
// Only display posts older than 26 hours in the future, nothing further in the future.
//
// 26 hours was chosen because it is the largest timezone difference
// currently in the world. Posts may be dated in the future
// due to software bugs, where the local user's date is used, but
// 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)
for _, entry := range pe.Entries[start:end] { // From new to old
// Convert to local time, remove sub-day info
pub := toLocalDay(entry.Published)
if pub.Before(curDay) {
// This post is on a new day, add a day header
curDay = pub
rawPage += fmt.Sprintf("\n## %s\n\n", curDay.Format("Jan 02, 2006"))
}
if entry.Title == "" || entry.Title == "/" {
// Just put author/title
// Mainly used for when you're tracking the root domain of a site
rawPage += fmt.Sprintf("=>%s %s\n", entry.URL, entry.Prefix)
} else {
// Include title and dash
rawPage += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Prefix, entry.Title)
}
}
if entry.Title == "" || entry.Title == "/" {
// Just put author/title
// Mainly used for when you're tracking the root domain of a site
rawPage += fmt.Sprintf("=>%s %s\n", entry.URL, entry.Prefix)
} else {
// Include title and dash
rawPage += fmt.Sprintf("=>%s %s - %s\n", entry.URL, entry.Prefix, entry.Title)
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)
}
}
}
@ -86,7 +160,7 @@ func Subscriptions(t *tab) {
Raw: rawPage,
Content: content,
Links: links,
URL: "about:subscriptions",
URL: u,
Width: termW,
Mediatype: structs.TextGemini,
}
@ -94,9 +168,11 @@ func Subscriptions(t *tab) {
setPage(t, &page)
t.applyBottomBar()
subscriptionPageUpdated = time.Now()
subscriptionPageUpdated[pageN] = time.Now()
logger.Log.Println("done rendering subscriptions page")
return u
}
// ManageSubscriptions displays the subscription managing page in
@ -109,9 +185,13 @@ func ManageSubscriptions(t *tab, u string) {
}
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(
"=>%s %s\n",
"about:manage-subscriptions?"+gemini.QueryEscape(u2),

View File

@ -11,7 +11,6 @@ import (
"os"
"path"
"reflect"
"sort"
"strings"
"sync"
"time"
@ -370,7 +369,7 @@ func updateAll() {
wg.Wait()
}
// AllURLs returns all the subscribed-to URLS, sorted alphabetically.
// AllURLs returns all the subscribed-to URLS.
func AllURLS() []string {
data.RLock()
defer data.RUnlock()
@ -386,7 +385,6 @@ func AllURLS() []string {
i++
}
sort.Strings(urls)
return urls
}
@ -394,7 +392,7 @@ func AllURLS() []string {
// The URL must be provided. It will do nothing if the URL is
// 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 {
data.Lock()
// Just delete from both instead of using a loop to find it