mirror of
https://github.com/makeworld-the-better-one/amfora.git
synced 2024-11-22 15:46:51 +03:00
Add pagination
This commit is contained in:
parent
1a2fba92c2
commit
dd7550dffb
2
NOTES.md
2
NOTES.md
@ -1,6 +1,6 @@
|
||||
# Notes
|
||||
|
||||
## Subscriptions (temp)
|
||||
## Subscriptions
|
||||
- TODO: remove all logger lines
|
||||
|
||||
## 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
|
||||
- [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
|
||||
|
@ -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")
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user