added dialog for editing community profile
This commit is contained in:
@@ -12,6 +12,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/database"
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
@@ -48,3 +49,76 @@ func CommunityAdminMenu(ctxt ui.AmContext) (string, any, error) {
|
|||||||
ctxt.VarMap().Set("amsterdam_pageTitle", menu.Title+" - "+comm.Name)
|
ctxt.VarMap().Set("amsterdam_pageTitle", menu.Title+" - "+comm.Name)
|
||||||
return "framed_template", "menu.jet", nil
|
return "framed_template", "menu.jet", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupCommunityProfileDialog(dlg *ui.Dialog, comm *database.Community) {
|
||||||
|
dlg.SetCommunity(comm)
|
||||||
|
if comm.IsAdmin {
|
||||||
|
dlg.Field("comtype").Disabled = true
|
||||||
|
dlg.Field("joinkey").Disabled = true
|
||||||
|
dlg.Field("membersonly").Disabled = true
|
||||||
|
dlg.Field("hidemode").Disabled = true
|
||||||
|
dlg.Field("read_lvl").Disabled = true
|
||||||
|
dlg.Field("write_lvl").Disabled = true
|
||||||
|
dlg.Field("create_lvl").Disabled = true
|
||||||
|
dlg.Field("delete_lvl").Disabled = true
|
||||||
|
dlg.Field("join_lvl").Disabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommunityProfileForm(ctxt ui.AmContext) (string, any, error) {
|
||||||
|
err := ctxt.SetCommunityContext(ctxt.URLParam("cid"))
|
||||||
|
if err != nil {
|
||||||
|
ctxt.SetRC(http.StatusNotFound)
|
||||||
|
return ui.ErrorPage(ctxt, err)
|
||||||
|
}
|
||||||
|
comm := ctxt.CurrentCommunity()
|
||||||
|
if !comm.TestPermission("Community.Write", ctxt.EffectiveLevel()) {
|
||||||
|
ctxt.SetRC(http.StatusForbidden)
|
||||||
|
return ui.ErrorPage(ctxt, errors.New("you are not permitted to access this page"))
|
||||||
|
}
|
||||||
|
var ci *database.ContactInfo
|
||||||
|
ci, err = comm.ContactInfo()
|
||||||
|
if err != nil {
|
||||||
|
return ui.ErrorPage(ctxt, err)
|
||||||
|
}
|
||||||
|
flags, err := comm.Flags()
|
||||||
|
if err != nil {
|
||||||
|
return ui.ErrorPage(ctxt, err)
|
||||||
|
}
|
||||||
|
dlg, err := ui.AmLoadDialog("commprofile")
|
||||||
|
if err == nil {
|
||||||
|
setupCommunityProfileDialog(dlg, comm)
|
||||||
|
dlg.Field("cc").Value = fmt.Sprintf("%d", comm.Id)
|
||||||
|
dlg.Field("name").Value = comm.Name
|
||||||
|
dlg.Field("alias").Value = comm.Alias
|
||||||
|
dlg.Field("synopsis").SetVal(comm.Synopsis)
|
||||||
|
dlg.Field("rules").SetVal(comm.Rules)
|
||||||
|
dlg.Field("language").SetVal(comm.Language)
|
||||||
|
dlg.Field("url").SetVal(ci.URL)
|
||||||
|
// TODO: set logo URL
|
||||||
|
dlg.Field("company").SetVal(ci.Company)
|
||||||
|
dlg.Field("addr1").SetVal(ci.Addr1)
|
||||||
|
dlg.Field("addr2").SetVal(ci.Addr2)
|
||||||
|
dlg.Field("loc").SetVal(ci.Locality)
|
||||||
|
dlg.Field("reg").SetVal(ci.Region)
|
||||||
|
dlg.Field("pcode").SetVal(ci.PostalCode)
|
||||||
|
dlg.Field("country").SetVal(ci.Country)
|
||||||
|
if comm.Public() {
|
||||||
|
dlg.Field("comtype").Value = "0"
|
||||||
|
dlg.Field("joinkey").Value = ""
|
||||||
|
} else {
|
||||||
|
dlg.Field("comtype").Value = "1"
|
||||||
|
dlg.Field("joinkey").SetVal(comm.JoinKey)
|
||||||
|
}
|
||||||
|
dlg.Field("membersonly").SetChecked(comm.MembersOnly)
|
||||||
|
dlg.Field("hidemode").Value = comm.HideMode()
|
||||||
|
dlg.Field("read_lvl").Value = fmt.Sprintf("%d", comm.ReadLevel)
|
||||||
|
dlg.Field("write_lvl").Value = fmt.Sprintf("%d", comm.WriteLevel)
|
||||||
|
dlg.Field("create_lvl").Value = fmt.Sprintf("%d", comm.CreateLevel)
|
||||||
|
dlg.Field("delete_lvl").Value = fmt.Sprintf("%d", comm.DeleteLevel)
|
||||||
|
dlg.Field("join_lvl").Value = fmt.Sprintf("%d", comm.JoinLevel)
|
||||||
|
dlg.Field("pic_in_post").SetChecked(flags.Get(database.CommunityFlagPicturesInPosts))
|
||||||
|
return dlg.Render(ctxt)
|
||||||
|
}
|
||||||
|
return ui.ErrorPage(ctxt, err)
|
||||||
|
}
|
||||||
|
|||||||
+139
-1
@@ -47,14 +47,38 @@ type Community struct {
|
|||||||
Rules *string `dd:"rules"`
|
Rules *string `dd:"rules"`
|
||||||
JoinKey *string `db:"joinkey"`
|
JoinKey *string `db:"joinkey"`
|
||||||
Alias string `db:"alias"`
|
Alias string `db:"alias"`
|
||||||
|
flags *util.OptionSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommunityProperties represents a property entry for a community.
|
||||||
|
type CommunityProperties struct {
|
||||||
|
Cid int32 `db:"cid"`
|
||||||
|
Index int32 `db:"ndx"`
|
||||||
|
Data *string `db:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Community property indexes defined.
|
||||||
|
const (
|
||||||
|
CommunityPropFlags = int32(0) // "flags" user property
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flag values for community property index CommunityPropFlags defined.
|
||||||
|
const (
|
||||||
|
CommunityFlagPicturesInPosts = uint(0)
|
||||||
|
)
|
||||||
|
|
||||||
// communityCache is the cache for Community objects.
|
// communityCache is the cache for Community objects.
|
||||||
var communityCache *lru.TwoQueueCache = nil
|
var communityCache *lru.TwoQueueCache = nil
|
||||||
|
|
||||||
// getCommunityMutex is a mutex on AmGetCommunity.
|
// getCommunityMutex is a mutex on AmGetCommunity.
|
||||||
var getCommunityMutex sync.Mutex
|
var getCommunityMutex sync.Mutex
|
||||||
|
|
||||||
|
// communityPropCache is the cache for CommunityProperties objects.
|
||||||
|
var communityPropCache *lru.Cache = nil
|
||||||
|
|
||||||
|
// getCommunityPropMutex is a mutex on AmGetCommunityProperty.
|
||||||
|
var getCommunityPropMutex sync.Mutex
|
||||||
|
|
||||||
// memberCacheData caches membership information for communities.
|
// memberCacheData caches membership information for communities.
|
||||||
type memberCacheData struct {
|
type memberCacheData struct {
|
||||||
isMember bool
|
isMember bool
|
||||||
@@ -76,7 +100,7 @@ func stuffMembership(cid int32, uid int32, member bool, locked bool, level uint1
|
|||||||
memberMutex.Unlock()
|
memberMutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// init initializes the community cache.
|
// init initializes the caches.
|
||||||
func init() {
|
func init() {
|
||||||
var err error
|
var err error
|
||||||
communityCache, err = lru.New2Q(50)
|
communityCache, err = lru.New2Q(50)
|
||||||
@@ -87,6 +111,10 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
communityPropCache, err = lru.New(100)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public returns true if the community is public.
|
// Public returns true if the community is public.
|
||||||
@@ -121,6 +149,16 @@ func (c *Community) LanguageTag() (*language.Tag, error) {
|
|||||||
return &t, nil
|
return &t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Community) HideMode() string {
|
||||||
|
if c.HideFromSearch {
|
||||||
|
return "BOTH"
|
||||||
|
} else if c.HideFromDirectory {
|
||||||
|
return "DIRECTORY"
|
||||||
|
} else {
|
||||||
|
return "NONE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Membership returns the details of the specified user's membership in the community.
|
/* Membership returns the details of the specified user's membership in the community.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* u - The user to check the membership of.
|
* u - The user to check the membership of.
|
||||||
@@ -200,6 +238,35 @@ func (c *Community) PermissionLevel(perm string) uint16 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFlags retrieves the flags from the properties.
|
||||||
|
func (c *Community) Flags() (*util.OptionSet, error) {
|
||||||
|
c.Mutex.Lock()
|
||||||
|
defer c.Mutex.Unlock()
|
||||||
|
if c.flags == nil {
|
||||||
|
s, err := AmGetCommunityProperty(c.Id, CommunityPropFlags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
return nil, fmt.Errorf("missing flags for community %d", c.Id)
|
||||||
|
}
|
||||||
|
c.flags = util.OptionSetFromString(*s)
|
||||||
|
}
|
||||||
|
return c.flags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveFlags writes the flags to the database and stores them.
|
||||||
|
func (c *Community) SaveFlags(f *util.OptionSet) error {
|
||||||
|
s := f.AsString()
|
||||||
|
c.Mutex.Lock()
|
||||||
|
defer c.Mutex.Unlock()
|
||||||
|
err := AmSetCommunityProperty(c.Id, CommunityPropFlags, &s)
|
||||||
|
if err == nil {
|
||||||
|
c.flags = f
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
/* AmGetCommunity returns a reference to the specified community.
|
/* AmGetCommunity returns a reference to the specified community.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* id - The ID of the community.
|
* id - The ID of the community.
|
||||||
@@ -342,3 +409,74 @@ func AmAutoJoinCommunities(user *User) error {
|
|||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// internalGetProp is a helper used by the property functions.
|
||||||
|
func internalGetCommProp(cid int32, ndx int32) (*CommunityProperties, error) {
|
||||||
|
var err error = nil
|
||||||
|
key := fmt.Sprintf("%d:%d", cid, ndx)
|
||||||
|
getCommunityPropMutex.Lock()
|
||||||
|
defer getCommunityPropMutex.Unlock()
|
||||||
|
rc, ok := communityPropCache.Get(key)
|
||||||
|
if !ok {
|
||||||
|
var dbdata []CommunityProperties
|
||||||
|
err = amdb.Select(&dbdata, "SELECT * from propcomm WHERE cid = ? AND ndx = ?", cid, ndx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(dbdata) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if len(dbdata) > 1 {
|
||||||
|
return nil, fmt.Errorf("AmGetCommunityProperty(%d): too many responses(%d)", cid, len(dbdata))
|
||||||
|
}
|
||||||
|
rc = &(dbdata[0])
|
||||||
|
communityPropCache.Add(key, rc)
|
||||||
|
}
|
||||||
|
return rc.(*CommunityProperties), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AmGetCommunityProperty retrieves the value of a user property.
|
||||||
|
* Parameters:
|
||||||
|
* cid - The ID of the community to get the property for.
|
||||||
|
* ndx - The index of the property to retrieve.
|
||||||
|
* Returns:
|
||||||
|
* Value of the property string.
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmGetCommunityProperty(cid int32, ndx int32) (*string, error) {
|
||||||
|
p, err := internalGetCommProp(cid, ndx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AmSetCommunityProperty sets the value of a community property.
|
||||||
|
* Parameters:
|
||||||
|
* cid - The ID of the community to set the property for.
|
||||||
|
* ndx - The index of the property to set.
|
||||||
|
* val - The new value of the property.
|
||||||
|
* Returns:
|
||||||
|
* Standard Go error status.
|
||||||
|
*/
|
||||||
|
func AmSetCommunityProperty(cid int32, ndx int32, val *string) error {
|
||||||
|
p, err := internalGetCommProp(cid, ndx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
getCommunityPropMutex.Lock()
|
||||||
|
defer getCommunityPropMutex.Unlock()
|
||||||
|
if p != nil {
|
||||||
|
_, err = amdb.Exec("UPDATE propcomm SET data = ? WHERE cid = ? AND ndx = ?", val, cid, ndx)
|
||||||
|
if err == nil {
|
||||||
|
p.Data = val
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
prop := CommunityProperties{Cid: cid, Index: ndx, Data: val}
|
||||||
|
_, err := amdb.NamedExec("INSERT INTO propcomm (cid, ndx, data) VALUES(:cid, :ndx, :data)", prop)
|
||||||
|
if err == nil {
|
||||||
|
communityPropCache.Add(fmt.Sprintf("%d:%d", cid, ndx), prop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -184,6 +185,7 @@ type Role interface {
|
|||||||
ID() string
|
ID() string
|
||||||
Name() string
|
Name() string
|
||||||
Level() uint16
|
Level() uint16
|
||||||
|
LevelStr() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *CfgRole) ID() string {
|
func (r *CfgRole) ID() string {
|
||||||
@@ -198,6 +200,10 @@ func (r *CfgRole) Level() uint16 {
|
|||||||
return r.level
|
return r.level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *CfgRole) LevelStr() string {
|
||||||
|
return fmt.Sprintf("%d", r.level)
|
||||||
|
}
|
||||||
|
|
||||||
// RoleList defines a list of security roles.
|
// RoleList defines a list of security roles.
|
||||||
type RoleList interface {
|
type RoleList interface {
|
||||||
Roles() []Role
|
Roles() []Role
|
||||||
|
|||||||
@@ -184,5 +184,3 @@ permissions:
|
|||||||
role: "Community.AnyAdmin"
|
role: "Community.AnyAdmin"
|
||||||
- name: "Community.MassMail"
|
- name: "Community.MassMail"
|
||||||
role: "Community.AnyAdmin"
|
role: "Community.AnyAdmin"
|
||||||
- name: "Community.Destroy"
|
|
||||||
role: "Community.Host"
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ func setupEcho() *echo.Echo {
|
|||||||
e.GET("/sysadmin", ui.AmWrap(SysAdminMenu))
|
e.GET("/sysadmin", ui.AmWrap(SysAdminMenu))
|
||||||
e.GET("/comm/:cid/profile", ui.AmWrap(ShowCommunity))
|
e.GET("/comm/:cid/profile", ui.AmWrap(ShowCommunity))
|
||||||
e.GET("/comm/:cid/admin", ui.AmWrap(CommunityAdminMenu))
|
e.GET("/comm/:cid/admin", ui.AmWrap(CommunityAdminMenu))
|
||||||
|
e.GET("/comm/:cid/admin/profile", ui.AmWrap(CommunityProfileForm))
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-24
@@ -23,17 +23,26 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DialogItemChoice holds a dialog item choice (only needed in case of defined dropdowns)
|
||||||
|
type DialogItemChoice struct {
|
||||||
|
Id string `yaml:"id"`
|
||||||
|
Text string `yaml:"text"`
|
||||||
|
Default bool `yaml:"default,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// DialogItem holds the dialog item definition.
|
// DialogItem holds the dialog item definition.
|
||||||
type DialogItem struct {
|
type DialogItem struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Caption string `yaml:"caption,omitempty"`
|
Caption string `yaml:"caption,omitempty"`
|
||||||
Subcaption string `yaml:"subcaption,omitempty"`
|
Subcaption string `yaml:"subcaption,omitempty"`
|
||||||
Required bool `yaml:"required,omitempty"`
|
Required bool `yaml:"required,omitempty"`
|
||||||
Size int `yaml:"size,omitempty"`
|
Disabled bool `yaml:"disabled,omitempty"`
|
||||||
MaxLength int `yaml:"maxlength,omitempty"`
|
Size int `yaml:"size,omitempty"`
|
||||||
Value string `yaml:"value,omitempty"`
|
MaxLength int `yaml:"maxlength,omitempty"`
|
||||||
Param string `yaml:"param,omitempty"`
|
Value string `yaml:"value,omitempty"`
|
||||||
|
Param string `yaml:"param,omitempty"`
|
||||||
|
Choices []DialogItemChoice `yaml:"choices,omitempty"`
|
||||||
AuxData any
|
AuxData any
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,6 +53,7 @@ type Dialog struct {
|
|||||||
Options string `yaml:"options,omitempty"`
|
Options string `yaml:"options,omitempty"`
|
||||||
MenuSelector string `yaml:"menuSelector,omitempty"`
|
MenuSelector string `yaml:"menuSelector,omitempty"`
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title"`
|
||||||
|
Subtitle string `yaml:"subtitle,omitempty"`
|
||||||
Action string `yaml:"action"`
|
Action string `yaml:"action"`
|
||||||
Instructions string `yaml:"instructions,omitempty"`
|
Instructions string `yaml:"instructions,omitempty"`
|
||||||
Fields []DialogItem `yaml:"fields"`
|
Fields []DialogItem `yaml:"fields"`
|
||||||
@@ -82,6 +92,9 @@ func AmLoadDialog(name string) (*Dialog, error) {
|
|||||||
if fld.Type == "date" && fld.Param == "" {
|
if fld.Type == "date" && fld.Param == "" {
|
||||||
d.Fields[i].Param = "year:-100"
|
d.Fields[i].Param = "year:-100"
|
||||||
}
|
}
|
||||||
|
if fld.Type == "dropdown" && len(fld.Choices) == 0 {
|
||||||
|
return nil, fmt.Errorf("dropdown field %s in dialog %s has no choices", fld.Name, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return &d, nil
|
return &d, nil
|
||||||
}
|
}
|
||||||
@@ -184,11 +197,29 @@ func (fld *DialogItem) SetVal(p *string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetInt sets the value of a field to an integer.
|
||||||
|
func (fld *DialogItem) SetInt(v int) {
|
||||||
|
fld.Value = fmt.Sprintf("%d", v)
|
||||||
|
}
|
||||||
|
|
||||||
// IsEmpty returns true if the field is empty.
|
// IsEmpty returns true if the field is empty.
|
||||||
func (fld *DialogItem) IsEmpty() bool {
|
func (fld *DialogItem) IsEmpty() bool {
|
||||||
return len(fld.Value) == 0
|
return len(fld.Value) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetCommunity alters a dialog's content to reflect the community.
|
||||||
|
func (d *Dialog) SetCommunity(comm *database.Community) {
|
||||||
|
d.Title = strings.ReplaceAll(d.Title, "[CNAME]", comm.Name)
|
||||||
|
d.Subtitle = strings.ReplaceAll(d.Subtitle, "[CNAME]", comm.Name)
|
||||||
|
d.Action = strings.ReplaceAll(d.Action, "[CID]", comm.Alias)
|
||||||
|
for i, fld := range d.Fields {
|
||||||
|
switch fld.Type {
|
||||||
|
case "userphoto", "communitylogo":
|
||||||
|
d.Fields[i].Param = strings.ReplaceAll(fld.Param, "[CID]", comm.Alias)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Field returns a pointer to a dialog's field, given its name.
|
/* Field returns a pointer to a dialog's field, given its name.
|
||||||
* Parameters:
|
* Parameters:
|
||||||
* name - The name of the field to find.
|
* name - The name of the field to find.
|
||||||
@@ -224,6 +255,46 @@ func (d *Dialog) Render(ctxt AmContext) (string, any, error) {
|
|||||||
if d.Fields[i].Value == "" {
|
if d.Fields[i].Value == "" {
|
||||||
d.Fields[i].Value = config.GlobalConfig.Defaults.TimeZone
|
d.Fields[i].Value = config.GlobalConfig.Defaults.TimeZone
|
||||||
}
|
}
|
||||||
|
case "dropdown":
|
||||||
|
defv := ""
|
||||||
|
for _, ch := range fld.Choices {
|
||||||
|
if ch.Default {
|
||||||
|
defv = ch.Id
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d.Fields[i].Value == "" {
|
||||||
|
d.Fields[i].Value = defv
|
||||||
|
} else {
|
||||||
|
ok := false
|
||||||
|
for _, ch := range fld.Choices {
|
||||||
|
if d.Fields[i].Value == ch.Id {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
d.Fields[i].Value = defv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "rolelist":
|
||||||
|
rl := database.AmRoleList(fld.Param)
|
||||||
|
defv := rl.Default().LevelStr()
|
||||||
|
if d.Fields[i].Value == "" {
|
||||||
|
d.Fields[i].Value = defv
|
||||||
|
} else {
|
||||||
|
ok := false
|
||||||
|
for _, r := range rl.Roles() {
|
||||||
|
if d.Fields[i].Value == r.LevelStr() {
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
d.Fields[i].Value = defv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: want to do something like dropdown but not sure what yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if d.MenuSelector != "" && d.MenuSelector != "nochange" {
|
if d.MenuSelector != "" && d.MenuSelector != "nochange" {
|
||||||
@@ -312,7 +383,7 @@ func (d *Dialog) LoadFromForm(ctxt AmContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.Fields[i].AuxData = dvals
|
d.Fields[i].AuxData = dvals
|
||||||
case "userphoto":
|
case "userphoto", "communitylogo":
|
||||||
d.Fields[i].Value = ctxt.FormField(fmt.Sprintf("%s_data", fld.Name))
|
d.Fields[i].Value = ctxt.FormField(fmt.Sprintf("%s_data", fld.Name))
|
||||||
default:
|
default:
|
||||||
d.Fields[i].Value = ctxt.FormField(fld.Name)
|
d.Fields[i].Value = ctxt.FormField(fld.Name)
|
||||||
@@ -468,20 +539,23 @@ func validateDateField(fld *DialogItem) error {
|
|||||||
|
|
||||||
// validators maps the field types to validator functions.
|
// validators maps the field types to validator functions.
|
||||||
var validators = map[string]validatorFunc{
|
var validators = map[string]validatorFunc{
|
||||||
"ams_id": validateAmsIdField,
|
"ams_id": validateAmsIdField,
|
||||||
"button": nilValidator,
|
"button": nilValidator,
|
||||||
"checkbox": nilValidator,
|
"checkbox": nilValidator,
|
||||||
"countrylist": validateCountryField,
|
"communitylogo": nilValidator,
|
||||||
"date": validateDateField,
|
"countrylist": validateCountryField,
|
||||||
"email": validateEmailField,
|
"date": validateDateField,
|
||||||
"header": nilValidator,
|
"dropdown": nilValidator,
|
||||||
"hidden": nilValidator,
|
"email": validateEmailField,
|
||||||
"integer": validateIntegerField,
|
"header": nilValidator,
|
||||||
"localelist": nilValidator, // TODO
|
"hidden": nilValidator,
|
||||||
"password": validateTextField,
|
"integer": validateIntegerField,
|
||||||
"text": validateTextField,
|
"localelist": nilValidator,
|
||||||
"tzlist": nilValidator, // TODO
|
"password": validateTextField,
|
||||||
"userphoto": nilValidator,
|
"rolelist": nilValidator,
|
||||||
|
"text": validateTextField,
|
||||||
|
"tzlist": nilValidator,
|
||||||
|
"userphoto": nilValidator,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Validate validates the values in the dialog.
|
/* Validate validates the values in the dialog.
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
#
|
||||||
|
# Amsterdam Web Communities System
|
||||||
|
# Copyright (c) 2025 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/.
|
||||||
|
#
|
||||||
|
name: "community.profile"
|
||||||
|
formName: "comprofform"
|
||||||
|
menuSelector: "community"
|
||||||
|
title: "Edit Community Profile:"
|
||||||
|
subtitle: "[CNAME]"
|
||||||
|
action: "/comm/[CID]/admin/profile"
|
||||||
|
fields:
|
||||||
|
- type: "hidden"
|
||||||
|
name: "cc"
|
||||||
|
value: ""
|
||||||
|
- type: "header"
|
||||||
|
name: "header1"
|
||||||
|
caption: "Basic Information"
|
||||||
|
- type: "text"
|
||||||
|
name: "name"
|
||||||
|
caption: "Community Name"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxLength: 128
|
||||||
|
- type: "ams_id"
|
||||||
|
name: "alias"
|
||||||
|
caption: "Community Alias"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxlength: 32
|
||||||
|
- type: "text"
|
||||||
|
name: "synopsis"
|
||||||
|
caption: "Synopsis"
|
||||||
|
size: 32
|
||||||
|
maxLength: 255
|
||||||
|
- type: "text"
|
||||||
|
name: "rules"
|
||||||
|
caption: "Rules"
|
||||||
|
size: 32
|
||||||
|
maxLength: 255
|
||||||
|
- type: "localelist"
|
||||||
|
name: "language"
|
||||||
|
caption: "Primary Language"
|
||||||
|
required: true
|
||||||
|
- type: "text"
|
||||||
|
name: "url"
|
||||||
|
caption: "Home Page"
|
||||||
|
subcaption: "(URL)"
|
||||||
|
size: 32
|
||||||
|
maxlength: 255
|
||||||
|
- type: "communitylogo"
|
||||||
|
name: "logo"
|
||||||
|
caption: "Community logo"
|
||||||
|
param: "/TODO/comm/[CID]/admin/logo"
|
||||||
|
- type: "header"
|
||||||
|
name: "header2"
|
||||||
|
caption: "Location"
|
||||||
|
- type: "text"
|
||||||
|
name: "company"
|
||||||
|
caption: "Company"
|
||||||
|
size: 32
|
||||||
|
maxlength: 255
|
||||||
|
- type: "text"
|
||||||
|
name: "addr1"
|
||||||
|
caption: "Address"
|
||||||
|
size: 32
|
||||||
|
maxlength: 255
|
||||||
|
- type: "text"
|
||||||
|
name: "addr2"
|
||||||
|
caption: "Address"
|
||||||
|
subcaption: "(line 2)"
|
||||||
|
size: 32
|
||||||
|
maxlength: 255
|
||||||
|
- type: "text"
|
||||||
|
name: "loc"
|
||||||
|
caption: "City"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxlength: 64
|
||||||
|
- type: "text"
|
||||||
|
name: "reg"
|
||||||
|
caption: "State/Province"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxlength: 64
|
||||||
|
- type: "text"
|
||||||
|
name: "pcode"
|
||||||
|
caption: "Zip/Postal Code"
|
||||||
|
required: true
|
||||||
|
size: 32
|
||||||
|
maxlength: 64
|
||||||
|
- type: "countrylist"
|
||||||
|
name: "country"
|
||||||
|
caption: "Country"
|
||||||
|
required: true
|
||||||
|
- type: "header"
|
||||||
|
name: "header3"
|
||||||
|
caption: "Security"
|
||||||
|
- type: "dropdown"
|
||||||
|
name: "comtype"
|
||||||
|
caption: "Community type"
|
||||||
|
required: true
|
||||||
|
choices:
|
||||||
|
- id: "0"
|
||||||
|
text: "Public"
|
||||||
|
default: true
|
||||||
|
- id: "1"
|
||||||
|
text: "Private"
|
||||||
|
- type: "text"
|
||||||
|
name: "joinkey"
|
||||||
|
caption: "Join Key"
|
||||||
|
subcaption: "(for private communities)"
|
||||||
|
size: 32
|
||||||
|
maxLength: 64
|
||||||
|
- type: "checkbox"
|
||||||
|
name: "membersonly"
|
||||||
|
caption: "Allow only members to access this community"
|
||||||
|
- type: "dropdown"
|
||||||
|
name: "hidemode"
|
||||||
|
caption: "Community visibility"
|
||||||
|
required: true
|
||||||
|
choices:
|
||||||
|
- id: "NONE"
|
||||||
|
text: "Show in both directory and search"
|
||||||
|
default: true
|
||||||
|
- id: "DIRECTORY"
|
||||||
|
text: "Hide in directory, but not in search"
|
||||||
|
- id: "BOTH"
|
||||||
|
text: "Hide in both directory and search"
|
||||||
|
- type: "rolelist"
|
||||||
|
name: "read_lvl"
|
||||||
|
caption: "Security level required to read contents"
|
||||||
|
required: true
|
||||||
|
param: "Community.Read"
|
||||||
|
- type: "rolelist"
|
||||||
|
name: "write_lvl"
|
||||||
|
caption: "Security level required to update profile"
|
||||||
|
required: true
|
||||||
|
param: "Community.Write"
|
||||||
|
- type: "rolelist"
|
||||||
|
name: "create_lvl"
|
||||||
|
caption: "Security level required to create new subobjects"
|
||||||
|
required: true
|
||||||
|
param: "Community.Create"
|
||||||
|
- type: "rolelist"
|
||||||
|
name: "delete_lvl"
|
||||||
|
caption: "Security level required to delete community"
|
||||||
|
required: true
|
||||||
|
param: "Community.Delete"
|
||||||
|
- type: "rolelist"
|
||||||
|
name: "join_lvl"
|
||||||
|
caption: "Security level required to join community"
|
||||||
|
required: true
|
||||||
|
param: "Community.Join"
|
||||||
|
- type: "header"
|
||||||
|
name: "header4"
|
||||||
|
caption: "Conferencing Options"
|
||||||
|
- type: "checkbox"
|
||||||
|
name: "pic_in_post"
|
||||||
|
caption: "Display user pictures next to posts in conferences"
|
||||||
|
subcaption: "(by default; user can override)"
|
||||||
|
- type: "button"
|
||||||
|
name: "update"
|
||||||
|
caption: "Update"
|
||||||
|
param: "blue"
|
||||||
|
- type: "button"
|
||||||
|
name: "cancel"
|
||||||
|
caption: "Cancel"
|
||||||
|
param: "red"
|
||||||
+5
-5
@@ -61,15 +61,15 @@ menudefs:
|
|||||||
community. Use with care and review all changes before applying them to the community.
|
community. Use with care and review all changes before applying them to the community.
|
||||||
items:
|
items:
|
||||||
- text: "Community Profile"
|
- text: "Community Profile"
|
||||||
link: "/TODO/comm/[CID]/admin/profile"
|
link: "/comm/[CID]/admin/profile"
|
||||||
permission: "Community.ShowAdmin"
|
permission: "Community.Write"
|
||||||
- text: "Set Community Category"
|
- text: "Set Community Category"
|
||||||
link: "/TODO/comm/[CID]/admin/category"
|
link: "/TODO/comm/[CID]/admin/category"
|
||||||
permission: "Community.ShowAdmin"
|
permission: "Community.Write"
|
||||||
ifdef: "USECAT"
|
ifdef: "USECAT"
|
||||||
- text: "Set Community Services"
|
- text: "Set Community Services"
|
||||||
link: "/TODO/comm/[CID]/admin/services"
|
link: "/TODO/comm/[CID]/admin/services"
|
||||||
permission: "Community.ShowAdmin"
|
permission: "Community.Write"
|
||||||
disabled: true
|
disabled: true
|
||||||
- text: "Membership Control"
|
- text: "Membership Control"
|
||||||
link: "/TODO/comm/[CID]/admin/members"
|
link: "/TODO/comm/[CID]/admin/members"
|
||||||
@@ -82,5 +82,5 @@ menudefs:
|
|||||||
permission: "Community.ShowAdmin"
|
permission: "Community.ShowAdmin"
|
||||||
- text: "Delete Community"
|
- text: "Delete Community"
|
||||||
link: "/TODO/comm/[CID]/admin/delete"
|
link: "/TODO/comm/[CID]/admin/delete"
|
||||||
permission: "Community.Destroy"
|
permission: "Community.Delete"
|
||||||
hazard: true
|
hazard: true
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.erbosoft.com/amy/amsterdam/config"
|
"git.erbosoft.com/amy/amsterdam/config"
|
||||||
|
"git.erbosoft.com/amy/amsterdam/database"
|
||||||
"git.erbosoft.com/amy/amsterdam/util"
|
"git.erbosoft.com/amy/amsterdam/util"
|
||||||
"github.com/CloudyKit/jet/v6"
|
"github.com/CloudyKit/jet/v6"
|
||||||
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
"github.com/CloudyKit/jet/v6/loaders/embedfs"
|
||||||
@@ -235,6 +236,10 @@ func SetupTemplates() {
|
|||||||
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
||||||
return reflect.ValueOf(AmMenu(s))
|
return reflect.ValueOf(AmMenu(s))
|
||||||
})
|
})
|
||||||
|
views.AddGlobalFunc("AmRoleList", func(a jet.Arguments) reflect.Value {
|
||||||
|
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
||||||
|
return reflect.ValueOf(database.AmRoleList(s))
|
||||||
|
})
|
||||||
views.AddGlobalFunc("CapitalizeString", func(a jet.Arguments) reflect.Value {
|
views.AddGlobalFunc("CapitalizeString", func(a jet.Arguments) reflect.Value {
|
||||||
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
s := a.Get(0).Convert(reflect.TypeFor[string]()).String()
|
||||||
return reflect.ValueOf(util.CapitalizeString(s))
|
return reflect.ValueOf(util.CapitalizeString(s))
|
||||||
|
|||||||
+85
-25
@@ -10,6 +10,9 @@
|
|||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ amsterdam_dialog.Title }}</h1>
|
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ amsterdam_dialog.Title }}</h1>
|
||||||
|
{{ if amsterdam_dialog.Subtitle != "" }}
|
||||||
|
<span class="text-blue-800 text-2xl font-bold ml-2">{{ amsterdam_dialog.Subtitle }}</span>
|
||||||
|
{{ end }}
|
||||||
<hr class="border-2 border-gray-400 w-4/5 mb-4">
|
<hr class="border-2 border-gray-400 w-4/5 mb-4">
|
||||||
</div>
|
</div>
|
||||||
<form name="{{ amsterdam_dialog.FormName }}" method="POST" action="{{ amsterdam_dialog.Action }}" class="max-w-2xl">
|
<form name="{{ amsterdam_dialog.FormName }}" method="POST" action="{{ amsterdam_dialog.Action }}" class="max-w-2xl">
|
||||||
@@ -58,26 +61,28 @@
|
|||||||
{{ range amsterdam_dialog.Fields }}
|
{{ range amsterdam_dialog.Fields }}
|
||||||
{{ if .Type == "text" || .Type == "ams_id" || .Type == "email" }}
|
{{ if .Type == "text" || .Type == "ams_id" || .Type == "email" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
value="{{ .Value }}"
|
value="{{ .Value }}" {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "integer" }}
|
{{ else if .Type == "integer" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="text" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
value="{{ .Value }}"
|
value="{{ .Value }}" {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
||||||
{{ vr := .ValueRange() }}
|
{{ vr := .ValueRange() }}
|
||||||
{{ if vr.Low != -1 && vr.High != -1 }}
|
{{ if vr.Low != -1 && vr.High != -1 }}
|
||||||
@@ -86,36 +91,69 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "password" }}
|
{{ else if .Type == "password" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}:
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
<input type="password" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="password" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
{{ if .Size > 0 }}size="{{ .Size }}"{{ end }}
|
||||||
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
{{ if .MaxLength > 0 }}maxlength="{{ .MaxLength }}"{{ end }}
|
||||||
value="{{ .Value }}"
|
value="{{ .Value }}" {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "checkbox" }}
|
{{ else if .Type == "checkbox" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<div class="w-24 text-right pr-4">
|
<div class="w-24 text-right pr-4">
|
||||||
<input type="checkbox" id="{{ .Name }}" name="{{ .Name }}"
|
<input type="checkbox" id="{{ .Name }}" name="{{ .Name }}"
|
||||||
value="Y" {{ if .Value != "" }}checked{{ end }}
|
value="Y" {{ if .Value != "" }}checked{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
|
class="w-4 h-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500" />
|
||||||
</div>
|
</div>
|
||||||
<label for="{{ .Name }}" class="flex-1 text-black text-sm">
|
<label for="{{ .Name }}" class="flex-1 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "countrylist" }}
|
{{ else if .Type == "dropdown" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
{{ v := .Value }}
|
{{ v := .Value }}
|
||||||
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }}
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
{{ range .Choices }}
|
||||||
|
<option value="{{ .Id }}" {{ if .Id == v }}selected{{ end }}>{{ .Text }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{ else if .Type == "rolelist" }}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
|
{{ v := .Value }}
|
||||||
|
{{ rl := AmRoleList(.Param) }}
|
||||||
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
{{ range rl.Roles() }}
|
||||||
|
<option value="{{ .LevelStr() }}" {{ if .LevelStr() == v }}selected{{ end }}>{{ .Name() }}</option>
|
||||||
|
{{ end }}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{ else if .Type == "countrylist" }}
|
||||||
|
<div class="flex items-center">
|
||||||
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
|
</label>
|
||||||
|
{{ v := .Value }}
|
||||||
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="flex-1 px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="XX" {{ if v == "XX" }}selected{{ end }}>🏳️ (unknown)</option>
|
<option value="XX" {{ if v == "XX" }}selected{{ end }}>🏳️ (unknown)</option>
|
||||||
{{ range GetCountryList() }}
|
{{ range GetCountryList() }}
|
||||||
@@ -126,12 +164,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "localelist" }}
|
{{ else if .Type == "localelist" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
{{ v := .Value }}
|
{{ v := .Value }}
|
||||||
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }}
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 max-w-md px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="flex-1 max-w-md px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
{{ range GetLanguageList() }}
|
{{ range GetLanguageList() }}
|
||||||
<option value="{{ .Tag }}" {{ if .Tag == v }}selected{{ end }}>{{ .Name }}</option>
|
<option value="{{ .Tag }}" {{ if .Tag == v }}selected{{ end }}>{{ .Name }}</option>
|
||||||
@@ -140,12 +179,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "tzlist" }}
|
{{ else if .Type == "tzlist" }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label for="{{ .Name }}" class="w-64 text-right pr-4 text-black text-sm">
|
<label for="{{ .Name }}"
|
||||||
|
class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
{{ v := .Value }}
|
{{ v := .Value }}
|
||||||
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }}
|
<select id="{{ .Name }}" name="{{ .Name }}" {{ if .Required }}required{{ end }} {{ if .Disabled }}disabled{{ end }}
|
||||||
class="flex-1 max-w-md px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="flex-1 max-w-md px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
{{ range GetTimeZoneList() }}
|
{{ range GetTimeZoneList() }}
|
||||||
<option value="{{ . }}" {{ if v == . }}selected{{ end }}>{{ . }}</option>
|
<option value="{{ . }}" {{ if v == . }}selected{{ end }}>{{ . }}</option>
|
||||||
@@ -155,25 +195,27 @@
|
|||||||
{{ else if .Type == "date" }}
|
{{ else if .Type == "date" }}
|
||||||
{{ dv := .DateValues() }}
|
{{ dv := .DateValues() }}
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<label class="w-64 text-right pr-4 text-black text-sm">
|
<label class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm">
|
||||||
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
{{ .Caption }}{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }}
|
||||||
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
{{ if .Required }}<span class="text-red-600">*</span>{{ end }}
|
||||||
</label>
|
</label>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<select name="{{ .Name }}_month"
|
<select name="{{ .Name }}_month" {{ if .Disabled }}disabled{{ end }}
|
||||||
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" {{ if dv[0] == -1 }}selected{{ end }}>---</option>
|
<option value="-1" {{ if dv[0] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range i := GetMonthList() }}
|
{{ range i := GetMonthList() }}
|
||||||
<option value="{{ i + 1 }}" {{ if dv[0] == i + 1 }}selected{{ end }}>{{ . }}</option>
|
<option value="{{ i + 1 }}" {{ if dv[0] == i + 1 }}selected{{ end }}>{{ . }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
<select name="{{ .Name }}_day" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<select name="{{ .Name }}_day" {{ if .Disabled }}disabled{{ end }}
|
||||||
|
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" {{ if dv[1] == -1 }}selected{{ end }}>---</option>
|
<option value="-1" {{ if dv[1] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range MakeIntRange(1, 32, 1) }}
|
{{ range MakeIntRange(1, 32, 1) }}
|
||||||
<option value="{{ . }}" {{ if dv[1] == . }}selected{{ end }}>{{ . }}</option>
|
<option value="{{ . }}" {{ if dv[1] == . }}selected{{ end }}>{{ . }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
<select name="{{ .Name }}_year" class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
<select name="{{ .Name }}_year" {{ if .Disabled }}disabled{{ end }}
|
||||||
|
class="px-3 py-2 border border-gray-300 rounded focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="-1" {{ if dv[2] == -1 }}selected{{ end }}>---</option>
|
<option value="-1" {{ if dv[2] == -1 }}selected{{ end }}>---</option>
|
||||||
{{ range MakeYearRange(.Param) }}
|
{{ range MakeYearRange(.Param) }}
|
||||||
<option value="{{ . }}" {{ if dv[2] == . }}selected{{ end }}>{{ . }}</option>
|
<option value="{{ . }}" {{ if dv[2] == . }}selected{{ end }}>{{ . }}</option>
|
||||||
@@ -183,13 +225,31 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "userphoto" }}
|
{{ else if .Type == "userphoto" }}
|
||||||
<div class="flex items-start">
|
<div class="flex items-start">
|
||||||
<label class="w-64 text-right pr-4 text-black text-sm pt-2">{{ .Caption }}
|
<label class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm pt-2">{{ .Caption }}
|
||||||
{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }} (click to change):</label>
|
{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }} (click to change):</label>
|
||||||
<input type="hidden" name="{{ .Name }}_data" value="{{ .Value }}"/>
|
<input type="hidden" name="{{ .Name }}_data" value="{{ .Value }}"/>
|
||||||
<a href="/profile_photo?tgt={{ target | url }}"
|
{{ if .Disabled }}
|
||||||
class="border-2 border-gray-300 rounded hover:border-blue-500 transition-colors">
|
<img src="{{ .Value }}" class="w-25 h-25">
|
||||||
<img src="{{ .Value }}" alt="Click to upload photo" class="w-25 h-25">
|
{{ else }}
|
||||||
</a>
|
<a href="/profile_photo?tgt={{ target | url }}"
|
||||||
|
class="border-2 border-gray-300 rounded hover:border-blue-500 transition-colors">
|
||||||
|
<img src="{{ .Value }}" alt="Click to upload photo" class="w-25 h-25">
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ else if .Type == "communitylogo" }}
|
||||||
|
<div class="flex items-start">
|
||||||
|
<label class="w-64 text-right pr-4 {{ if .Disabled }}text-gray-400{{ else }}text-black{{ end }} text-sm pt-2">{{ .Caption }}
|
||||||
|
{{ if .Subcaption != "" }} {{ .Subcaption }}{{ end }} (click to change):</label>
|
||||||
|
<input type="hidden" name="{{ .Name }}_data" value="{{ .Value }}"/>
|
||||||
|
{{ if .Disabled }}
|
||||||
|
<img src="/img/builtin/default-community.jpg" class="w-28 h-16 rounded">
|
||||||
|
{{ else }}
|
||||||
|
<a href="{{ .Param }}"
|
||||||
|
class="border-2 border-gray-300 rounded hover:border-blue-500 transition-colors">
|
||||||
|
<img src="/img/builtin/default-community.jpg" alt="Click to upload logo" class="w-28 h-16 rounded">
|
||||||
|
</a>
|
||||||
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ else if .Type == "header" }}
|
{{ else if .Type == "header" }}
|
||||||
<h2 class="text-lg font-bold text-black mb-4">{{ .Caption }}</h2>
|
<h2 class="text-lg font-bold text-black mb-4">{{ .Caption }}</h2>
|
||||||
@@ -202,7 +262,7 @@
|
|||||||
{{ range amsterdam_dialog.Fields }}
|
{{ range amsterdam_dialog.Fields }}
|
||||||
{{ if .Type == "button" }}
|
{{ if .Type == "button" }}
|
||||||
{{ clstmp := "bg-" + .Param + "-600 hover:bg-" + .Param + "-700" }}
|
{{ clstmp := "bg-" + .Param + "-600 hover:bg-" + .Param + "-700" }}
|
||||||
<button type="submit" name="{{ .Name }}"
|
<button type="submit" name="{{ .Name }}" {{ if .Disabled }}disabled{{ end }}
|
||||||
class="{{ clstmp }} text-white px-6 py-2 rounded font-medium transition-colors">{{ .Caption }}</button>
|
class="{{ clstmp }} text-white px-6 py-2 rounded font-medium transition-colors">{{ .Caption }}</button>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|||||||
Reference in New Issue
Block a user