amalgm 0.1.101 → 0.1.102
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -115,30 +115,68 @@ function isEmbeddableImageSrc(url) {
|
|
|
115
115
|
|
|
116
116
|
// --- EmbedChrome (email form) ------------------------------------------------
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
/**
|
|
119
|
+
* Embed-type glyph shown before the name — Gmail-safe geometric unicode
|
|
120
|
+
* (inline SVG is stripped by Gmail), the email stand-in for the UI's
|
|
121
|
+
* FileTypeIcon / app icon.
|
|
122
|
+
*/
|
|
123
|
+
// Codepoints with NO emoji variant — Gmail swaps U+25B6 (▶) for a colored
|
|
124
|
+
// emoji even with the U+FE0E text selector, so we use the small triangle
|
|
125
|
+
// U+25B8 (▸) instead. ◆ / ▣ / ♪ have no emoji form and render monochrome.
|
|
126
|
+
const EMBED_GLYPH = {
|
|
127
|
+
video: '▸', // ▸
|
|
128
|
+
audio: '♪', // ♪
|
|
129
|
+
app: '◆', // ◆
|
|
130
|
+
entity: '◆', // ◆
|
|
131
|
+
document: '▣', // ▣
|
|
132
|
+
file: '▣', // ▣
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
function glyphSpan(glyph) {
|
|
136
|
+
return glyph
|
|
137
|
+
? `<span style="font-size:12px;line-height:16px;color:${COLOR.text};padding-right:7px">${glyph}</span>`
|
|
138
|
+
: '';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* One header pill, the email form of EmbedChrome's floating pill: a pure-black
|
|
143
|
+
* fill (Gmail dark mode recolors near-blacks like stone-950 — which is why the
|
|
144
|
+
* name used to flip to a white pill — but leaves #000 alone) with a stone-800
|
|
145
|
+
* border carrying the rounded shape and white text.
|
|
146
|
+
*/
|
|
147
|
+
function headerPill(inner) {
|
|
148
|
+
return `<span style="display:inline-block;background:${COLOR.bg};border:1px solid ${COLOR.pillBorder};border-radius:12px;padding:5px 11px;font-size:13px;line-height:16px;color:${COLOR.text};white-space:nowrap">${inner}</span>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** `[glyph] name` — name clipped with an ellipsis (email's stand-in for the UI's edge fade). */
|
|
152
|
+
function namePill(glyph, name) {
|
|
153
|
+
const label =
|
|
154
|
+
`<span style="display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;` +
|
|
155
|
+
`white-space:nowrap;vertical-align:bottom;color:${COLOR.text}">${escapeText(name)}</span>`;
|
|
156
|
+
return headerPill(`${glyphSpan(glyph)}${label}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** The single "Open ↗" affordance — a link when href is given. */
|
|
160
|
+
function openPill(href) {
|
|
161
|
+
const inner = `Open<span style="color:${COLOR.textMuted};padding-left:5px">↗</span>`;
|
|
162
|
+
return href
|
|
163
|
+
? `<a href="${escapeAttr(href)}" target="_blank" style="text-decoration:none">${headerPill(inner)}</a>`
|
|
164
|
+
: headerPill(inner);
|
|
120
165
|
}
|
|
121
166
|
|
|
122
167
|
/**
|
|
123
168
|
* The expanded-embed card: rounded-16 stone-900 border, black fill, a header
|
|
124
|
-
* row carrying the name pill (left) and
|
|
125
|
-
*
|
|
126
|
-
* plain label (e.g. "Attachment").
|
|
169
|
+
* row carrying the `[glyph] name` pill (left) and a single Open pill (right),
|
|
170
|
+
* media below. `action.href` points the Open pill; pass action:null for none.
|
|
127
171
|
*/
|
|
128
|
-
function embedShell({
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
? `<a href="${escapeAttr(actionHref)}" target="_blank" style="text-decoration:none">${chromePill(
|
|
132
|
-
`<span style="color:${COLOR.link}">${escapeText(actionLabel)}</span>`,
|
|
133
|
-
)}</a>`
|
|
134
|
-
: actionText
|
|
135
|
-
? chromePill(`<span style="color:${COLOR.textMuted}">${escapeText(actionText)}</span>`)
|
|
136
|
-
: '';
|
|
172
|
+
function embedShell({ glyph, name, action, body }) {
|
|
173
|
+
const left = name ? namePill(glyph, name) : '';
|
|
174
|
+
const right = action ? openPill(action.href) : '';
|
|
137
175
|
return (
|
|
138
176
|
`<div style="border:1px solid ${COLOR.border};border-radius:16px;background:${COLOR.bg};margin:16px 0;overflow:hidden">` +
|
|
139
177
|
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>` +
|
|
140
|
-
`<td align="left" style="padding:10px 10px 8px">${
|
|
141
|
-
`<td align="right" style="padding:10px 10px 8px">${
|
|
178
|
+
`<td align="left" style="padding:10px 10px 8px">${left}</td>` +
|
|
179
|
+
`<td align="right" style="padding:10px 10px 8px;white-space:nowrap">${right}</td>` +
|
|
142
180
|
`</tr></table>` +
|
|
143
181
|
body +
|
|
144
182
|
`</div>`
|
|
@@ -147,9 +185,19 @@ function embedShell({ name, actionLabel, actionHref, actionText, body }) {
|
|
|
147
185
|
|
|
148
186
|
// --- Media embeds -------------------------------------------------------------
|
|
149
187
|
|
|
150
|
-
/**
|
|
151
|
-
|
|
152
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Bare rounded image per the ruleset — images never wear chrome. The <img> is
|
|
190
|
+
* wrapped in a link so Gmail treats it as a clickable image rather than a
|
|
191
|
+
* savable attachment, which suppresses Gmail's Download / Save-to-Photos /
|
|
192
|
+
* Save-to-Drive hover overlay. http images link to themselves; inline (cid)
|
|
193
|
+
* images take the passed href (the "open in amalgm" target).
|
|
194
|
+
*/
|
|
195
|
+
function renderImageEmbed(alt, url, href) {
|
|
196
|
+
const img = `<img src="${escapeAttr(url)}" alt="${escapeAttr(alt || '')}" style="display:block;max-width:100%;height:auto;border-radius:16px;border:1px solid ${COLOR.border};margin:16px 0">`;
|
|
197
|
+
const link = href || (isHttpUrl(url) ? url : null);
|
|
198
|
+
return link
|
|
199
|
+
? `<a href="${escapeAttr(link)}" target="_blank" style="text-decoration:none;display:block">${img}</a>`
|
|
200
|
+
: img;
|
|
153
201
|
}
|
|
154
202
|
|
|
155
203
|
const PLAY_PILL =
|
|
@@ -185,7 +233,7 @@ function renderVideoEmbed(label, url, poster) {
|
|
|
185
233
|
`<video controls preload="metadata" src="${safeUrl}"${posterSrc ? ` poster="${escapeAttr(posterSrc)}"` : ''} style="display:block;width:100%;max-height:480px;background:${COLOR.bg}">` +
|
|
186
234
|
fallback +
|
|
187
235
|
`</video>`;
|
|
188
|
-
return embedShell({ name,
|
|
236
|
+
return embedShell({ glyph: EMBED_GLYPH.video, name, action: { href: url }, body });
|
|
189
237
|
}
|
|
190
238
|
|
|
191
239
|
/**
|
|
@@ -197,16 +245,15 @@ function renderVideoEmbed(label, url, poster) {
|
|
|
197
245
|
function renderAttachedVideoCard(label, poster) {
|
|
198
246
|
const name = label || 'Video';
|
|
199
247
|
const posterSrc = poster && isEmbeddableImageSrc(poster) ? poster : null;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return embedShell({ name, actionText: 'Attachment', body });
|
|
248
|
+
// Poster is the animating GIF in Gmail; the real video rides in the
|
|
249
|
+
// attachment strip. No in-body note — the header + strip carry it.
|
|
250
|
+
// Poster <img> wrapped in a link so Gmail doesn't slap its save/download
|
|
251
|
+
// hover overlay on it (it treats linked images as clickable, not savable).
|
|
252
|
+
const body = posterSrc
|
|
253
|
+
? `<a href="https://amalgm.ai" target="_blank" style="text-decoration:none;display:block"><img src="${escapeAttr(posterSrc)}" alt="${escapeAttr(name)}" style="display:block;width:100%;height:auto;background:${COLOR.bg}"></a>`
|
|
254
|
+
: `<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>` +
|
|
255
|
+
`<td align="center" style="padding:44px 16px;background:${COLOR.bg}">${PLAY_PILL}</td></tr></table>`;
|
|
256
|
+
return embedShell({ glyph: EMBED_GLYPH.video, name, action: { href: 'https://amalgm.ai' }, body });
|
|
210
257
|
}
|
|
211
258
|
|
|
212
259
|
/**
|
|
@@ -216,18 +263,19 @@ function renderAttachedVideoCard(label, poster) {
|
|
|
216
263
|
function renderFileCardEmbed(label, url) {
|
|
217
264
|
const name = label || mediaNameOf(url) || 'File';
|
|
218
265
|
const ext = mediaExtensionOf(name).toUpperCase();
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
266
|
+
const http = isHttpUrl(url);
|
|
267
|
+
const glyph = mediaKindOf(url) === 'audio' ? EMBED_GLYPH.audio : EMBED_GLYPH.document;
|
|
268
|
+
const extChip = ext
|
|
269
|
+
? `<span style="font-size:10px;color:${COLOR.textMuted};padding-left:8px">${escapeText(ext)}</span>`
|
|
270
|
+
: '';
|
|
271
|
+
const meta = http
|
|
272
|
+
? ''
|
|
273
|
+
: `<span style="font-size:12px;color:${COLOR.textMuted};padding-left:10px">On your computer</span>`;
|
|
274
|
+
const body =
|
|
275
|
+
`<div style="padding:6px 16px 16px">` +
|
|
276
|
+
`<span style="font-size:13px;color:${COLOR.text}">${escapeText(name)}</span>${extChip}${meta}` +
|
|
277
|
+
`</div>`;
|
|
278
|
+
return embedShell({ glyph, name, action: { href: http ? url : 'https://amalgm.ai' }, body });
|
|
231
279
|
}
|
|
232
280
|
|
|
233
281
|
/**
|
|
@@ -243,7 +291,7 @@ function renderFileCardEmbed(label, url) {
|
|
|
243
291
|
*/
|
|
244
292
|
function renderMediaEmbed(alt, url, poster) {
|
|
245
293
|
if (isCidUrl(url)) {
|
|
246
|
-
return renderImageEmbed(alt, url);
|
|
294
|
+
return renderImageEmbed(alt, url, 'https://amalgm.ai');
|
|
247
295
|
}
|
|
248
296
|
const attachedMatch = url.match(/^attached:(.*)$/i);
|
|
249
297
|
if (attachedMatch) {
|
|
@@ -313,13 +361,11 @@ function renderEntityExpanded(label, type) {
|
|
|
313
361
|
const display = label || entityTypeLabel(type);
|
|
314
362
|
const body =
|
|
315
363
|
`<table role="presentation" width="100%" cellpadding="0" cellspacing="0"><tr>` +
|
|
316
|
-
`<td align="center" style="padding:
|
|
364
|
+
`<td align="center" style="padding:40px 16px 44px">` +
|
|
317
365
|
`<span style="display:block;font-size:11px;line-height:14px;color:${COLOR.textMuted};text-transform:uppercase;letter-spacing:0.04em;padding-bottom:6px">${escapeText(entityTypeLabel(type))}</span>` +
|
|
318
|
-
`<span style="display:block;font-size:16px;font-weight:650;color:${COLOR.text}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
`</a></td></tr></table>`;
|
|
322
|
-
return embedShell({ name: display, actionLabel: 'Open ↗', actionHref: 'https://amalgm.ai', body });
|
|
366
|
+
`<span style="display:block;font-size:16px;font-weight:650;color:${COLOR.text}">${escapeText(display)}</span>` +
|
|
367
|
+
`</td></tr></table>`;
|
|
368
|
+
return embedShell({ glyph: EMBED_GLYPH.entity, name: display, action: { href: 'https://amalgm.ai' }, body });
|
|
323
369
|
}
|
|
324
370
|
|
|
325
371
|
function renderEntityEmbed(mode, type, label) {
|