Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions app/handlers/apiv1/post.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package apiv1

import (
"fmt"
"strings"

"github.com/getfider/fider/app/actions"
"github.com/getfider/fider/app/metrics"
"github.com/getfider/fider/app/models/cmd"
"github.com/getfider/fider/app/models/dto"
"github.com/getfider/fider/app/models/entity"
"github.com/getfider/fider/app/models/enum"
"github.com/getfider/fider/app/models/query"
Expand All @@ -13,6 +17,35 @@ import (
"github.com/getfider/fider/app/tasks"
)

// appendUnreferencedAttachments appends a fider-image markdown reference to the end of the
// description for every attachment being added that isn't already referenced in it. This
// keeps API-created posts consistent with the editor, which embeds images inline as
// "![](fider-image:<bkey>)". Must be called after UploadImages so blob keys are finalized.
func appendUnreferencedAttachments(description string, attachments []*dto.ImageUpload) string {
seen := map[string]bool{}
refs := make([]string, 0, len(attachments))
for _, a := range attachments {
if a == nil || a.Remove || a.BlobKey == "" || seen[a.BlobKey] {
continue
}
seen[a.BlobKey] = true
if strings.Contains(description, "fider-image:"+a.BlobKey) {
continue
}
refs = append(refs, fmt.Sprintf("![](fider-image:%s)", a.BlobKey))
}

if len(refs) == 0 {
return description
}

appended := strings.Join(refs, "\n\n")
if strings.TrimSpace(description) == "" {
return appended
}
return description + "\n\n" + appended
}

// SearchPosts return existing posts based on search criteria
func SearchPosts() web.HandlerFunc {
return func(c *web.Context) error {
Expand Down Expand Up @@ -73,7 +106,7 @@ func CreatePost() web.HandlerFunc {

newPost := &cmd.AddNewPost{
Title: action.Title,
Description: action.Description,
Description: appendUnreferencedAttachments(action.Description, action.Attachments),
}
err := bus.Dispatch(c, newPost)
if err != nil {
Expand Down Expand Up @@ -133,17 +166,18 @@ func UpdatePost() web.HandlerFunc {
return c.HandleValidation(result)
}

// Upload first so blob keys are finalized before building the description.
if err := bus.Dispatch(c, &cmd.UploadImages{Images: action.Attachments, Folder: "attachments"}); err != nil {
return c.Failure(err)
}

updatePost := &cmd.UpdatePost{
Post: action.Post,
Title: action.Title,
Description: action.Description,
Description: appendUnreferencedAttachments(action.Description, action.Attachments),
}

err := bus.Dispatch(c,
&cmd.UploadImages{
Images: action.Attachments,
Folder: "attachments",
},
updatePost,
&cmd.SetAttachments{
Post: action.Post,
Expand Down
32 changes: 32 additions & 0 deletions app/handlers/apiv1/post_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,38 @@ func TestCreatePostHandler(t *testing.T) {
Expect(newPost.Description).Equals("")
}

func TestCreatePostHandler_AppendsUnreferencedAttachments(t *testing.T) {
RegisterT(t)

var newPost *cmd.AddNewPost
bus.AddHandler(func(ctx context.Context, c *cmd.AddNewPost) error {
newPost = c
c.Result = &entity.Post{ID: 1, Title: c.Title, Description: c.Description}
return nil
})
bus.AddHandler(func(ctx context.Context, q *query.GetPostBySlug) error { return app.ErrNotFound })
bus.AddHandler(func(ctx context.Context, c *cmd.SetAttachments) error { return nil })
bus.AddHandler(func(ctx context.Context, c *cmd.AddVote) error { return nil })
bus.AddHandler(func(ctx context.Context, c *cmd.UploadImages) error { return nil })

code, _ := mock.NewServer().
OnTenant(mock.DemoTenant).
AsUser(mock.JonSnow).
ExecutePost(apiv1.CreatePost(), `{
"title": "Post with attachments",
"description": "Already referenced: ![](fider-image:attachments/referenced.png)",
"attachments": [
{ "bkey": "attachments/referenced.png" },
{ "bkey": "attachments/standalone.png" }
]
}`)

Expect(code).Equals(http.StatusOK)
// The already-referenced attachment is left as-is (not duplicated), and the
// unreferenced one is appended at the end as a fider-image markdown reference.
Expect(newPost.Description).Equals("Already referenced: ![](fider-image:attachments/referenced.png)\n\n![](fider-image:attachments/standalone.png)")
}

func TestCreatePostHandler_WithoutTitle(t *testing.T) {
RegisterT(t)

Expand Down
8 changes: 1 addition & 7 deletions public/pages/Home/components/ShareFeedback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const ShareFeedback: React.FC<ShareFeedbackProps> = (props) => {
}

useEffect(() => {
if (!titleManuallyEdited && !isInitialMount) {
if (!titleManuallyEdited && !isInitialMount && !description.startsWith("![](fider-image:attachments")) {
// Find newline in the original markdown content for truncation
let newlineIndex = Math.min(description.indexOf("\n"), 80)
if (newlineIndex == -1) {
Expand Down Expand Up @@ -158,12 +158,6 @@ export const ShareFeedback: React.FC<ShareFeedbackProps> = (props) => {

const handleDescriptionChange = (value: string) => {
setCachedDescription(value)

// If the description starts with an image attachment, we don't want to set it as the title
if (value.startsWith("![](fider-image:attachments")) {
return
}

setDescription(value)
}

Expand Down