diff --git a/app/static/js/app.js b/app/static/js/app.js index 9902388..4539409 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -1677,7 +1677,7 @@ function createMessageElement(msg) { ` : ''} - ${window.deviceCaps?.supports_raw_resend && typeof msg.id === 'number' ? ` @@ -1710,14 +1710,14 @@ function createMessageElement(msg) {
${processMessageContent(msg.content, { isOwn: false })}
${metaInfo ? `
${metaInfo}
` : ''}
- - ${contactsGeoCache[msg.sender] ? ` - ` : ''} @@ -1732,7 +1732,7 @@ function createMessageElement(msg) { ` : ''} ${!isContactProtectedByName(msg.sender) ? ` - ` : ''} diff --git a/app/static/js/dm.js b/app/static/js/dm.js index 889cfdd..bcdffef 100644 --- a/app/static/js/dm.js +++ b/app/static/js/dm.js @@ -1499,7 +1499,7 @@ function displayMessages(messages) { // only raw resend (arrow-repeat). const resendBtn = msg.is_own ? `
-
diff --git a/app/static/js/message-utils.js b/app/static/js/message-utils.js index d732396..b826f68 100644 --- a/app/static/js/message-utils.js +++ b/app/static/js/message-utils.js @@ -309,6 +309,25 @@ function escapeHtmlAttribute(text) { .replace(/>/g, '>'); } +/** + * Serialize a value as a JS literal safe to splice into an inline event + * handler attribute. + * + * JSON.stringify() alone is not enough. Its output is dropped straight into + * markup, so a message containing an apostrophe closes the attribute early and + * a following '<' ends the whole tag — the rest of the button then leaks into + * the page as visible text (e.g. `title="Quote">` next to a broken icon), and + * the handler never runs. Escaping both quote characters and the angle + * brackets keeps the literal intact for the JS parser, which sees the + * attribute value only after HTML entities are decoded. + * + * @param {*} value - Value to pass to the handler + * @returns {string} - Escaped JSON literal, ready for an attribute + */ +function jsArg(value) { + return escapeHtmlAttribute(JSON.stringify(value === undefined ? null : value)); +} + /* ============================================================================= Share Cards — contacts, channels and positions shared inside chat messages ============================================================================= diff --git a/docs/whatsnew.md b/docs/whatsnew.md index 7c9fbe2..2c7d6c4 100644 --- a/docs/whatsnew.md +++ b/docs/whatsnew.md @@ -16,6 +16,10 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g - **Two ways to keep full access to your own demo.** Locking the instance is only half of it; you still need to administer the thing. `MC_DEMO_TRUSTED_NETS` takes a list of networks — `192.168.0.0/16`, say — whose visitors are never restricted, so from your own LAN the app behaves exactly as it always has, with nothing to type. `MC_DEMO_UNLOCK_CODE` sets a code you enter once under **Settings → Operator access**; the browser is then remembered for thirty days from any network, which is what you want when the instance is published through a tunnel and every visitor looks like it is coming from the same place. A *Lock again* button sits in the same spot for when you want to see the instance the way a visitor does. The cookie holds a derived token rather than the code itself, so changing the code in `.env` logs every unlocked browser out — which is also how you take access back. One warning worth repeating from the guide: if mc-webui is published through a tunnel or a reverse proxy, every visitor arrives from that one address, and if the tunnel runs on your LAN then listing your LAN as trusted would unlock the instance for the whole internet. mc-webui declines to make that mistake — an address that arrives with a proxy header is not matched against the trusted list unless `MC_TRUST_PROXY` is on — and says so in the log at startup, but the safe habit is to use the unlock code whenever the app is not reached directly. +### Fixes + +- **A message quoting something in angle brackets no longer breaks the row of buttons under it.** A bot answering with `Type 'channel ' for a description` was enough: the buttons beneath that message came out mangled, with a stray `title="Quote"` printed next to them, and the first two — reply and quote — stopped working. The cause was in how those buttons were built: the message text was pasted into them without being escaped, so an apostrophe closed the button early and a `<` ended it altogether, spilling the rest of the markup onto the page as text. The text is now escaped properly wherever it is handed to a button, in channel chat and in direct messages alike, so quotes, angle brackets and both kinds of quotation mark are just characters again. The same fix covers sender names containing an apostrophe, whose reply, map and block buttons were quietly dead for the same reason. + --- ## 2.11.0 — 2026-08-20