diff --git a/backend/internal/web/handlers_discussion_test.go b/backend/internal/web/handlers_discussion_test.go index 86dac65..cdd3e64 100644 --- a/backend/internal/web/handlers_discussion_test.go +++ b/backend/internal/web/handlers_discussion_test.go @@ -472,6 +472,32 @@ func TestDiscussionBrokerUnregistersOnCancel(t *testing.T) { } } +func TestDiscussionStaticScriptSuppressesDuplicateMessageSwap(t *testing.T) { + js, err := os.ReadFile("../../static/discussion-sse.js") + if err != nil { + t.Fatalf("read discussion-sse.js: %v", err) + } + script := string(js) + for _, want := range []string{"htmx:beforeSwap", "messageIdFromHTML", "preventDefault()"} { + if !strings.Contains(script, want) { + t.Fatalf("discussion script missing %q for duplicate swap suppression; script: %.1200s", want, script) + } + } +} + +func TestDiscussionStaticScriptClearsComposerAfterSuccessfulPost(t *testing.T) { + js, err := os.ReadFile("../../static/discussion-sse.js") + if err != nil { + t.Fatalf("read discussion-sse.js: %v", err) + } + script := string(js) + for _, want := range []string{"htmx:afterRequest", "discussion-message-body", ".reset()"} { + if !strings.Contains(script, want) { + t.Fatalf("discussion script missing %q for composer reset; script: %.1200s", want, script) + } + } +} + func TestTablosListDiscussionUnreadBadge(t *testing.T) { pool, cleanup := setupTestDB(t) defer cleanup() diff --git a/backend/static/discussion-sse.js b/backend/static/discussion-sse.js index 475f440..56024a1 100644 --- a/backend/static/discussion-sse.js +++ b/backend/static/discussion-sse.js @@ -3,6 +3,18 @@ return Boolean(document.querySelector('[data-message-id="' + CSS.escape(messageId) + '"]')); } + function isDiscussionMessageForm(element) { + return Boolean(element && element.matches && element.matches('form[action$="/discussion/messages"]')); + } + + function messageIdFromHTML(html) { + if (!html) return ""; + var template = document.createElement("template"); + template.innerHTML = html.trim(); + var message = template.content.querySelector("[data-message-id]"); + return message ? message.dataset.messageId : ""; + } + function ensureMessageList(container) { var messages = container.querySelector("#discussion-messages"); if (!messages) return null; @@ -52,4 +64,21 @@ document.addEventListener("DOMContentLoaded", connectDiscussionStreams); document.body.addEventListener("htmx:afterSwap", connectDiscussionStreams); + document.body.addEventListener("htmx:beforeSwap", function (event) { + if (!isDiscussionMessageForm(event.detail && event.detail.elt)) return; + + var messageId = messageIdFromHTML(event.detail.xhr && event.detail.xhr.responseText); + if (messageId && messageExists(messageId)) { + event.detail.shouldSwap = false; + event.preventDefault(); + } + }); + document.body.addEventListener("htmx:afterRequest", function (event) { + var form = event.detail && event.detail.elt; + if (!isDiscussionMessageForm(form) || !event.detail.successful) return; + + form.reset(); + var textarea = form.querySelector("#discussion-message-body"); + if (textarea) textarea.value = ""; + }); })();