added topic traversal (Read New/Next Topic), untested
This commit is contained in:
+34
-3
@@ -100,6 +100,9 @@ func Topics(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
return ui.ErrorPage(ctxt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
traverser := ui.NewTopicTraverser(topics)
|
||||||
|
ctxt.SetSession("topic.traverser", traverser)
|
||||||
|
|
||||||
tz := prefs.Location()
|
tz := prefs.Location()
|
||||||
loc := prefs.Localizer()
|
loc := prefs.Localizer()
|
||||||
fdate := make([]string, len(topics))
|
fdate := make([]string, len(topics))
|
||||||
@@ -107,10 +110,17 @@ func Topics(ctxt ui.AmContext) (string, any, error) {
|
|||||||
fdate[i] = loc.Strftime("%x %X", t.LastUpdate.In(tz))
|
fdate[i] = loc.Strftime("%x %X", t.LastUpdate.In(tz))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create the "read new" URL
|
||||||
|
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.URLParam("confid"))
|
||||||
|
firstTopic := traverser.FirstTopic()
|
||||||
|
if firstTopic >= 1 {
|
||||||
|
ctxt.VarMap().Set("urlReadNew", fmt.Sprintf("%s/r/%d", urlStem, firstTopic))
|
||||||
|
}
|
||||||
|
|
||||||
ctxt.VarMap().Set("canCreate", conf.TestPermission("Conference.Create", myLevel))
|
ctxt.VarMap().Set("canCreate", conf.TestPermission("Conference.Create", myLevel))
|
||||||
ctxt.VarMap().Set("conferenceName", conf.Name)
|
ctxt.VarMap().Set("conferenceName", conf.Name)
|
||||||
ctxt.VarMap().Set("urlBack", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
ctxt.VarMap().Set("urlBack", fmt.Sprintf("/comm/%s/conf", comm.Alias))
|
||||||
ctxt.VarMap().Set("urlStem", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.URLParam("confid")))
|
ctxt.VarMap().Set("urlStem", urlStem)
|
||||||
ctxt.VarMap().Set("permalink", "TODO")
|
ctxt.VarMap().Set("permalink", "TODO")
|
||||||
ctxt.VarMap().Set("view", view)
|
ctxt.VarMap().Set("view", view)
|
||||||
ctxt.VarMap().Set("sort", sort)
|
ctxt.VarMap().Set("sort", sort)
|
||||||
@@ -433,6 +443,12 @@ func templateOverrideLink(args jet.Arguments) reflect.Value {
|
|||||||
* Standard Go error status.
|
* Standard Go error status.
|
||||||
*/
|
*/
|
||||||
func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
// Extract the traverser first, so we can unclear it in background tasks.
|
||||||
|
var traverser ui.TopicTraverser = nil
|
||||||
|
if ctxt.IsSession("topic.traverser") {
|
||||||
|
traverser = ctxt.GetSession("topic.traverser").(ui.TopicTraverser)
|
||||||
|
}
|
||||||
|
|
||||||
// If we need to reset a topic's last read count (as with "Next & Keep New"), spin the task off.
|
// If we need to reset a topic's last read count (as with "Next & Keep New"), spin the task off.
|
||||||
if ctxt.HasParameter("rst") {
|
if ctxt.HasParameter("rst") {
|
||||||
rst := strings.Split(ctxt.Parameter("rst"), ",")
|
rst := strings.Split(ctxt.Parameter("rst"), ",")
|
||||||
@@ -445,6 +461,9 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
|||||||
topic, _ := database.AmGetTopic(ctx, int32(topicId))
|
topic, _ := database.AmGetTopic(ctx, int32(topicId))
|
||||||
if topic != nil {
|
if topic != nil {
|
||||||
topic.SetLastRead(ctx, user, int32(lastRead))
|
topic.SetLastRead(ctx, user, int32(lastRead))
|
||||||
|
if traverser != nil && int32(lastRead) < topic.TopMessage {
|
||||||
|
traverser.UnclearTopic(topic.Number)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -567,6 +586,18 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
|||||||
}
|
}
|
||||||
ctxt.VarMap().Set("advancedControls", advancedControls)
|
ctxt.VarMap().Set("advancedControls", advancedControls)
|
||||||
|
|
||||||
|
// Adjust the traverser and get the "next" link.
|
||||||
|
urlStem := fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias").(string))
|
||||||
|
if traverser != nil {
|
||||||
|
traverser.ClearTopic(topic.Number)
|
||||||
|
nextTopic := traverser.NextTopic(topic.Number)
|
||||||
|
if nextTopic >= 0 {
|
||||||
|
nextTopicURL := fmt.Sprintf("%s/r/%d", urlStem, nextTopic)
|
||||||
|
ctxt.VarMap().Set("urlNextTopic", nextTopicURL)
|
||||||
|
ctxt.VarMap().Set("urlNextKeepNew", fmt.Sprintf("%s?rst=%d,%d", nextTopicURL, topic.TopicId, lastRead))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Render the output.
|
// Render the output.
|
||||||
ctxt.VarMap().Set("amsterdam_pageTitle", fmt.Sprintf("%s: %s", topic.Name, summaryLine))
|
ctxt.VarMap().Set("amsterdam_pageTitle", fmt.Sprintf("%s: %s", topic.Name, summaryLine))
|
||||||
ctxt.VarMap().Set("topicName", topic.Name)
|
ctxt.VarMap().Set("topicName", topic.Name)
|
||||||
@@ -578,7 +609,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
|||||||
ctxt.VarMap().SetFunc("post_getOverrideLink", templateOverrideLink)
|
ctxt.VarMap().SetFunc("post_getOverrideLink", templateOverrideLink)
|
||||||
ctxt.VarMap().SetFunc("post_getText", templatePostText)
|
ctxt.VarMap().SetFunc("post_getText", templatePostText)
|
||||||
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
|
||||||
ctxt.VarMap().Set("post_stem", fmt.Sprintf("/comm/%s/conf/%s/r/%d", comm.Alias, ctxt.GetScratch("currentAlias").(string), topic.Number))
|
ctxt.VarMap().Set("post_stem", fmt.Sprintf("%s/r/%d", urlStem, topic.Number))
|
||||||
ctxt.VarMap().Set("post_max", topic.TopMessage)
|
ctxt.VarMap().Set("post_max", topic.TopMessage)
|
||||||
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", topicPostRef))
|
ctxt.VarMap().Set("post_topicPermalink", fmt.Sprintf("/go/%s", topicPostRef))
|
||||||
ctxt.VarMap().Set("posts", posts)
|
ctxt.VarMap().Set("posts", posts)
|
||||||
@@ -586,7 +617,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any, error) {
|
|||||||
ctxt.VarMap().Set("pin", pin)
|
ctxt.VarMap().Set("pin", pin)
|
||||||
ctxt.VarMap().Set("rangeEnd", postRange[1])
|
ctxt.VarMap().Set("rangeEnd", postRange[1])
|
||||||
ctxt.VarMap().Set("rangeStart", postRange[0])
|
ctxt.VarMap().Set("rangeStart", postRange[0])
|
||||||
ctxt.VarMap().Set("topicListLink", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias").(string)))
|
ctxt.VarMap().Set("topicListLink", urlStem)
|
||||||
ctxt.VarMap().Set("topicNum", topic.Number)
|
ctxt.VarMap().Set("topicNum", topic.Number)
|
||||||
if resetLastRead {
|
if resetLastRead {
|
||||||
user := ctxt.CurrentUser()
|
user := ctxt.CurrentUser()
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Amsterdam Web Communities System
|
||||||
|
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Package ui holds the support for the Amsterdam user interface, wrapping Echo and Jet templates.
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
|
"github.com/bits-and-blooms/bitset"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TopicTraverser is the data structure that allows us to navigate to "next" topics.
|
||||||
|
type TopicTraverser interface {
|
||||||
|
FirstTopic() int16
|
||||||
|
NextTopic(int16) int16
|
||||||
|
ClearTopic(int16)
|
||||||
|
UnclearTopic(int16)
|
||||||
|
}
|
||||||
|
|
||||||
|
// topicTraverser is the internal data structure that implements TopicTraverser.
|
||||||
|
type topicTraverser struct {
|
||||||
|
lock sync.RWMutex
|
||||||
|
topics []int16
|
||||||
|
active *bitset.BitSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTopicTraverser creates the traverser data structure from the topic listing.
|
||||||
|
func NewTopicTraverser(topics []*database.TopicSummary) TopicTraverser {
|
||||||
|
trav := topicTraverser{
|
||||||
|
topics: make([]int16, 0, len(topics)),
|
||||||
|
active: bitset.New(uint(len(topics))),
|
||||||
|
}
|
||||||
|
p := 0
|
||||||
|
for _, t := range topics {
|
||||||
|
if t.Unread > 0 {
|
||||||
|
trav.topics = append(trav.topics, t.Number)
|
||||||
|
trav.active.Set(uint(p))
|
||||||
|
p++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &trav
|
||||||
|
}
|
||||||
|
|
||||||
|
// FirstTopic returns the first unread topic number in the traverser.
|
||||||
|
func (trav *topicTraverser) FirstTopic() int16 {
|
||||||
|
trav.lock.RLock()
|
||||||
|
defer trav.lock.RUnlock()
|
||||||
|
i, b := trav.active.NextSet(0)
|
||||||
|
if b {
|
||||||
|
return trav.topics[i]
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextTopic returns the unread topic number in the traverser after the specified one.
|
||||||
|
func (trav *topicTraverser) NextTopic(cur int16) int16 {
|
||||||
|
trav.lock.RLock()
|
||||||
|
defer trav.lock.RUnlock()
|
||||||
|
seeking := false
|
||||||
|
for i, v := range trav.topics {
|
||||||
|
if v == cur {
|
||||||
|
seeking = true
|
||||||
|
} else if seeking && trav.active.Test(uint(i)) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trav.FirstTopic() // look from the beginning
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearTopic clears the specified topic number from the traverser.
|
||||||
|
func (trav *topicTraverser) ClearTopic(num int16) {
|
||||||
|
trav.lock.Lock()
|
||||||
|
defer trav.lock.Unlock()
|
||||||
|
for i, v := range trav.topics {
|
||||||
|
if v == num {
|
||||||
|
trav.active.Clear(uint(i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnclearTopic restores the specified topic number to the traverser.
|
||||||
|
func (trav *topicTraverser) UnclearTopic(num int16) {
|
||||||
|
trav.lock.Lock()
|
||||||
|
defer trav.lock.Unlock()
|
||||||
|
for i, v := range trav.topics {
|
||||||
|
if v == num {
|
||||||
|
trav.active.Set(uint(i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-6
@@ -24,13 +24,13 @@
|
|||||||
class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
||||||
{{ if isTopicHidden }}Show Topic{{ else }}Hide Topic{{ end }}
|
{{ if isTopicHidden }}Show Topic{{ else }}Hide Topic{{ end }}
|
||||||
</a>
|
</a>
|
||||||
{{ if false }}{* TODO *}
|
{{ if isset(urlNextTopic) }}
|
||||||
<a href="/TODO"
|
<a href="{{ urlNextTopic }}"
|
||||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Next Topic</a>
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Next Topic</a>
|
||||||
{{ if false }}{* TODO *}
|
{{ end }}
|
||||||
<a href="/TODO"
|
{{ if isset(urlNextKeepNew) }}
|
||||||
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Next & Keep New</a>
|
<a href="{{ urlNextKeepNew }}"
|
||||||
{{ end }}
|
class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Next & Keep New</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<a href="/TODO"
|
<a href="/TODO"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Find</a>
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">Find</a>
|
||||||
|
|||||||
@@ -26,6 +26,12 @@
|
|||||||
Add Topic
|
Add Topic
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
{{ if isset(urlReadNew) }}
|
||||||
|
<a href="{{ urlReadNew }}"
|
||||||
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
||||||
|
Read New
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
<a href="/TODO/find"
|
<a href="/TODO/find"
|
||||||
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
||||||
Find
|
Find
|
||||||
|
|||||||
Reference in New Issue
Block a user