wired up all the hotlist functionality

This commit is contained in:
2026-01-26 23:13:48 -07:00
parent 58aa01361d
commit 3a9166bcb6
10 changed files with 282 additions and 8 deletions
+6
View File
@@ -98,6 +98,11 @@ func Topics(ctxt ui.AmContext) (string, any, error) {
return ui.ErrorPage(ctxt, err)
}
hotlistTest, err := database.AmIsInHotlist(ctxt.Ctx(), ctxt.CurrentUser(), comm.Id, conf.ConfId)
if err != nil {
return ui.ErrorPage(ctxt, err)
}
traverser := ui.NewTopicTraverser(topics)
ctxt.SetSession("topic.traverser", traverser)
@@ -116,6 +121,7 @@ func Topics(ctxt ui.AmContext) (string, any, error) {
}
ctxt.VarMap().Set("canCreate", conf.TestPermission("Conference.Create", myLevel))
ctxt.VarMap().Set("showHotlist", !hotlistTest)
ctxt.VarMap().Set("conferenceName", conf.Name)
ctxt.VarMap().Set("urlBack", fmt.Sprintf("/comm/%s/conf", comm.Alias))
ctxt.VarMap().Set("urlStem", urlStem)
+19 -1
View File
@@ -122,7 +122,25 @@ func AttachmentSend(ctxt ui.AmContext) (string, any, error) {
return "bytes", data, nil
}
/* HideTopic hides or shows rthe current topic for the current user.
/* AddToHotlist adds the current community and conference to the user's hotlist..
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func AddToHotlist(ctxt ui.AmContext) (string, any, error) {
comm := ctxt.CurrentCommunity()
conf := ctxt.GetScratch("currentConference").(*database.Conference)
err := database.AmAppendToHotlist(ctxt.Ctx(), ctxt.CurrentUser(), comm.Id, conf.ConfId)
if err != nil {
return ui.ErrorPage(ctxt, err)
}
return "redirect", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, ctxt.GetScratch("currentAlias")), nil
}
/* HideTopic hides or shows the current topic for the current user.
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
+1 -1
View File
@@ -383,7 +383,7 @@ func AmGetConferenceByAliasInCommunity(ctx context.Context, cid int32, alias str
err := row.Scan(&confid)
switch err {
case nil:
AmGetConference(ctx, confid)
return AmGetConference(ctx, confid)
case sql.ErrNoRows:
return nil, errors.New("conference not found")
}
+109 -1
View File
@@ -9,7 +9,11 @@
// The database package contains database management and storage logic.
package database
import "context"
import (
"context"
"database/sql"
"errors"
)
// ConferenceHotlist represents a user's conference hotlist.
type ConferenceHotlist struct {
@@ -19,6 +23,8 @@ type ConferenceHotlist struct {
ConfId int32 `db:"confid"`
}
const HOTLIST_SEQUENCE_SPACING = 100
// Community gets the community pointer from the hotlist.
func (h *ConferenceHotlist) Community(ctx context.Context) (*Community, error) {
return AmGetCommunity(ctx, h.CommId)
@@ -62,3 +68,105 @@ func AmCopyConferenceHotlist(ctx context.Context, from, to *User) error {
success = true
return nil
}
// AmReorderHotlist exchanges the position of two items on the user's hotlist.
func AmReorderHotlist(ctx context.Context, u *User, seq1, seq2 int16) error {
success := false
tx := amdb.MustBegin()
defer func() {
if !success {
tx.Rollback()
}
}()
_, err := tx.ExecContext(ctx, "UPDATE confhotlist SET sequence = -1 WHERE uid = ? AND sequence = ?", u.Uid, seq1)
if err == nil {
_, err = tx.ExecContext(ctx, "UPDATE confhotlist SET sequence = ? WHERE uid = ? AND sequence = ?", seq1, u.Uid, seq2)
if err == nil {
_, err = tx.ExecContext(ctx, "UPDATE confhotlist SET sequence = ? WHERE uid = ? AND sequence = -1", seq2, u.Uid)
}
}
if err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
success = true
return nil
}
// AmRemoveEntryFromHotlist removes an entry from the user's hotlist.
func AmRemoveEntryFromHotlist(ctx context.Context, u *User, seq int16) error {
success := false
tx := amdb.MustBegin()
defer func() {
if !success {
tx.Rollback()
}
}()
_, err := tx.ExecContext(ctx, "DELETE FROM confhotlist WHERE uid = ? AND sequence = ?", u.Uid, seq)
if err == nil {
_, err = tx.ExecContext(ctx, "UPDATE confhotlist SET sequence = sequence - ? WHERE uid = ? AND sequence > ?", HOTLIST_SEQUENCE_SPACING, u.Uid, seq)
}
if err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
success = true
return nil
}
// AmAppendToHotlist adds a community/conference ID to the end of the user's hotlist.
func AmAppendToHotlist(ctx context.Context, u *User, commid, confid int32) error {
success := false
tx := amdb.MustBegin()
defer func() {
if !success {
tx.Rollback()
}
}()
var newseq int16
row := tx.QueryRowContext(ctx, "SELECT sequence FROM confhotlist WHERE uid = ? AND commid = ? AND confid = ?", u.Uid, commid, confid)
err := row.Scan(&newseq)
if err == nil {
return errors.New("community/conference already exist in hotlist")
} else if err != sql.ErrNoRows {
return err
}
row = tx.QueryRowContext(ctx, "SELECT MAX(sequence) FROM confhotlist WHERE uid = ?", u.Uid)
err = row.Scan(&newseq)
if err == sql.ErrNoRows {
newseq = 0
} else if err != nil {
return err
}
_, err = tx.ExecContext(ctx, "INSERT INTO confhotlist (uid, sequence, commid, confid) VALUES (?, ?, ?, ?)",
u.Uid, newseq+HOTLIST_SEQUENCE_SPACING, commid, confid)
if err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
success = true
return nil
}
// AmIsInHotlist returns true if the community/conference pair is in the hotlist.
func AmIsInHotlist(ctx context.Context, u *User, commid, confid int32) (bool, error) {
row := amdb.QueryRowContext(ctx, "SELECT sequence FROM confhotlist WHERE uid = ? AND commid = ? AND confid = ?", u.Uid, commid, confid)
var tmp int16
err := row.Scan(&tmp)
switch err {
case nil:
return true, nil
case sql.ErrNoRows:
return false, nil
}
return false, err
}
+2 -2
View File
@@ -43,10 +43,10 @@
- Manage Communities on communities sidebox
- ~~Conference Hotlist sidebox~~
- ~~"New" flag on Conference Hotlist sidebox~~
- Manage on Conference Hotlist sidebox
- ~~Manage on Conference Hotlist sidebox~~
- Sidebox configuration
- Topics view:
- Find
- Manage
- Add to Hotlist/Remove from Hotlist
- ~~Add to Hotlist/Remove from Hotlist~~
+2
View File
@@ -71,6 +71,7 @@ func setupEcho() *echo.Echo {
e.POST("/find", ui.AmWrap(Find))
e.GET("/user/:uname", ui.AmWrap(ShowProfile))
e.POST("/quick_email", ui.AmWrap(QuickEMail))
e.GET("/hotlist", ui.AmWrap(Hotlist))
e.GET("/sysadmin", ui.AmWrap(SysAdminMenu))
e.GET("/create_comm", ui.AmWrap(CreateCommunityForm))
e.POST("/create_comm", ui.AmWrap(CreateCommunity))
@@ -100,6 +101,7 @@ func setupEcho() *echo.Echo {
confGroup.GET("", ui.AmWrap(Topics))
confGroup.GET("/new_topic", ui.AmWrap(NewTopicForm))
confGroup.POST("/new_topic", ui.AmWrap(NewTopic))
confGroup.GET("/hotlist", ui.AmWrap(AddToHotlist))
confGroup.GET("/r/:topic", ui.AmWrap(ReadPosts), ui.SetTopic)
confGroup.POST("/r/:topic", ui.AmWrap(PostInTopic), ui.SetTopic)
opsGroup := confGroup.Group("/op/:topic", ui.SetTopic)
+74
View File
@@ -0,0 +1,74 @@
{*
* 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/.
*}
<div class="p-4">
<!-- Page Title -->
<div class="mb-6">
<h1 class="text-blue-800 text-4xl font-bold mb-2">Your Conference Hotlist</h1>
<hr class="border-2 border-gray-400 w-4/5 mb-6">
</div>
<!-- Hotlist Table -->
<div class="max-w-4xl mb-8">
<div class="bg-white border border-gray-300 rounded-lg overflow-hidden">
<table class="w-full">
<tbody class="divide-y divide-gray-200">
{{ range i, hl := hotlist }}
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 whitespace-nowrap text-center w-12">
{{ if i < (len(hotlist) - 1) }}
<a href="/hotlist?m={{ i }}&n=1" class="text-2xl hover:scale-125 inline-block transition-transform"
title="Move Down">⬇️</a>
{{ else }}
<span class="text-2xl text-gray-300">&nbsp;</span>
{{ end }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-center w-12">
{{ if i > 0 }}
<a href="/hotlist?m={{ i }}&n=-1" class="text-2xl hover:scale-125 inline-block transition-transform"
title="Move Up">⬆️</a>
{{ else }}
<span class="text-2xl text-gray-300">&nbsp;</span>
{{ end }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-center w-12">
<a href="/hotlist?d={{ i }}" class="text-2xl hover:scale-125 inline-block transition-transform"
title="Remove">❌</a>
</td>
<td class="px-4 py-3 text-sm">
<div class="font-bold text-black">{{ conferences[i] }}</div>
<div class="text-gray-600">({{ communities[i] }})</div>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
<!-- Legend -->
<div class="max-w-4xl">
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h2 class="text-blue-800 font-bold text-lg mb-3">How to update the hotlist:</h2>
<div class="space-y-2 text-sm">
<div class="flex items-center gap-3">
<span class="text-2xl">⬇️</span>
<span class="text-gray-700">Click this symbol to move the specified conference down in your hotlist.</span>
</div>
<div class="flex items-center gap-3">
<span class="text-2xl">⬆️</span>
<span class="text-gray-700">Click this symbol to move the specified conference up in your hotlist.</span>
</div>
<div class="flex items-center gap-3">
<span class="text-2xl">❌</span>
<span class="text-gray-700">Click this symbol to remove the specified conference from your hotlist.</span>
</div>
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -32,7 +32,7 @@
{{ if sb.Flags["canManage"] }}
<div class="mt-3 text-center">
<span class="text-black text-xs font-bold">
[ <a href="/TODO/manage_hotlist" class="text-blue-700 hover:text-blue-900">Manage</a> ]
[ <a href="/hotlist" class="text-blue-700 hover:text-blue-900">Manage</a> ]
</span>
</div>
{{ end }}
+2 -2
View File
@@ -40,8 +40,8 @@
class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
Manage
</a>
{{ if false }}{* TODO *}
<a href="/TODO{{ urlStem }}/hotlist"
{{ if showHotlist }}
<a href="{{ urlStem }}/hotlist"
class="bg-orange-600 hover:bg-orange-700 text-black px-4 py-2 rounded text-sm font-medium transition-colors">
Add to Hotlist
</a>
+66
View File
@@ -476,3 +476,69 @@ func QuickEMail(ctxt ui.AmContext) (string, any, error) {
msg.Send()
return "redirect", "/user/" + user.Username, nil
}
/* Hotlist displays and edits the user's conference hotlist.
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
* Standard Go error status.
*/
func Hotlist(ctxt ui.AmContext) (string, any, error) {
me := ctxt.CurrentUser()
if me.IsAnon {
return ui.ErrorPage(ctxt, errors.New("you are not logged in"))
}
hotlist, err := database.AmGetConferenceHotlist(ctxt.Ctx(), me)
if err != nil {
return ui.ErrorPage(ctxt, err)
}
if ctxt.HasParameter("m") {
index := ctxt.QueryParamInt("m", -1)
dir := ctxt.QueryParamInt("n", 0)
if index >= 0 && (index+dir) != index {
err := database.AmReorderHotlist(ctxt.Ctx(), me, hotlist[index].Sequence, hotlist[index+dir].Sequence)
if err != nil {
return ui.ErrorPage(ctxt, err)
}
tmp := hotlist[index].CommId
hotlist[index].CommId = hotlist[index+dir].CommId
hotlist[index+dir].CommId = tmp
tmp = hotlist[index].ConfId
hotlist[index].ConfId = hotlist[index+dir].ConfId
hotlist[index+dir].ConfId = tmp
}
} else if ctxt.HasParameter("d") {
index := ctxt.QueryParamInt("d", -1)
if index >= 0 {
err := database.AmRemoveEntryFromHotlist(ctxt.Ctx(), me, hotlist[index].Sequence)
if err != nil {
return ui.ErrorPage(ctxt, err)
}
hotlist = append(hotlist[:index], hotlist[index+1:]...)
}
}
communities := make([]string, len(hotlist))
conferences := make([]string, len(hotlist))
for i := range hotlist {
comm, err := hotlist[i].Community(ctxt.Ctx())
if err != nil {
return ui.ErrorPage(ctxt, err)
}
communities[i] = comm.Name
conf, err := hotlist[i].Conference(ctxt.Ctx())
if err != nil {
return ui.ErrorPage(ctxt, err)
}
conferences[i] = conf.Name
}
ctxt.VarMap().Set("hotlist", hotlist)
ctxt.VarMap().Set("communities", communities)
ctxt.VarMap().Set("conferences", conferences)
ctxt.VarMap().Set("amsterdam_pageTitle", "Your Conference Hotlist")
return "framed_template", "hotlist.jet", nil
}