Add video picker and Discord video attachments.
CI / test (pull_request) Successful in 6m29s

The picker accepts one MP4 or WebM, the thread lightbox zooms photos, and Discord gets the video as a file on a follow-up message so photo embeds still render.
This commit is contained in:
2026-08-31 02:00:56 -07:00
parent 84dea8ea5d
commit f96df3222d
22 changed files with 1018 additions and 144 deletions
+37 -5
View File
@@ -37,6 +37,8 @@ type Config struct {
Blob blob.Uploader
Events events.Publisher
BaseURL string
// HoldUploadUntilDiscord keeps create-post temp files until PostedToDiscord.
HoldUploadUntilDiscord bool
}
type Server struct {
@@ -107,7 +109,27 @@ type threadPostCtx struct {
type imagePickerCtx struct {
ID string
Images []store.PostImage
Video *store.PostImage
Photos []store.PostImage
}
func splitPickerMedia(images []store.PostImage) (*store.PostImage, []store.PostImage) {
photos := make([]store.PostImage, 0, len(images))
var video *store.PostImage
for i := range images {
if images[i].Kind == store.MediaKindVideo {
img := images[i]
video = &img
continue
}
photos = append(photos, images[i])
}
return video, photos
}
func postPhotos(images []store.PostImage) []store.PostImage {
_, photos := splitPickerMedia(images)
return photos
}
func New(st store.Store, sessionStore scs.Store, templateFS fs.FS, staticFS fs.FS, cfg Config) (*Server, error) {
@@ -125,11 +147,13 @@ func New(st store.Store, sessionStore scs.Store, templateFS fs.FS, staticFS fs.F
return threadPostCtx{User: user, CSRF: csrf, Root: root, Post: post, Depth: depth}
},
"imagePicker": func(id string, images []store.PostImage) imagePickerCtx {
return imagePickerCtx{ID: id, Images: images}
video, photos := splitPickerMedia(images)
return imagePickerCtx{ID: id, Video: video, Photos: photos}
},
"newImagePicker": func(id string) imagePickerCtx {
return imagePickerCtx{ID: id}
},
"postPhotos": postPhotos,
"add": func(a, b int) int { return a + b },
"rank": func(i int) int { return i + 1 },
"isAdmin": func(u *store.User) bool { return u.Admin() },
@@ -348,7 +372,14 @@ func (s *Server) handleSubmit(w http.ResponseWriter, r *http.Request) {
if !ok {
return
}
defer cleanup()
var media []events.Media
held := false
defer func() {
if !held {
closeHeldMedia(media)
cleanup()
}
}()
if !s.requireCSRF(w, r) {
return
}
@@ -386,7 +417,7 @@ func (s *Server) handleSubmit(w http.ResponseWriter, r *http.Request) {
Body: body,
City: city,
}
images, newKeys, err := s.postImagesFromForm(r.Context(), r, post.ID, nil)
images, newKeys, media, err := s.postImagesFromForm(r.Context(), r, post.ID, nil)
if err != nil {
writePostImageRequestError(w, err)
return
@@ -397,7 +428,8 @@ func (s *Server) handleSubmit(w http.ResponseWriter, r *http.Request) {
http.Error(w, "could not save question", http.StatusInternalServerError)
return
}
s.publishPostCreated(post, post, u)
s.publishPostCreated(post, post, u, media, cleanup)
held = true
http.Redirect(w, r, "/questions/"+url.PathEscape(post.ID), http.StatusSeeOther)
}