REFACTOR: move /messages/<id>/send handler into its own function
This commit is contained in:
parent
5cbf96f379
commit
73c89f70fb
@ -25,6 +25,21 @@ func (app *Application) messages_index(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.buffered_render_page(w, "tpl/messages.tpl", global_data, chat_view_data)
|
app.buffered_render_page(w, "tpl/messages.tpl", global_data, chat_view_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Application) message_send(w http.ResponseWriter, r *http.Request) {
|
||||||
|
room_id := get_room_id_from_context(r.Context())
|
||||||
|
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
panic_if(err)
|
||||||
|
var message_data struct {
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
|
panic_if(json.Unmarshal(body, &message_data))
|
||||||
|
|
||||||
|
trove := scraper.SendDMMessage(room_id, message_data.Text, 0)
|
||||||
|
app.Profile.SaveDMTrove(trove, false)
|
||||||
|
go app.Profile.SaveDMTrove(trove, true)
|
||||||
|
}
|
||||||
|
|
||||||
func (app *Application) message_detail(w http.ResponseWriter, r *http.Request) {
|
func (app *Application) message_detail(w http.ResponseWriter, r *http.Request) {
|
||||||
room_id := get_room_id_from_context(r.Context())
|
room_id := get_room_id_from_context(r.Context())
|
||||||
|
|
||||||
@ -35,25 +50,13 @@ func (app *Application) message_detail(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// First send a message, if applicable
|
// First send a message, if applicable
|
||||||
if is_sending {
|
if is_sending {
|
||||||
body, err := io.ReadAll(r.Body)
|
app.message_send(w, r)
|
||||||
panic_if(err)
|
|
||||||
var message_data struct {
|
|
||||||
Text string `json:"text"`
|
|
||||||
LatestPollingTimestamp int `json:"latest_polling_timestamp,string"`
|
|
||||||
}
|
|
||||||
panic_if(json.Unmarshal(body, &message_data))
|
|
||||||
trove := scraper.SendDMMessage(room_id, message_data.Text, 0)
|
|
||||||
app.Profile.SaveDMTrove(trove, false)
|
|
||||||
app.buffered_render_htmx(w, "dm-composer", global_data, chat_view_data) // Wipe the chat box
|
app.buffered_render_htmx(w, "dm-composer", global_data, chat_view_data) // Wipe the chat box
|
||||||
go app.Profile.SaveDMTrove(trove, true)
|
|
||||||
chat_view_data.LatestPollingTimestamp = message_data.LatestPollingTimestamp // We're going to return some messages
|
|
||||||
} else {
|
|
||||||
chat_view_data.LatestPollingTimestamp = -1 // TODO: why not 0? If `0` then it won't generate a SQL `where` clause
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Why are the input (parsed out of the HTTP request) and output (computed from the messsages
|
// `LatestPollingTimestamp` sort of passes-through the function; if we're not updating it, it
|
||||||
// fetched and printed into the HTTP response) using the same variable in `chat_view_data`?
|
// goes out the same it came in. Hence, using a single variable for it
|
||||||
|
chat_view_data.LatestPollingTimestamp = 0
|
||||||
chat_view_data.ActiveRoomID = room_id
|
chat_view_data.ActiveRoomID = room_id
|
||||||
if latest_timestamp_str := r.URL.Query().Get("latest_timestamp"); latest_timestamp_str != "" {
|
if latest_timestamp_str := r.URL.Query().Get("latest_timestamp"); latest_timestamp_str != "" {
|
||||||
var err error
|
var err error
|
||||||
@ -84,13 +87,6 @@ func (app *Application) message_detail(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Polling for updates and sending a message should add messages at the bottom of the page (newest)
|
// Polling for updates and sending a message should add messages at the bottom of the page (newest)
|
||||||
if r.URL.Query().Has("poll") || is_sending {
|
if r.URL.Query().Has("poll") || is_sending {
|
||||||
app.buffered_render_htmx(w, "messages-with-poller", global_data, chat_view_data)
|
app.buffered_render_htmx(w, "messages-with-poller", global_data, chat_view_data)
|
||||||
|
|
||||||
// OOB-swap the composer polling timestamp field since it may have updated
|
|
||||||
app.buffered_render_htmx(w, "composer-polling-timestamp-field", global_data, map[string]interface{}{
|
|
||||||
"LatestPollingTimestamp": chat_view_data.LatestPollingTimestamp,
|
|
||||||
"oob": true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -756,12 +756,17 @@ func TestMessagesRoom(t *testing.T) {
|
|||||||
assert.Len(cascadia.QueryAll(root, selector("#chat-view .dm-message")), 5)
|
assert.Len(cascadia.QueryAll(root, selector("#chat-view .dm-message")), 5)
|
||||||
|
|
||||||
// Should have the poller at the bottom
|
// Should have the poller at the bottom
|
||||||
node := cascadia.Query(root, selector("#new-messages-poller"))
|
poller := cascadia.Query(root, selector("#new-messages-poller"))
|
||||||
assert.NotNil(node)
|
assert.NotNil(poller)
|
||||||
assert.Contains(node.Attr, html.Attribute{
|
assert.Contains(poller.Attr, html.Attribute{Key: "hx-get", Val: "/messages/1488963321701171204-1178839081222115328"})
|
||||||
Key: "hx-get",
|
assert.Contains(
|
||||||
Val: "/messages/1488963321701171204-1178839081222115328?poll&latest_timestamp=1686025129144&scroll_bottom=1",
|
cascadia.Query(poller, selector("input[name='scroll_bottom']")).Attr,
|
||||||
})
|
html.Attribute{Key: "value", Val: "1"},
|
||||||
|
)
|
||||||
|
assert.Contains(
|
||||||
|
cascadia.Query(poller, selector("input[name='latest_timestamp']")).Attr,
|
||||||
|
html.Attribute{Key: "value", Val: "1686025129144"},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading the page since a given message
|
// Loading the page since a given message
|
||||||
@ -785,12 +790,17 @@ func TestMessagesRoomPollForUpdates(t *testing.T) {
|
|||||||
assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 3)
|
assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 3)
|
||||||
|
|
||||||
// Should have the poller at the bottom
|
// Should have the poller at the bottom
|
||||||
node := cascadia.Query(root, selector("#new-messages-poller"))
|
poller := cascadia.Query(root, selector("#new-messages-poller"))
|
||||||
assert.NotNil(node)
|
assert.NotNil(poller)
|
||||||
assert.Contains(node.Attr, html.Attribute{
|
assert.Contains(poller.Attr, html.Attribute{Key: "hx-get", Val: "/messages/1488963321701171204-1178839081222115328"})
|
||||||
Key: "hx-get",
|
assert.Contains(
|
||||||
Val: "/messages/1488963321701171204-1178839081222115328?poll&latest_timestamp=1686025129144&scroll_bottom=1",
|
cascadia.Query(poller, selector("input[name='scroll_bottom']")).Attr,
|
||||||
})
|
html.Attribute{Key: "value", Val: "1"},
|
||||||
|
)
|
||||||
|
assert.Contains(
|
||||||
|
cascadia.Query(poller, selector("input[name='latest_timestamp']")).Attr,
|
||||||
|
html.Attribute{Key: "value", Val: "1686025129144"},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading the page since latest message (no updates)
|
// Loading the page since latest message (no updates)
|
||||||
@ -814,12 +824,17 @@ func TestMessagesRoomPollForUpdatesEmptyResult(t *testing.T) {
|
|||||||
assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 0)
|
assert.Len(cascadia.QueryAll(root, selector(".dm-message")), 0)
|
||||||
|
|
||||||
// Should have the poller at the bottom, with the same value as previously
|
// Should have the poller at the bottom, with the same value as previously
|
||||||
node := cascadia.Query(root, selector("#new-messages-poller"))
|
poller := cascadia.Query(root, selector("#new-messages-poller"))
|
||||||
assert.NotNil(node)
|
assert.NotNil(poller)
|
||||||
assert.Contains(node.Attr, html.Attribute{
|
assert.Contains(poller.Attr, html.Attribute{Key: "hx-get", Val: "/messages/1488963321701171204-1178839081222115328"})
|
||||||
Key: "hx-get",
|
assert.Contains(
|
||||||
Val: "/messages/1488963321701171204-1178839081222115328?poll&latest_timestamp=1686025129144&scroll_bottom=1",
|
cascadia.Query(poller, selector("input[name='scroll_bottom']")).Attr,
|
||||||
})
|
html.Attribute{Key: "value", Val: "1"},
|
||||||
|
)
|
||||||
|
assert.Contains(
|
||||||
|
cascadia.Query(poller, selector("input[name='latest_timestamp']")).Attr,
|
||||||
|
html.Attribute{Key: "value", Val: "1686025129144"},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scroll back in the messages
|
// Scroll back in the messages
|
||||||
|
@ -86,23 +86,33 @@
|
|||||||
{{define "messages-with-poller"}}
|
{{define "messages-with-poller"}}
|
||||||
{{template "messages" .}}
|
{{template "messages" .}}
|
||||||
|
|
||||||
<div id="new-messages-poller"
|
<form id="new-messages-poller"
|
||||||
hx-swap="outerHTML {{if $.ScrollBottom}}scroll:.chat-messages:bottom{{end}}"
|
hx-swap="outerHTML {{if $.ScrollBottom}}scroll:.chat-messages:bottom{{end}}"
|
||||||
hx-trigger="load delay:3s"
|
hx-trigger="load delay:3s"
|
||||||
hx-get="/messages/{{$.ActiveRoomID}}?poll&latest_timestamp={{$.LatestPollingTimestamp}}&scroll_bottom={{if $.ScrollBottom}}1{{else}}0{{end}}"
|
hx-get="/messages/{{$.ActiveRoomID}}"
|
||||||
></div>
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
{{define "composer-polling-timestamp-field"}}
|
|
||||||
<input id="composerPollingTimestamp"
|
|
||||||
type="hidden"
|
|
||||||
name="latest_polling_timestamp"
|
|
||||||
value="{{.LatestPollingTimestamp}}"
|
|
||||||
{{if .oob}}
|
|
||||||
{{/* See comment about `oob` in the "dm-composer" template. Here we pass a parameter to say oob or not */}}
|
|
||||||
hx-swap-oob="true"
|
|
||||||
{{end}}
|
|
||||||
>
|
>
|
||||||
|
<input type="hidden" name="poll">
|
||||||
|
<input type="hidden" name="latest_timestamp" value="{{$.LatestPollingTimestamp}}">
|
||||||
|
<input type="hidden" name="scroll_bottom" value="{{if $.ScrollBottom}}1{{else}}0{{end}}">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* The poller's timestamp will be updated by HTMX, but the POST URL for /send needs updating too
|
||||||
|
*/
|
||||||
|
(function() {
|
||||||
|
const composer_form = document.querySelector(".dm-composer form");
|
||||||
|
if (composer_form === null) {
|
||||||
|
// Initial page load; composer isn't rendered yet
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [path, qs] = composer_form.attributes["hx-post"].value.split("?");
|
||||||
|
const params = new URLSearchParams(qs);
|
||||||
|
params.set("latest_timestamp", "{{.LatestPollingTimestamp}}");
|
||||||
|
composer_form.setAttribute("hx-post", [path, params.toString()].join("?"));
|
||||||
|
htmx.process(composer_form); // Manually enable HTMX on the manually-added node
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "chat-view"}}
|
{{define "chat-view"}}
|
||||||
@ -133,14 +143,13 @@
|
|||||||
{{if .ActiveRoomID}}
|
{{if .ActiveRoomID}}
|
||||||
<div class="dm-composer">
|
<div class="dm-composer">
|
||||||
<form
|
<form
|
||||||
hx-post="/messages/{{.ActiveRoomID}}/send"
|
hx-post="/messages/{{.ActiveRoomID}}/send?latest_timestamp={{.LatestPollingTimestamp}}"
|
||||||
hx-target="#new-messages-poller"
|
hx-target="#new-messages-poller"
|
||||||
hx-swap="outerHTML scroll:.chat-messages:bottom"
|
hx-swap="outerHTML scroll:.chat-messages:bottom"
|
||||||
hx-ext="json-enc"
|
hx-ext="json-enc"
|
||||||
>
|
>
|
||||||
{{template "dm-composer"}}
|
{{template "dm-composer"}}
|
||||||
<input id="realInput" type="hidden" name="text" value="" />
|
<input id="realInput" type="hidden" name="text" value="" />
|
||||||
{{template "composer-polling-timestamp-field" (dict "LatestPollingTimestamp" .LatestPollingTimestamp "oob" false)}}
|
|
||||||
<input type="submit" />
|
<input type="submit" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -183,25 +192,21 @@
|
|||||||
// Disable auto-scroll-bottom on new message loads if the user has scrolled up
|
// Disable auto-scroll-bottom on new message loads if the user has scrolled up
|
||||||
chat_messages.addEventListener('scroll', function() {
|
chat_messages.addEventListener('scroll', function() {
|
||||||
const _node = document.querySelector("#new-messages-poller");
|
const _node = document.querySelector("#new-messages-poller");
|
||||||
const node = _node.cloneNode()
|
const node = _node.cloneNode(true)
|
||||||
_node.remove(); // Removing and re-inserting the element cancels the HTMX polling, otherwise it will use the old values
|
_node.remove(); // Removing and re-inserting the element cancels the HTMX polling, otherwise it will use the old values
|
||||||
|
const scroll_bottom_input = node.querySelector("input[name='scroll_bottom']")
|
||||||
|
|
||||||
const scrollPosition = chat_messages.scrollTop;
|
const scrollPosition = chat_messages.scrollTop;
|
||||||
var bottomOfElement = chat_messages.scrollHeight - chat_messages.clientHeight;
|
var bottomOfElement = chat_messages.scrollHeight - chat_messages.clientHeight;
|
||||||
|
|
||||||
var [path, qs] = node.attributes["hx-get"].value.split("?")
|
|
||||||
var params = new URLSearchParams(qs)
|
|
||||||
|
|
||||||
if (scrollPosition === bottomOfElement) {
|
if (scrollPosition === bottomOfElement) {
|
||||||
// At bottom; new messages should be scrolled into view
|
// At bottom; new messages should be scrolled into view
|
||||||
node.setAttribute("hx-swap", "outerHTML scroll:.chat-messages:bottom");
|
node.setAttribute("hx-swap", "outerHTML scroll:.chat-messages:bottom");
|
||||||
params.set("scroll_bottom", "1")
|
scroll_bottom_input.value = 1;
|
||||||
node.setAttribute("hx-get", [path, params.toString()].join("?"))
|
|
||||||
} else {
|
} else {
|
||||||
// User has scrolled up; disable auto-scrolling when new messages arrive
|
// User has scrolled up; disable auto-scrolling when new messages arrive
|
||||||
node.setAttribute("hx-swap", "outerHTML");
|
node.setAttribute("hx-swap", "outerHTML");
|
||||||
params.set("scroll_bottom", "0")
|
scroll_bottom_input.value = 0;
|
||||||
node.setAttribute("hx-get", [path, params.toString()].join("?"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chat_messages.appendChild(node);
|
chat_messages.appendChild(node);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user