display of "manage conference list" page

This commit is contained in:
2026-02-19 22:42:53 -07:00
parent ed607e33b3
commit 68fa307736
6 changed files with 226 additions and 25 deletions
+6 -3
View File
@@ -40,15 +40,18 @@ func Conferences(ctxt ui.AmContext) (string, any) {
ctxt.VarMap().Set("commName", comm.Name)
ctxt.VarMap().Set("commAlias", comm.Alias)
ctxt.SetFrameTitle("Conference Listing: " + comm.Name)
clist, err := database.AmGetCommunityConferences(ctxt.Ctx(), comm.Id,
comm.TestPermission("Community.ShowHiddenObjects", ctxt.EffectiveLevel()))
clist, err := database.AmListConferences(ctxt.Ctx(), comm.Id, comm.TestPermission("Community.ShowHiddenObjects", ctxt.EffectiveLevel()))
if err != nil {
return "error", err
}
ctxt.VarMap().Set("conferences", clist)
if len(clist) > 0 {
newflag := make([]bool, len(clist))
for i, conf := range clist {
for i, c := range clist {
conf, err := c.Conf(ctxt.Ctx())
if err != nil {
return "error", err
}
msgCount, err := conf.UnreadMessages(ctxt.Ctx(), ctxt.CurrentUser())
if err != nil {
return "error", err
+40
View File
@@ -789,3 +789,43 @@ func CreateConference(ctxt ui.AmContext) (string, any) {
log.Infof("Created conference '%s'", conf.Name)
return "redirect", fmt.Sprintf("/comm/%s/conf/%s", comm.Alias, alias)
}
/* ManageConferenceList displays the list for managing conferences.
* Parameters:
* ctxt - The AmContext for the request.
* Returns:
* Command string dictating what to be rendered.
* Data as a parameter for the command string.
*/
func ManageConferenceList(ctxt ui.AmContext) (string, any) {
comm := ctxt.CurrentCommunity()
if !comm.TestPermission("Community.Create", ctxt.EffectiveLevel()) {
return "error", ENOPERM
}
clist, err := database.AmListConferences(ctxt.Ctx(), comm.Id, true)
if err != nil {
return "error", err
}
ntopics := make([]int, len(clist))
nposts := make([]int, len(clist))
for i, c := range clist {
conf, err := c.Conf(ctxt.Ctx())
if err != nil {
return "error", err
}
ntopics[i], nposts[i], err = conf.Stats(ctxt.Ctx())
if err != nil {
return "error", err
}
}
ctxt.VarMap().Set("confs", clist)
ctxt.VarMap().Set("ntopics", ntopics)
ctxt.VarMap().Set("nposts", nposts)
ctxt.VarMap().Set("commName", comm.Name)
ctxt.VarMap().Set("baseUrl", fmt.Sprintf("/comm/%s/manage_conf", comm.Alias))
ctxt.VarMap().Set("returnUrl", fmt.Sprintf("/comm/%s/conf", comm.Alias))
ctxt.SetFrameTitle("Manage Conference List")
return "framed", "manage_conflist.jet"
}
+47 -14
View File
@@ -70,6 +70,23 @@ type ConferenceProperties struct {
Data *string `db:"data"` // property data
}
// ConferenceSummary represents summary information about a conference.
type ConferenceSummary struct {
ConfId int32 // conference ID
Name string // conference name
Alias string // an alias for the conference
LastUpdate *time.Time // last update date/time
Hosts []string // usernames of the hosts
Description string // description string
Sequence int16 // sequence number in the list
Hidden bool // hidden in list?
}
// Conf gets the conference from the summary.
func (cs *ConferenceSummary) Conf(ctx context.Context) (*Conference, error) {
return AmGetConference(ctx, cs.ConfId)
}
// Default spacing between sequence numbers in commtoconf table.
const COMMTOCONF_SEQ_SPACING = 10
@@ -786,6 +803,15 @@ func (c *Conference) GetActiveUserEMailAddrs(ctx context.Context, userSelect, da
return rc, nil
}
// Stats retrieves the number of topics and posts in this conference.
func (c *Conference) Stats(ctx context.Context) (int, int, error) {
row := amdb.QueryRowContext(ctx, "SELECT COUNT(*), SUM(top_message + 1) FROM topics WHERE confid = ?", c.ConfId)
ntopic := 0
npost := 0
err := row.Scan(&ntopic, &npost)
return ntopic, npost, err
}
/* AmGetConference returns a conference given its ID.
* Parameters:
* ctx - Standard Go context value.
@@ -864,37 +890,44 @@ func AmGetConferenceByAliasInCommunity(ctx context.Context, cid int32, alias str
return nil, err
}
/* AmGetCommunityConferences returns all conferences for a given community.
/* AmListConferences returns all conferences for a given community.
* Parameters:
* ctx - Standard Go context value.
* cid - Community ID to get conferences for.
* showHidden - true to show hidden conferences.
* Returns:
* Array containing the COnference pointers, or nil.
* Array containing the ConferenceSummary pointers, or nil.
* Stanbard Go error status.
*/
func AmGetCommunityConferences(ctx context.Context, cid int32, showHidden bool) ([]*Conference, error) {
func AmListConferences(ctx context.Context, cid int32, showHidden bool) ([]*ConferenceSummary, error) {
q := ""
if !showHidden {
q = " AND x.hide_list = 0"
}
rs, err := amdb.QueryContext(ctx, `SELECT x.confid FROM commtoconf x, confs c WHERE x.confid = c.confid
AND x.commid = ?`+q+" ORDER BY x.sequence, c.name", cid)
rs, err := amdb.QueryContext(ctx, `SELECT x.confid, c.name, c.lastupdate, c.descr, x.sequence, x.hide_list FROM commtoconf x, confs c
WHERE x.confid = c.confid AND x.commid = ?`+q+" ORDER BY x.sequence, c.name", cid)
if err != nil {
return nil, err
}
rc := make([]*Conference, 0, 6)
rc := make([]*ConferenceSummary, 0)
for rs.Next() {
var confid int32
if err = rs.Scan(&confid); err == nil {
conf, err := AmGetConference(ctx, confid)
if err == nil {
rc = append(rc, conf)
var cs ConferenceSummary
if err = rs.Scan(&(cs.ConfId), &(cs.Name), &(cs.LastUpdate), &(cs.Description), &(cs.Sequence), &(cs.Hidden)); err == nil {
rc = append(rc, &cs)
} else {
log.Errorf("AmGetCommunityConferences conference error: %v", err)
return nil, err
}
} else {
log.Errorf("AmGetCommunityConferences scan error: %v", err)
}
for i := range rc {
row := amdb.QueryRowContext(ctx, "SELECT alias FROM confalias WHERE confid = ?", rc[i].ConfId)
err = row.Scan(&(rc[i].Alias))
if err != nil {
return nil, err
}
err = amdb.SelectContext(ctx, &(rc[i].Hosts), `SELECT u.username FROM confmember m, users u WHERE u.uid = m.uid AND m.confid = ?
AND m.granted_lvl = ? ORDER BY u.username`, rc[i].ConfId, AmRole("Conference.Host").Level())
if err != nil {
return nil, err
}
}
return rc, nil
+1
View File
@@ -117,6 +117,7 @@ func setupEcho() *echo.Echo {
// conference group
commGroup.GET("/create_conf", ui.AmWrap(CreateConferenceForm))
commGroup.POST("/create_conf", ui.AmWrap(CreateConference))
commGroup.GET("/manage_conf", ui.AmWrap(ManageConferenceList))
commGroup.GET("/conf", ui.AmWrap(Conferences), ui.ValidateConference)
confGroup := commGroup.Group("/conf/:confid", ui.ValidateConference, ui.SetConference)
confGroup.GET("", ui.AmWrap(Topics))
+6 -7
View File
@@ -23,7 +23,7 @@
<span class="text-lg pt-0.5 flex-shrink-0">🟣</span>
<div class="flex-1">
<div class="mb-2">
<a href="/comm/{{ commAlias }}/conf/{{ c.AliasesQ(.Ctx())[0] }}"
<a href="/comm/{{ commAlias }}/conf/{{ c.Alias }}"
class="text-blue-700 hover:text-blue-900 font-bold text-lg">{{ c.Name }}</a>
<span class="text-gray-600 text-sm ml-2">- Latest activity: {{ DisplayActivity(c.LastUpdate, .) }}</span>
{{ if newflag[i] }}
@@ -31,16 +31,15 @@
{{ end }}
</div>
<div class="text-sm text-gray-700 mb-1">
{{ hl := c.HostsQ(.Ctx()) }}
{{ if len(hl) == 1 }}
{{ if len(c.Hosts) == 1 }}
<span class="font-medium">Host:</span>
{{ else }}
<span class="font-medium">Hosts:</span>
{{ end }}
{{ if len(hl) > 0 }}
{{ range j, u := hl }}
{{ if len(c.Hosts) > 0 }}
{{ range j, u := c.Hosts }}
{{ if j > 0 }}, {{ end }}
<a href="/users/{{ u.Username }}" class="text-blue-700 hover:text-blue-900">{{ u.Username }}</a>
<a href="/users/{{ u }}" class="text-blue-700 hover:text-blue-900">{{ u }}</a>
{{ end }}
{{ else }}
None
@@ -60,7 +59,7 @@
Find Posts
</a>
{{ if canManage }}
<a href="TODO"
<a href="/comm/{{ commAlias }}/manage_conf"
class="inline-block bg-gray-600 hover:bg-gray-700 text-white px-6 py-2 rounded font-medium transition-colors">
Manage
</a>
+125
View File
@@ -0,0 +1,125 @@
{*
* 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">
<!-- Top Title -->
<div class="mb-2">
<div class="flex items-baseline gap-3 mb-2">
<h1 class="text-blue-800 text-4xl font-bold">Manage Conference List</h1>
<h2 class="text-blue-800 text-2xl font-bold">Community: {{ commName }}</h2>
</div>
<hr class="border-2 border-gray-400 w-4/5 mt-2 mb-6">
</div>
<!-- Backlink -->
<div class="mb-4">
<a class="text-blue-700 hover:text-blue-900 text-sm flex items-center gap-2 w-fit" href="{{ returnUrl }}">
<span>←</span>
Return to Conference List
</a>
</div>
<!-- Conference Table -->
<div class="max-w-4xl mb-8">
<div class="bg-white border border-gray-300 rounded-lg overflow-hidden">
{{ if len(confs) > 0 }}
<table class="w-full">
<thead class="bg-gray-100 border-b-2 border-gray-300">
<tr>
<th class="px-4 py-3 text-center text-xs font-bold text-gray-700 uppercase tracking-wider w-20" colspan="4">&nbsp;</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Conference Name</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Topics</th>
<th class="px-4 py-3 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">Posts</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
{{ range i, c := confs }}
<tr class="hover:bg-gray-50">
<td class="px-4 py-3 whitespace-nowrap text-center w-12">
{{ if c.Hidden }}
<a href="{{ baseUrl }}?t={{ c.ConfId }}" class="inline-block text-3xl hover:scale-110 transition-transform"
title="Click to display">
</a>
{{ else }}
<a href="{{ baseUrl }}?t={{ c.ConfId }}" class="inline-block text-3xl hover:scale-110 transition-transform"
title="Click to hide">
🟢
</a>
{{ end }}
</td>
<td class="px-4 py-3 whitespace-nowrap text-center w-12">
{{ if i < (len(confs) - 1) }}
<a href="{{ baseUrl }}?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="{{ baseUrl }}?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="/TODO" class="text-2xl hover:scale-125 inline-block transition-transform"
title="Delete Conference">❌</a>
</td>
<td class="px-4 py-3 text-sm">
<div class="font-bold text-black">{{ c.Name }}</div>
</td>
<td class="px-4 py-3 text-sm">
<div class="text-black">{{ ntopics[i] }}</div>
</td>
<td class="px-4 py-3 text-sm">
<div class="text-black">{{ nposts[i] }}</div>
</td>
</tr>
{{ end }}
</tbody>
</table>
{{ else }}
<span class="text-gray-800"><i>No conferences in this community.</i></span>
{{ end }}
</div>
</div>
<!-- Legend -->
<div class="max-w-4xl mb-6">
<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 community's conference list:</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">This indicates that the conference is displayed in the community's conference list. Click the symbol to hide it.</span>
</div>
<div class="flex items-center gap-3">
<span class="text-2xl">⚫</span>
<span class="text-gray-700">This indicates that the conference is hidden in the community's conference list. Click the symbol to display it.</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 down in the community's conference list.</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 the community's conference list.</span>
</div>
<div class="flex items-center gap-3">
<span class="text-2xl">❌</span>
<span class="text-gray-700">Click this symbol to delete the specified conference. You will be prompted to confirm this action.</span>
</div>
</div>
</div>
</div>
</div>