mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(attachments): unify kind-icon, fix coordinator audio pill + thumbnail-error gap
Three copies of the kind->glyph mapping had drifted: the coordinator pill rendered audio as the document glyph (not the audio note) and showed no inline preview, diverging from the interactive pane. Export kindIcon() from composer_attachments.js (+ window bridge) as the single source of truth; the interactive pane imports it and the coordinator pill uses it. Wire the coordinator pill to buildAttachmentPreview too (image/pdf thumbnail, audio player), gracefully no-oping on history replay (which omits attachment_id), matching interactive. Also fix buildAttachmentPreview's thumbnail-error handler: it called img.remove(), but the caller has already replaced the icon span with the img, so a failed thumbnail left a blank gap. Swap in the kind glyph instead (.attach-preview-icon, sized to the thumbnail slot).
This commit is contained in:
@@ -778,13 +778,38 @@ function createCoordinatorPane(root, wsId, opts) {
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "msg-user-attach-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent = kind === "image" ? "🖼" : "📄";
|
||||
icon.textContent =
|
||||
typeof window.kindIcon === "function"
|
||||
? window.kindIcon(kind)
|
||||
: kind === "image"
|
||||
? "🖼"
|
||||
: kind === "audio"
|
||||
? "🎵"
|
||||
: "📄";
|
||||
pill.appendChild(icon);
|
||||
const name = document.createElement("span");
|
||||
name.className = "msg-user-attach-name";
|
||||
name.textContent =
|
||||
(a && a.filename) || (kind === "image" ? "image" : "document");
|
||||
(a && a.filename) ||
|
||||
(kind === "image" ? "image" : kind === "audio" ? "audio" : "document");
|
||||
pill.appendChild(name);
|
||||
// Inline preview (image/pdf thumbnail, audio player) — the same affordance
|
||||
// the interactive pane renders, shared via the buildAttachmentPreview
|
||||
// window bridge composer_attachments.js installs. No-ops on history
|
||||
// replay (the /history projection omits attachment_id), matching interactive.
|
||||
const prev =
|
||||
typeof window.buildAttachmentPreview === "function"
|
||||
? window.buildAttachmentPreview({
|
||||
kind: kind,
|
||||
wsId: wsId,
|
||||
attachmentId: a && a.attachment_id,
|
||||
filename: a && a.filename,
|
||||
})
|
||||
: null;
|
||||
if (prev) {
|
||||
if (kind === "image" || kind === "pdf") icon.replaceWith(prev);
|
||||
else pill.appendChild(prev);
|
||||
}
|
||||
pills.appendChild(pill);
|
||||
});
|
||||
el.appendChild(pills);
|
||||
|
||||
@@ -88,6 +88,18 @@
|
||||
background: var(--bg-surface);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
/* Fallback when a thumbnail fails to load: the kind glyph in the same slot the
|
||||
thumbnail would have occupied (so layout doesn't jump and no blank gap). */
|
||||
.attach-preview-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
.attach-preview-audio {
|
||||
height: 30px;
|
||||
max-width: min(240px, 100%);
|
||||
@@ -114,6 +126,11 @@
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.msg-user-attach-pill .attach-preview-icon {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
font-size: 22px;
|
||||
}
|
||||
.msg-user-attach-pill .attach-preview-audio {
|
||||
height: 32px;
|
||||
max-width: min(280px, 100%);
|
||||
|
||||
@@ -57,7 +57,11 @@ function _attachUrl(wsId, id, suffix) {
|
||||
);
|
||||
}
|
||||
|
||||
function _kindIcon(kind) {
|
||||
// Canonical kind → glyph mapping. Exported (and mirrored on `window` below) so
|
||||
// the interactive pane and the coordinator pill builder share one source of
|
||||
// truth instead of re-deriving it — the audio (🎵) case in particular has
|
||||
// drifted before.
|
||||
export function kindIcon(kind) {
|
||||
if (kind === "image") return "🖼";
|
||||
if (kind === "audio") return "🎵";
|
||||
return "📄"; // pdf + text
|
||||
@@ -78,9 +82,15 @@ export function buildAttachmentPreview(opts) {
|
||||
img.decoding = "async";
|
||||
img.alt = "";
|
||||
img.src = _attachUrl(wsId, id, "/thumbnail");
|
||||
// Drop the node if the thumbnail can't render, so the icon shows instead.
|
||||
// If the thumbnail can't render, swap in the kind glyph rather than removing
|
||||
// the node: the caller has already replaced the original icon span with this
|
||||
// img, so a bare remove() would leave a blank gap (no icon at all).
|
||||
img.addEventListener("error", function () {
|
||||
img.remove();
|
||||
var fallback = document.createElement("span");
|
||||
fallback.className = "attach-preview attach-preview-icon";
|
||||
fallback.setAttribute("aria-hidden", "true");
|
||||
fallback.textContent = kindIcon(kind);
|
||||
img.replaceWith(fallback);
|
||||
});
|
||||
return img;
|
||||
}
|
||||
@@ -175,7 +185,7 @@ export function createAttachmentController(opts) {
|
||||
var icon = document.createElement("span");
|
||||
icon.className = "composer-chip-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent = _kindIcon(info.kind);
|
||||
icon.textContent = kindIcon(info.kind);
|
||||
chip.appendChild(icon);
|
||||
|
||||
var name = document.createElement("span");
|
||||
@@ -279,7 +289,7 @@ export function createAttachmentController(opts) {
|
||||
// classify_upload on the server is authoritative — adopt its kind for the
|
||||
// chip icon, then render the preview now there's a real id.
|
||||
var swapIcon = chip.querySelector(".composer-chip-icon");
|
||||
if (swapIcon) swapIcon.textContent = _kindIcon(info.kind);
|
||||
if (swapIcon) swapIcon.textContent = kindIcon(info.kind);
|
||||
var name = chip.querySelector(".composer-chip-name");
|
||||
if (name) {
|
||||
name.textContent = info.filename || "(unnamed)";
|
||||
@@ -438,3 +448,4 @@ export function createAttachmentController(opts) {
|
||||
// this deferred module evaluated). New module code imports instead.
|
||||
window.createAttachmentController = createAttachmentController;
|
||||
window.buildAttachmentPreview = buildAttachmentPreview;
|
||||
window.kindIcon = kindIcon;
|
||||
|
||||
@@ -37,7 +37,10 @@ import {
|
||||
import { authFetch } from "./auth.js";
|
||||
import { showToast } from "./toast.js";
|
||||
import { Composer } from "./composer.js";
|
||||
import { createAttachmentController } from "./composer_attachments.js";
|
||||
import {
|
||||
createAttachmentController,
|
||||
kindIcon,
|
||||
} from "./composer_attachments.js";
|
||||
import { createQueueController } from "./composer_queue.js";
|
||||
import { StatusBar } from "./status_bar.js";
|
||||
import { streamingRender, streamingRenderFinalize } from "./renderer.js";
|
||||
@@ -373,12 +376,7 @@ class Pane {
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "msg-user-attach-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent =
|
||||
a.kind === "image"
|
||||
? "\ud83d\uddbc"
|
||||
: a.kind === "audio"
|
||||
? "\ud83c\udfb5"
|
||||
: "\ud83d\udcc4";
|
||||
icon.textContent = kindIcon(a.kind);
|
||||
pill.appendChild(icon);
|
||||
const nameEl = document.createElement("span");
|
||||
nameEl.className = "msg-user-attach-name";
|
||||
|
||||
Reference in New Issue
Block a user