From 08b0fa618504250fe08158c741c3142899e1ab29 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Sun, 15 Feb 2026 23:08:22 -0700 Subject: [PATCH] landed "manage communities" list on sidebox --- community.go | 83 +++++++++++++++++++++++++++++++++-------- database/community.go | 8 ++-- docs/MISSINGFUNCS.md | 2 +- main.go | 4 +- ui/render_wrap.go | 5 ++- ui/views/comlist.jet | 43 +++++++++++++++++++++ ui/views/sb_ftrcomm.jet | 2 +- ui/views/unjoin.jet | 1 + 8 files changed, 123 insertions(+), 25 deletions(-) create mode 100644 ui/views/comlist.jet diff --git a/community.go b/community.go index f1a245b..a8c894c 100644 --- a/community.go +++ b/community.go @@ -224,30 +224,77 @@ func JoinCommunityWithKey(ctxt ui.AmContext) (string, any) { return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias) } -/* UnjoinCommunity starts the process of unjoining a community. +/* ManageCommunities displays the current list of communities with unjoin buttons. * Parameters: * ctxt - The AmContext for the request. * Returns: * Command string dictating what to be rendered. * Data as a parameter for the command string. */ -func UnjoinCommunity(ctxt ui.AmContext) (string, any) { - me := ctxt.CurrentUser() - comm := ctxt.CurrentCommunity() // set by middleware - mbr, lock, _, err := comm.Membership(ctxt.Ctx(), me) +func ManageCommunities(ctxt ui.AmContext) (string, any) { + if ctxt.CurrentUser().IsAnon { + return "redirect", "/" + } + + comms, err := database.AmGetCommunitiesForUser(ctxt.Ctx(), ctxt.CurrentUserId()) if err != nil { return "error", err } - if !mbr { - // not a member, just redirect to profile - return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias) + + canUnjoin := make([]bool, len(comms)) + for i, c := range comms { + _, lock, _, err := c.Membership(ctxt.Ctx(), ctxt.CurrentUser()) + if err != nil { + return "error", err + } + canUnjoin[i] = !lock } - if lock { - return "error", ENOUNJOIN + + ctxt.VarMap().Set("communities", comms) + ctxt.VarMap().Set("canUnjoin", canUnjoin) + ctxt.SetFrameTitle("Your Communities") + ctxt.SetLeftMenu("top") + return "framed", "comlist.jet" +} + +/* UnjoinCommunity starts the process of unjoining a community. This is a meta-function which sets the return URL + * based on its parameter. + * Parameters: + * uctx - Use context for the function: + * prof - Return URL is to the community profile page. + * manage - Return URL is to the "manage communities" page. + * Returns: + * Page function for unjoining a community. + */ +func UnjoinCommunity(uctx string) ui.AmPageFunc { + var returnTemplate string + switch uctx { + case "prof": + returnTemplate = "/comm/[A]/profile" + case "manage": + returnTemplate = "/manage_comm" + default: + panic(fmt.Sprintf("config error! uctx = %s", uctx)) + } + return func(ctxt ui.AmContext) (string, any) { + comm := ctxt.CurrentCommunity() + returnURL := strings.ReplaceAll(returnTemplate, "[A]", comm.Alias) + mbr, lock, _, err := comm.Membership(ctxt.Ctx(), ctxt.CurrentUser()) + if err != nil { + return "error", err + } + if !mbr { + // not a member, just redirect to profile + return "redirect", returnURL + } + if lock { + return "error", ENOUNJOIN + } + ctxt.VarMap().Set("comm", comm) + ctxt.VarMap().Set("returnURL", returnURL) + ctxt.SetFrameTitle("Unjoin Community") + return "framed", "unjoin.jet" } - ctxt.VarMap().Set("comm", comm) - ctxt.SetFrameTitle("Unjoin Community") - return "framed", "unjoin.jet" } /* UnjoinCommunityConfirm finishes the process of unjoining a community. @@ -260,19 +307,23 @@ func UnjoinCommunity(ctxt ui.AmContext) (string, any) { func UnjoinCommunityConfirm(ctxt ui.AmContext) (string, any) { me := ctxt.CurrentUser() comm := ctxt.CurrentCommunity() // set by middleware + returnURL := ctxt.FormField("returnURL") + if returnURL == "" { + returnURL = fmt.Sprintf("/comm/%s/profile", comm.Alias) + } mbr, lock, _, err := comm.Membership(ctxt.Ctx(), me) if err != nil { return "error", err } if !mbr { // not a member, just redirect to profile - return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias) + return "redirect", returnURL } if lock { return "error", ENOUNJOIN } if ctxt.FormFieldIsSet("cancel") { - return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias) + return "redirect", returnURL } if ctxt.FormFieldIsSet("unjoin") { err = comm.SetMembership(ctxt.Ctx(), me, 0, false, me.Uid, ctxt.RemoteIP()) @@ -280,7 +331,7 @@ func UnjoinCommunityConfirm(ctxt ui.AmContext) (string, any) { return "error", err } ctxt.ClearCommunityContext() - return "redirect", fmt.Sprintf("/comm/%s/profile", comm.Alias) + return "redirect", returnURL } return "error", EBUTTON } diff --git a/database/community.go b/database/community.go index 7ec6ca7..ad60764 100644 --- a/database/community.go +++ b/database/community.go @@ -199,10 +199,6 @@ func (c *Community) Membership(ctx context.Context, u *User) (bool, bool, uint16 m := mbr.(*memberCacheData) return m.isMember, m.locked, m.level, nil } - if AmTestPermission("Community.NoJoinRequired", u.BaseLevel) { - // "no join required" - they are effectively a member, but don't cache that - return true, false, u.BaseLevel, nil - } row := amdb.QueryRowContext(ctx, "SELECT locked, granted_lvl FROM commmember WHERE commid = ? AND uid = ?", c.Id, u.Uid) var locked bool var level uint16 @@ -212,6 +208,10 @@ func (c *Community) Membership(ctx context.Context, u *User) (bool, bool, uint16 return true, locked, level, nil } if err == sql.ErrNoRows { + if AmTestPermission("Community.NoJoinRequired", u.BaseLevel) { + // "no join required" - they are effectively a member, but don't cache that + return true, false, u.BaseLevel, nil + } err = nil memberCache.Add(key, &memberCacheData{isMember: false, locked: false, level: uint16(0)}) } diff --git a/docs/MISSINGFUNCS.md b/docs/MISSINGFUNCS.md index d0fb08f..2fcf1e7 100644 --- a/docs/MISSINGFUNCS.md +++ b/docs/MISSINGFUNCS.md @@ -51,7 +51,7 @@ _(italicized items can be deferred)_ - ~~Post Filter User~~ - ~~Post Move~~ - ~~Post Publish~~ -- Manage Communities on communities sidebox +- ~~Manage Communities on communities sidebox~~ - ~~Conference Hotlist sidebox~~ - ~~"New" flag on Conference Hotlist sidebox~~ - ~~Manage on Conference Hotlist sidebox~~ diff --git a/main.go b/main.go index ab1f95f..d720676 100644 --- a/main.go +++ b/main.go @@ -75,6 +75,7 @@ func setupEcho() *echo.Echo { e.GET("/sysadmin", ui.AmWrap(SysAdminMenu)) e.GET("/create_comm", ui.AmWrap(CreateCommunityForm)) e.POST("/create_comm", ui.AmWrap(CreateCommunity)) + e.GET("/manage_comm", ui.AmWrap(ManageCommunities)) e.POST("/attachment_upload", ui.AmWrap(AttachmentUpload)) e.GET("/attachment/:post", ui.AmWrap(AttachmentSend)) e.POST("/__invite_send", ui.AmWrap(InviteSend)) @@ -86,7 +87,8 @@ func setupEcho() *echo.Echo { commGroup.GET("/profile", fn) commGroup.GET("/join", ui.AmWrap(JoinCommunity)) commGroup.POST("/join", ui.AmWrap(JoinCommunityWithKey)) - commGroup.GET("/unjoin", ui.AmWrap(UnjoinCommunity)) + commGroup.GET("/unjoin", ui.AmWrap(UnjoinCommunity("prof"))) + commGroup.GET("/unj", ui.AmWrap(UnjoinCommunity("manage"))) commGroup.POST("/unjoin", ui.AmWrap(UnjoinCommunityConfirm)) commGroup.GET("/members", ui.AmWrap(MemberList)) commGroup.POST("/members", ui.AmWrap(MemberSearch)) diff --git a/ui/render_wrap.go b/ui/render_wrap.go index f3fa03c..27cf86a 100644 --- a/ui/render_wrap.go +++ b/ui/render_wrap.go @@ -136,7 +136,8 @@ func AmSendPageData(ctxt echo.Context, amctxt AmContext, command string, data an // expireTime is the expiration time sent in the dynamic headers. var expireTime string = lctime.Strftime("%c", time.Unix(1, 0)) -type PageFunc func(AmContext) (string, any) +// AmPageFunc is the definition for an Amsterdam "page function" that handles most of the work and defers to the wrapper for rendering. +type AmPageFunc func(AmContext) (string, any) /* AmWrap wraps the Amsterdam handler function in a wrapper that implements the spec for * Echo handler functions. @@ -145,7 +146,7 @@ type PageFunc func(AmContext) (string, any) * Returns: * The wrapped function. */ -func AmWrap(myfunc PageFunc) echo.HandlerFunc { +func AmWrap(myfunc AmPageFunc) echo.HandlerFunc { return func(c echo.Context) error { ctxt := AmContextFromEchoContext(c) diff --git a/ui/views/comlist.jet b/ui/views/comlist.jet new file mode 100644 index 0000000..c4e5a8a --- /dev/null +++ b/ui/views/comlist.jet @@ -0,0 +1,43 @@ +{* + * 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/. + *} +
+ +
+

Your Communities

+
+
+ + + + +
+ {{ range i, c := communities }} +
+ 🟣 + {{ c.Name }} + {{ if canUnjoin[i] }} + 🗙 + {{ end }} +
+ {{ end }} +
+ + +
+

+ Note: Click on the 🗙 symbol to unjoin a community. You will lose access to that + community's resources and discussions. +

+
+
diff --git a/ui/views/sb_ftrcomm.jet b/ui/views/sb_ftrcomm.jet index 8f74fce..7047bdf 100644 --- a/ui/views/sb_ftrcomm.jet +++ b/ui/views/sb_ftrcomm.jet @@ -31,7 +31,7 @@ [ {{ if sb.Flags["canManage"] }} - Manage + Manage {{ if sb.Flags["canCreate"] }}|{{ end }} {{ end }} {{ if sb.Flags["canCreate"] }} diff --git a/ui/views/unjoin.jet b/ui/views/unjoin.jet index e8cd8c4..0c8a606 100644 --- a/ui/views/unjoin.jet +++ b/ui/views/unjoin.jet @@ -42,6 +42,7 @@
+