← back to Rentv
Content Studio: one-button Email-to-Boom now includes images + video links (rich send)
7d1be369f8c90ffd01dc9e74e036942acdc7b42b · 2026-08-05 11:16:25 -0700 · steve
Files touched
M public/admin/pr-intelligence/content-studio.htmlM server.js
Diff
commit 7d1be369f8c90ffd01dc9e74e036942acdc7b42b
Author: steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 11:16:25 2026 -0700
Content Studio: one-button Email-to-Boom now includes images + video links (rich send)
---
public/admin/pr-intelligence/content-studio.html | 6 ++++--
server.js | 14 +++++++++++++-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/public/admin/pr-intelligence/content-studio.html b/public/admin/pr-intelligence/content-studio.html
index 62050d2e..a1f4b90d 100644
--- a/public/admin/pr-intelligence/content-studio.html
+++ b/public/admin/pr-intelligence/content-studio.html
@@ -142,6 +142,7 @@
<div class="cc" id="imgmeta"></div>
<div id="imgcands" style="display:flex;flex-wrap:wrap;gap:8px;margin:0 0 10px"></div>
<div class="cs-field"><label>Photo credit (the firm the image came from)</label><input id="b_credit" placeholder="e.g. CBRE"></div>
+ <div class="cs-field"><label>Video link(s) — included in the email to Boom (comma/newline separated)</label><input id="b_videos" placeholder="https://… reel / teaser / video URLs"></div>
<div class="cs-field"><label>Body <a class="cs-copy" data-copy="b_body">📋 copy</a></label><textarea id="b_body" class="cs-body"></textarea></div>
<div class="cs-row">
<button class="btn" id="publish">🚀 Publish to blog</button>
@@ -169,7 +170,7 @@
const srcEl = document.getElementById('src');
gen.disabled = false; gen.textContent = '✨ Pull highlights + draft';
$('b_image').value = selected.image ? (selected.image.startsWith('http') ? selected.image : location.origin + selected.image) : '';
- $('b_credit').value = ''; $('imgcands').innerHTML = ''; $('imgmeta').innerHTML = '';
+ $('b_credit').value = ''; $('b_videos').value = ''; $('imgcands').innerHTML = ''; $('imgmeta').innerHTML = '';
// RENTV stories (numeric id) get a full-body fetch; external sources use their RSS summary.
const isRentv = /^\d+$/.test(String(selected.id)) && src === 'RENTV';
if (isRentv) {
@@ -277,7 +278,8 @@
try {
const r = await PR.api('/../content/email', { method: 'POST', body: {
title: $('b_title').value.trim(), dek: $('b_dek').value.trim(), image: $('b_image').value.trim(),
- photo_credit: $('b_credit').value.trim(), body: $('b_body').value, linkedin: $('s_li').value, x: $('s_x').value,
+ photo_credit: $('b_credit').value.trim(), videos: $('b_videos').value, body: $('b_body').value,
+ linkedin: $('s_li').value, x: $('s_x').value,
} });
$('pubmeta').innerHTML = `<span class="cs-ok">✅ Emailed to ${PR.esc(r.to)}</span>`;
PR.toast('Emailed to Boom');
diff --git a/server.js b/server.js
index 87ca2ff2..d8d7510d 100644
--- a/server.js
+++ b/server.js
@@ -511,9 +511,21 @@ app.post('/api/content/email', adminOnly, async (req, res) => {
const b = req.body || {};
if (!GEORGE_HDR) return res.status(503).json({ ok: false, error: 'email not configured (GEORGE_BASIC_AUTH missing)' });
const title = clean(b.title, 200) || 'RENTV post';
+ // Collect ALL images (the credited building photo + any extras) and ALL videos — one rich send.
+ const images = (Array.isArray(b.images) ? b.images : String(b.images || '').split(/[\n,]+/)).map(s => clean(s, 500)).filter(u => /^https?:|^\/img\//i.test(u));
+ if (b.image && !images.includes(clean(b.image, 500))) images.unshift(clean(b.image, 500));
+ const videos = (Array.isArray(b.videos) ? b.videos : String(b.videos || '').split(/[\n,]+/)).map(s => clean(s, 500)).filter(u => /^https?:|^\/(vid|social-videos)\//i.test(u));
+ const absolutize = (u) => /^https?:/i.test(u) ? u : ('https://rentv.agentabrams.com' + u);
+ const imgHtml = images.length
+ ? `<p>${images.map(u => `<img src="${absolutize(u)}" style="max-width:100%;margin-bottom:6px">`).join('<br>')}<br><small>Photo: ${clean(b.photo_credit, 160) || '—'}</small></p>`
+ : '';
+ const vidHtml = videos.length
+ ? `<hr><h3>Video${videos.length > 1 ? 's' : ''}</h3>` + videos.map(u => `<p>▶ <a href="${absolutize(u)}">${absolutize(u)}</a></p>`).join('')
+ : '';
const html = `<h1>${clean(b.title, 200)}</h1>${b.dek ? `<p><em>${clean(b.dek, 300)}</em></p>` : ''}`
- + (b.image ? `<p><img src="${clean(b.image, 500)}" style="max-width:100%"><br><small>Photo: ${clean(b.photo_credit, 160) || '—'}</small></p>` : '')
+ + imgHtml
+ `<div>${String(b.body || '').split(/\n\n+/).map(p => `<p>${clean(p, 4000)}</p>`).join('')}</div>`
+ + vidHtml
+ (b.linkedin ? `<hr><h3>LinkedIn</h3><p>${clean(b.linkedin, 3000)}</p>` : '')
+ (b.x ? `<h3>X</h3><p>${clean(b.x, 600)}</p>` : '');
try {
← c0aff668 auto-save: 2026-08-05T11:11:27 (3 files) — data/markets.json
·
back to Rentv
·
chore: v0.19.0 (session close — Content Studio + photo track 3cdd23d7 →