wired up all the hotlist functionality
This commit is contained in:
@@ -98,6 +98,11 @@ func Topics(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return ui.ErrorPage(ctxt, err)
|
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)
|
traverser := ui.NewTopicTraverser(topics)
|
||||||
ctxt.SetSession("topic.traverser", traverser)
|
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("canCreate", conf.TestPermission("Conference.Create", myLevel))
|
||||||
|
ctxt.VarMap().Set("showHotlist", !hotlistTest)
|
||||||
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", urlStem)
|
ctxt.VarMap().Set("urlStem", urlStem)
|
||||||
|
|||||||
+19
-1
@@ -122,7 +122,25 @@ func AttachmentSend(ctxt ui.AmContext) (string, any, error) {
|
|||||||
return "bytes", data, nil
|
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:
|
* Parameters:
|
||||||
* ctxt - The AmContext for the request.
|
* ctxt - The AmContext for the request.
|
||||||
* Returns:
|
* Returns:
|
||||||
|
|||||||
@@ -383,7 +383,7 @@ func AmGetConferenceByAliasInCommunity(ctx context.Context, cid int32, alias str
|
|||||||
err := row.Scan(&confid)
|
err := row.Scan(&confid)
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
AmGetConference(ctx, confid)
|
return AmGetConference(ctx, confid)
|
||||||
case sql.ErrNoRows:
|
case sql.ErrNoRows:
|
||||||
return nil, errors.New("conference not found")
|
return nil, errors.New("conference not found")
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-1
@@ -9,7 +9,11 @@
|
|||||||
// The database package contains database management and storage logic.
|
// The database package contains database management and storage logic.
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
// ConferenceHotlist represents a user's conference hotlist.
|
// ConferenceHotlist represents a user's conference hotlist.
|
||||||
type ConferenceHotlist struct {
|
type ConferenceHotlist struct {
|
||||||
@@ -19,6 +23,8 @@ type ConferenceHotlist struct {
|
|||||||
ConfId int32 `db:"confid"`
|
ConfId int32 `db:"confid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HOTLIST_SEQUENCE_SPACING = 100
|
||||||
|
|
||||||
// Community gets the community pointer from the hotlist.
|
// Community gets the community pointer from the hotlist.
|
||||||
func (h *ConferenceHotlist) Community(ctx context.Context) (*Community, error) {
|
func (h *ConferenceHotlist) Community(ctx context.Context) (*Community, error) {
|
||||||
return AmGetCommunity(ctx, h.CommId)
|
return AmGetCommunity(ctx, h.CommId)
|
||||||
@@ -62,3 +68,105 @@ func AmCopyConferenceHotlist(ctx context.Context, from, to *User) error {
|
|||||||
success = true
|
success = true
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@
|
|||||||
- Manage Communities on communities sidebox
|
- Manage Communities on communities sidebox
|
||||||
- ~~Conference Hotlist sidebox~~
|
- ~~Conference Hotlist sidebox~~
|
||||||
- ~~"New" flag on Conference Hotlist sidebox~~
|
- ~~"New" flag on Conference Hotlist sidebox~~
|
||||||
- Manage on Conference Hotlist sidebox
|
- ~~Manage on Conference Hotlist sidebox~~
|
||||||
- Sidebox configuration
|
- Sidebox configuration
|
||||||
- Topics view:
|
- Topics view:
|
||||||
- Find
|
- Find
|
||||||
- Manage
|
- Manage
|
||||||
- Add to Hotlist/Remove from Hotlist
|
- ~~Add to Hotlist/Remove from Hotlist~~
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func setupEcho() *echo.Echo {
|
|||||||
e.POST("/find", ui.AmWrap(Find))
|
e.POST("/find", ui.AmWrap(Find))
|
||||||
e.GET("/user/:uname", ui.AmWrap(ShowProfile))
|
e.GET("/user/:uname", ui.AmWrap(ShowProfile))
|
||||||
e.POST("/quick_email", ui.AmWrap(QuickEMail))
|
e.POST("/quick_email", ui.AmWrap(QuickEMail))
|
||||||
|
e.GET("/hotlist", ui.AmWrap(Hotlist))
|
||||||
e.GET("/sysadmin", ui.AmWrap(SysAdminMenu))
|
e.GET("/sysadmin", ui.AmWrap(SysAdminMenu))
|
||||||
e.GET("/create_comm", ui.AmWrap(CreateCommunityForm))
|
e.GET("/create_comm", ui.AmWrap(CreateCommunityForm))
|
||||||
e.POST("/create_comm", ui.AmWrap(CreateCommunity))
|
e.POST("/create_comm", ui.AmWrap(CreateCommunity))
|
||||||
@@ -100,6 +101,7 @@ func setupEcho() *echo.Echo {
|
|||||||
confGroup.GET("", ui.AmWrap(Topics))
|
confGroup.GET("", ui.AmWrap(Topics))
|
||||||
confGroup.GET("/new_topic", ui.AmWrap(NewTopicForm))
|
confGroup.GET("/new_topic", ui.AmWrap(NewTopicForm))
|
||||||
confGroup.POST("/new_topic", ui.AmWrap(NewTopic))
|
confGroup.POST("/new_topic", ui.AmWrap(NewTopic))
|
||||||
|
confGroup.GET("/hotlist", ui.AmWrap(AddToHotlist))
|
||||||
confGroup.GET("/r/:topic", ui.AmWrap(ReadPosts), ui.SetTopic)
|
confGroup.GET("/r/:topic", ui.AmWrap(ReadPosts), ui.SetTopic)
|
||||||
confGroup.POST("/r/:topic", ui.AmWrap(PostInTopic), ui.SetTopic)
|
confGroup.POST("/r/:topic", ui.AmWrap(PostInTopic), ui.SetTopic)
|
||||||
opsGroup := confGroup.Group("/op/:topic", ui.SetTopic)
|
opsGroup := confGroup.Group("/op/:topic", ui.SetTopic)
|
||||||
|
|||||||
@@ -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"> </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"> </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>
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{{ if sb.Flags["canManage"] }}
|
{{ if sb.Flags["canManage"] }}
|
||||||
<div class="mt-3 text-center">
|
<div class="mt-3 text-center">
|
||||||
<span class="text-black text-xs font-bold">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
@@ -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">
|
class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition-colors">
|
||||||
Manage
|
Manage
|
||||||
</a>
|
</a>
|
||||||
{{ if false }}{* TODO *}
|
{{ if showHotlist }}
|
||||||
<a href="/TODO{{ urlStem }}/hotlist"
|
<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">
|
class="bg-orange-600 hover:bg-orange-700 text-black px-4 py-2 rounded text-sm font-medium transition-colors">
|
||||||
Add to Hotlist
|
Add to Hotlist
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
+66
@@ -476,3 +476,69 @@ func QuickEMail(ctxt ui.AmContext) (string, any, error) {
|
|||||||
msg.Send()
|
msg.Send()
|
||||||
return "redirect", "/user/" + user.Username, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user