Pictures in Posts now works!

This commit is contained in:
2026-02-23 22:41:59 -07:00
parent c4cecbc8a9
commit 110e917921
12 changed files with 193 additions and 46 deletions
+29
View File
@@ -436,6 +436,23 @@ func templateBozo(args jet.Arguments) reflect.Value {
return reflect.ValueOf(rc) return reflect.ValueOf(rc)
} }
// templateProfileImage returns the user profile image associated with this post.
func templateProfileImage(args jet.Arguments) reflect.Value {
post := args.Get(0).Convert(reflect.TypeFor[*database.PostHeader]()).Interface().(*database.PostHeader)
ctxt := args.Get(1).Convert(reflect.TypeFor[ui.AmContext]()).Interface().(ui.AmContext)
rc := "/img/builtin/no-user.png"
user, err := post.Creator(ctxt.Ctx())
if err == nil {
ci, err := user.ContactInfo(ctxt.Ctx())
if err == nil {
if ci.PhotoURL != nil {
rc = *ci.PhotoURL
}
}
}
return reflect.ValueOf(rc)
}
/* ReadPosts displays posts in a topic. /* ReadPosts displays posts in a topic.
* Parameters: * Parameters:
* ctxt - The AmContext for the request. * ctxt - The AmContext for the request.
@@ -555,6 +572,16 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
} }
ctxt.VarMap().Set("pseud", pseud) ctxt.VarMap().Set("pseud", pseud)
// Get property flags from conference and user.
cflags, err := conf.Flags(ctxt.Ctx())
if err != nil {
return "error", err
}
uflags, err := ctxt.CurrentUser().Flags(ctxt.Ctx())
if err != nil {
return "error", err
}
// Set permission and status flags. // Set permission and status flags.
confHidePerm := conf.TestPermission("Conference.Hide", myLevel) confHidePerm := conf.TestPermission("Conference.Hide", myLevel)
ctxt.VarMap().Set("canFreeze", confHidePerm) ctxt.VarMap().Set("canFreeze", confHidePerm)
@@ -568,6 +595,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
confPostPerm := conf.TestPermission("Conference.Post", myLevel) confPostPerm := conf.TestPermission("Conference.Post", myLevel)
ctxt.VarMap().Set("canPost", (!(topic.Frozen || topic.Archived) || confHidePerm) && confPostPerm) ctxt.VarMap().Set("canPost", (!(topic.Frozen || topic.Archived) || confHidePerm) && confPostPerm)
ctxt.VarMap().Set("showFrozenArchiveMessages", confPostPerm) ctxt.VarMap().Set("showFrozenArchiveMessages", confPostPerm)
ctxt.VarMap().Set("showPics", cflags.Get(database.ConferenceFlagPicturesInPosts) && uflags.Get(database.UserFlagPicturesInPosts))
// Set advanced controls. // Set advanced controls.
advancedControls := ctxt.HasParameter("ac") && (len(posts) == 1) advancedControls := ctxt.HasParameter("ac") && (len(posts) == 1)
@@ -622,6 +650,7 @@ func ReadPosts(ctxt ui.AmContext) (string, any) {
ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName) ctxt.VarMap().SetFunc("post_getUserName", templateExtractUserName)
ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo) ctxt.VarMap().SetFunc("post_getAttachmentInfo", templateAttachmentInfo)
ctxt.VarMap().SetFunc("post_isBozo", templateBozo) ctxt.VarMap().SetFunc("post_isBozo", templateBozo)
ctxt.VarMap().SetFunc("post_profileImage", templateProfileImage)
ctxt.VarMap().Set("post_stem", fmt.Sprintf("%s/r/%d", urlStem, topic.Number)) ctxt.VarMap().Set("post_stem", fmt.Sprintf("%s/r/%d", urlStem, topic.Number))
ctxt.VarMap().Set("post_max", topic.TopMessage) ctxt.VarMap().Set("post_max", topic.TopMessage)
ctxt.VarMap().Set("posts", posts) ctxt.VarMap().Set("posts", posts)
+13
View File
@@ -90,6 +90,7 @@ type AmConfig struct {
CountryList struct { CountryList struct {
Prioritize string `yaml:"prioritize"` Prioritize string `yaml:"prioritize"`
} `yaml:"countryList"` } `yaml:"countryList"`
VeniceCompatibleImageURLs bool `yaml:"veniceCompatibleImageURLs"`
} `yaml:"rendering"` } `yaml:"rendering"`
Posting struct { Posting struct {
ExternalDictionary string `yaml:"externalDictionary"` ExternalDictionary string `yaml:"externalDictionary"`
@@ -198,6 +199,17 @@ func overlayInt(loaded int, defaulted int) int {
return defaulted return defaulted
} }
/* overlayOptionFlag is a helper that takes a loaded or defaulted option flag and returns it.
* Parameters:
* loaded - The option flag loaded from a configuration file.
* defaulted - The default value of this option flag.
* Returns:
* Combined value.
*/
func overlayOptionFlag(loaded, defaulted bool) bool {
return loaded || defaulted
}
/* overlayConfig takes two configuration structures and overlays them to create the third. /* overlayConfig takes two configuration structures and overlays them to create the third.
* Parameters: * Parameters:
* dest - Points to the destination copnfiguration structure. * dest - Points to the destination copnfiguration structure.
@@ -230,6 +242,7 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) {
dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir) dest.Rendering.TemplateDir = overlayString(loaded.Rendering.TemplateDir, defaults.Rendering.TemplateDir)
dest.Rendering.CookieKey = overlayString(loaded.Rendering.CookieKey, defaults.Rendering.CookieKey) dest.Rendering.CookieKey = overlayString(loaded.Rendering.CookieKey, defaults.Rendering.CookieKey)
dest.Rendering.CountryList.Prioritize = overlayString(loaded.Rendering.CountryList.Prioritize, defaults.Rendering.CountryList.Prioritize) dest.Rendering.CountryList.Prioritize = overlayString(loaded.Rendering.CountryList.Prioritize, defaults.Rendering.CountryList.Prioritize)
dest.Rendering.VeniceCompatibleImageURLs = overlayOptionFlag(loaded.Rendering.VeniceCompatibleImageURLs, defaults.Rendering.VeniceCompatibleImageURLs)
dest.Posting.ExternalDictionary = overlayString(loaded.Posting.ExternalDictionary, defaults.Posting.ExternalDictionary) dest.Posting.ExternalDictionary = overlayString(loaded.Posting.ExternalDictionary, defaults.Posting.ExternalDictionary)
dest.Posting.Uploads.MaxSize = overlayString(loaded.Posting.Uploads.MaxSize, defaults.Posting.Uploads.MaxSize) dest.Posting.Uploads.MaxSize = overlayString(loaded.Posting.Uploads.MaxSize, defaults.Posting.Uploads.MaxSize)
dest.Posting.Uploads.NoCompressTypes = overlayStringArray(loaded.Posting.Uploads.NoCompressTypes, defaults.Posting.Uploads.NoCompressTypes) dest.Posting.Uploads.NoCompressTypes = overlayStringArray(loaded.Posting.Uploads.NoCompressTypes, defaults.Posting.Uploads.NoCompressTypes)
+1
View File
@@ -43,6 +43,7 @@ rendering:
cookiekey: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz cookiekey: ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
countryList: countryList:
prioritize: US prioritize: US
veniceCompatibleImageURLs: false
posting: posting:
externalDictionary: "" externalDictionary: ""
uploads: uploads:
+1 -1
View File
@@ -72,7 +72,7 @@ _(italicized items can be deferred)_
- Import Messages - Import Messages
- ~~Delete Conference~~ - ~~Delete Conference~~
- ~~Add to Hotlist/Remove from Hotlist~~ - ~~Add to Hotlist/Remove from Hotlist~~
- Actually implement pictures in posts - ~~Actually implement pictures in posts~~
- ~~Related to bugs in Export Messages caused by bad data:~~ - ~~Related to bugs in Export Messages caused by bad data:~~
- ~~Provide a per-conference flag that will set BuggyAttachment behavior~~ - ~~Provide a per-conference flag that will set BuggyAttachment behavior~~
- ~~New feature: remove attachment from message (requires Conference.Nuke permission)~~ - ~~New feature: remove attachment from message (requires Conference.Nuke permission)~~
+43 -1
View File
@@ -1,6 +1,6 @@
/* /*
* Amsterdam Web Communities System * Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -34,6 +34,12 @@ var ENOACCOUNT error = errors.New("you cannot create a new account while logged
func LoginForm(ctxt ui.AmContext) (string, any) { func LoginForm(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -63,6 +69,12 @@ func Login(ctxt ui.AmContext) (string, any) {
if err == nil { if err == nil {
dlg.LoadFromForm(ctxt) dlg.LoadFromForm(ctxt)
target := dlg.Field("tgt").Value target := dlg.Field("tgt").Value
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -166,6 +178,12 @@ func Logout(ctxt ui.AmContext) (string, any) {
func VerifyEmailForm(ctxt ui.AmContext) (string, any) { func VerifyEmailForm(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -222,6 +240,12 @@ func VerifyEMail(ctxt ui.AmContext) (string, any) {
if err == nil { if err == nil {
dlg.LoadFromForm(ctxt) dlg.LoadFromForm(ctxt)
target := dlg.Field("tgt").Value target := dlg.Field("tgt").Value
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -276,6 +300,12 @@ func VerifyEMail(ctxt ui.AmContext) (string, any) {
func NewAccountUserAgreement(ctxt ui.AmContext) (string, any) { func NewAccountUserAgreement(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -302,6 +332,12 @@ func NewAccountUserAgreement(ctxt ui.AmContext) (string, any) {
func NewAccountForm(ctxt ui.AmContext) (string, any) { func NewAccountForm(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -337,6 +373,12 @@ func NewAccount(ctxt ui.AmContext) (string, any) {
if err == nil { if err == nil {
dlg.LoadFromForm(ctxt) dlg.LoadFromForm(ctxt)
target := dlg.Field("tgt").Value target := dlg.Field("tgt").Value
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
+3
View File
@@ -54,6 +54,9 @@ func setupEcho() *echo.Echo {
e.RouteNotFound("/*", ui.AmWrap(AmNotFoundHandler), uiset...) e.RouteNotFound("/*", ui.AmWrap(AmNotFoundHandler), uiset...)
e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...) e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...)
e.GET("/img/*", ui.AmServeImage) e.GET("/img/*", ui.AmServeImage)
if config.GlobalConfig.Rendering.VeniceCompatibleImageURLs {
e.GET("/venice/imagedata/:id", ui.AmServeVeniceCompatibleImage)
}
e.GET("/static/*", ui.AmStaticFileHandler()) e.GET("/static/*", ui.AmStaticFileHandler())
e.GET("/go/:postlink", ui.AmWrap(JumpToShortcut)) e.GET("/go/:postlink", ui.AmWrap(JumpToShortcut))
-2
View File
@@ -23,14 +23,12 @@ fields:
- type: "password" - type: "password"
name: "pass1" name: "pass1"
caption: "Password" caption: "Password"
required: true
size: 32 size: 32
maxlength: 128 maxlength: 128
- type: "password" - type: "password"
name: "pass2" name: "pass2"
caption: "Password" caption: "Password"
subcaption: "(retype)" subcaption: "(retype)"
required: true
size: 32 size: 32
maxlength: 128 maxlength: 128
- type: "text" - type: "text"
+22 -1
View File
@@ -1,6 +1,6 @@
/* /*
* Amsterdam Web Communities System * Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -89,6 +89,27 @@ func AmServeImage(c echo.Context) error {
return c.String(http.StatusNotFound, err.Error()) return c.String(http.StatusNotFound, err.Error())
} }
/* AmServeVeniceCompatibleImage serves an image from the image store under a Venice-compatible URI.
* Parameters:
* c - The Echo context for this request.
* Returns:
* Standard Go error return.
*/
func AmServeVeniceCompatibleImage(c echo.Context) error {
id, err := strconv.Atoi(c.Param("id"))
if err == nil {
var img *database.ImageStore
img, err = database.AmLoadImage(c.Request().Context(), int32(id))
if err == nil {
return c.Blob(http.StatusOK, img.MimeType, img.Data)
}
}
if err == nil {
err = fmt.Errorf("image not found: %s", c.Request().URL.Path)
}
return c.String(http.StatusNotFound, err.Error())
}
/* AmProcessUploadedImage takes an image and resizes it to a specified size, returning its data. /* AmProcessUploadedImage takes an image and resizes it to a specified size, returning its data.
* Parameters: * Parameters:
* fileheader - The multipart file header from the uploaded file. * fileheader - The multipart file header from the uploaded file.
+4
View File
@@ -110,6 +110,7 @@
{{ post_overrideLink := "" }} {{ post_overrideLink := "" }}
{{ post_attach := nil }} {{ post_attach := nil }}
{{ post_bozo := false }} {{ post_bozo := false }}
{{ post_profile_pic := "" }}
{{ range i, p := posts }} {{ range i, p := posts }}
{{ post_cur = p }} {{ post_cur = p }}
{{ post_userName = post_getUserName(p, .) }} {{ post_userName = post_getUserName(p, .) }}
@@ -118,6 +119,9 @@
{{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }} {{ post_overrideLink = post_getOverrideLink(p, post_topicPermalink) }}
{{ post_attach = post_getAttachmentInfo(p, .) }} {{ post_attach = post_getAttachmentInfo(p, .) }}
{{ post_bozo = post_isBozo(p, post_topic, .) }} {{ post_bozo = post_isBozo(p, post_topic, .) }}
{{ if showPics }}
{{ post_profile_pic = post_profileImage(p, .) }}
{{ end }}
{{ include "singlepost.jet" }} {{ include "singlepost.jet" }}
{{ if advancedControls }} {{ if advancedControls }}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
+2 -1
View File
@@ -20,7 +20,8 @@
<!-- Left Column: Photo and Metadata --> <!-- Left Column: Photo and Metadata -->
<div class="flex-shrink-0 w-40"> <div class="flex-shrink-0 w-40">
<div class="border-2 border-gray-300 rounded mb-4"> <div class="border-2 border-gray-300 rounded mb-4">
<img src="{{ photoURL }}" alt="{{ username }}'s photo" class="w-full h-auto"> <img src="{{ photoURL }}" alt="{{ username }}'s photo" class="w-full h-auto rounded-3xl border-2 border-gray-300"
onerror="this.src='/img/builtin/no-user.png'">
</div> </div>
<div class="text-xs text-gray-700 space-y-2"> <div class="text-xs text-gray-700 space-y-2">
<div> <div>
+11
View File
@@ -7,6 +7,15 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. * file, You can obtain one at https://mozilla.org/MPL/2.0/.
*} *}
<div class="flex-1 border-2 border-gray-300 rounded-lg p-4 bg-white"> <div class="flex-1 border-2 border-gray-300 rounded-lg p-4 bg-white">
<div class="flex gap-2">
{{ if !post_bozo && isset(post_profile_pic) && (post_profile_pic != "") }}
<div class="flex-shrink-0">
<img src="{{ post_profile_pic }}" alt="Profile image: {{ post_userName }}"
class="w-16 h-16 rounded-lg border-2 border-gray-300"
onerror="this.src='/img/builtin/no-user.png'">
</div>
{{ end }}
<div class="flex-1">
<div class="flex justify-between items-start mb-3"> <div class="flex justify-between items-start mb-3">
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
<a href="{{ post_stem }}?r={{ post_cur.Num }}&ac=1" class="text-blue-700 hover:text-blue-900 font-mono">{{ post_cur.Num }}</a> of <a href="{{ post_stem }}?r={{ post_cur.Num }}&ac=1" class="text-blue-700 hover:text-blue-900 font-mono">{{ post_cur.Num }}</a> of
@@ -48,4 +57,6 @@
<pre class="amsPost font-mono text-sm whitespace-pre-wrap bg-gray-50 p-4 rounded border border-gray-200">{{ post_text | postRewrite | raw }}</pre> <pre class="amsPost font-mono text-sm whitespace-pre-wrap bg-gray-50 p-4 rounded border border-gray-200">{{ post_text | postRewrite | raw }}</pre>
{{ end }} {{ end }}
{{ end }} {{ end }}
</div>
</div>
</div> </div>
+25 -1
View File
@@ -1,6 +1,6 @@
/* /*
* Amsterdam Web Communities System * Amsterdam Web Communities System
* Copyright (c) 2025 Erbosoft Metaverse Design Solutions, All Rights Reserved * Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
* *
* This Source Code Form is subject to the terms of the Mozilla Public * 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 * License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -45,6 +45,12 @@ func userPhotoURL(ci *database.ContactInfo) string {
func EditProfileForm(ctxt ui.AmContext) (string, any) { func EditProfileForm(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -125,6 +131,12 @@ func EditProfile(ctxt ui.AmContext) (string, any) {
if err == nil { if err == nil {
dlg.LoadFromForm(ctxt) dlg.LoadFromForm(ctxt)
target := dlg.Field("tgt").Value target := dlg.Field("tgt").Value
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -228,6 +240,12 @@ func EditProfile(ctxt ui.AmContext) (string, any) {
func ProfilePhotoForm(ctxt ui.AmContext) (string, any) { func ProfilePhotoForm(ctxt ui.AmContext) (string, any) {
// Get target URI. // Get target URI.
target := ctxt.Parameter("tgt") target := ctxt.Parameter("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }
@@ -264,6 +282,12 @@ func ProfilePhoto(ctxt ui.AmContext) (string, any) {
return "error", err return "error", err
} }
target := ctxt.FormField("tgt") target := ctxt.FormField("tgt")
if target == "" {
v := ctxt.GetSession("lastKnownGood")
if v != nil {
target = v.(string)
}
}
if target == "" { if target == "" {
target = "/" target = "/"
} }