← back to Marketing Command Center
Quick Post live path: + Threads/Bluesky, 'Publish live' hands off to Composer (prefill + targeted account + dry-run+confirm) — no direct live post
311ff853d7828f21c110f2f1a7995ac523d71dc7 · 2026-08-12 16:33:07 -0700 · Steve
Files touched
M package.jsonM public/panels/assets.jsM public/panels/compose.jsM public/panels/composer.jsM public/panels/quickpost.jsM public/quickpost.js
Diff
commit 311ff853d7828f21c110f2f1a7995ac523d71dc7
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 16:33:07 2026 -0700
Quick Post live path: + Threads/Bluesky, 'Publish live' hands off to Composer (prefill + targeted account + dry-run+confirm) — no direct live post
---
package.json | 2 +-
public/panels/assets.js | 2 +-
public/panels/compose.js | 12 +++++++++++-
public/panels/composer.js | 35 +++++++++++++++++++++++++++++++++++
public/panels/quickpost.js | 3 ++-
public/quickpost.js | 32 +++++++++++++++++++++++++++++++-
6 files changed, 81 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 6331c2f..3b5054e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "marketing-command-center",
- "version": "1.10.0",
+ "version": "1.10.1",
"description": "DW Marketing Command Center — Constant Contact, marketing calendar, suggested copy, on-demand layouts",
"main": "server.js",
"scripts": {
diff --git a/public/panels/assets.js b/public/panels/assets.js
index ac5d6af..a0a72f3 100644
--- a/public/panels/assets.js
+++ b/public/panels/assets.js
@@ -84,7 +84,7 @@ window.MCC_PANELS['assets'] = {
if (window.MCCQuickPost) libGrid.querySelectorAll('.qp-mount').forEach(m => {
if (m.dataset.wired) return; m.dataset.wired = '1';
window.MCCQuickPost.attach(m, () => ({ caption: m.dataset.name, mediaUrl: m.dataset.url, source: 'asset-card' }),
- { mini: true, includeAll: false });
+ { mini: true, includeAll: false, live: true });
});
}
async function loadLibrary() {
diff --git a/public/panels/compose.js b/public/panels/compose.js
index 5036758..5bae0ad 100644
--- a/public/panels/compose.js
+++ b/public/panels/compose.js
@@ -75,6 +75,16 @@ window.MCC_PANELS['compose'] = {
if (q.length && badge) { badge.hidden = false; badge.textContent = q.length; }
} catch (_) {}
- initSuggested(); // Suggested is the default tab
+ // If Quick Post handed off a "Publish live" prefill, open the Manual tab
+ // directly (composer.js reads + applies the prefill on its init).
+ let hasPrefill = false;
+ try { hasPrefill = !!sessionStorage.getItem('mccComposePrefill'); } catch (_) {}
+ if (hasPrefill) {
+ root.querySelectorAll('.cmp2-tab').forEach(b => b.classList.toggle('active', b.dataset.tab === 'manual'));
+ paneS.hidden = true; paneM.hidden = false;
+ initManual();
+ } else {
+ initSuggested(); // Suggested is the default tab
+ }
},
};
diff --git a/public/panels/composer.js b/public/panels/composer.js
index d57af9c..eca5788 100644
--- a/public/panels/composer.js
+++ b/public/panels/composer.js
@@ -240,5 +240,40 @@ window.MCC_PANELS['composer'] = {
() => ({ caption: cap.value.trim(), mediaUrl: state.videoUrl || state.mediaUrl, source: 'composer' }),
{ mini: true });
}
+
+ // Prefill from Quick Post's "Publish live" handoff — apply caption + media +
+ // preselect channels, then the human picks the exact Page/IG account, dry-runs,
+ // and confirms here. This IS the gated live path.
+ try {
+ const raw = sessionStorage.getItem('mccComposePrefill');
+ if (raw) {
+ sessionStorage.removeItem('mccComposePrefill');
+ const pf = JSON.parse(raw);
+ if (pf && Date.now() - (pf.ts || 0) < 120000) {
+ if (pf.caption) { cap.value = pf.caption; countIt(); }
+ const u = pf.mediaUrl || '';
+ if (u) {
+ const isVid = /\.(mp4|mov|webm|m4v)(\?|$)/i.test(u);
+ const ph = $('#cmp-preview').querySelector('.cmp-ph'); if (ph) ph.style.display = 'none';
+ if (isVid) {
+ state.videoUrl = u; state.mediaUrl = '';
+ const v = $('#cmp-video'); if (v) { v.src = u; v.style.display = 'block'; }
+ $('#cmp-img').style.display = 'none';
+ } else {
+ state.mediaUrl = u; state.videoUrl = '';
+ const img = $('#cmp-img'); img.src = u; img.style.display = 'block';
+ const v = $('#cmp-video'); if (v) { v.style.display = 'none'; v.removeAttribute('src'); }
+ }
+ }
+ (pf.channels || []).forEach(ch => {
+ const cb = root.querySelector('#cmp-targets input[data-ch="' + ch + '"]');
+ if (cb && !cb.disabled) { cb.checked = true; state.selected.add(ch); }
+ });
+ toggleFbPages();
+ const note = $('#cmp-bannernote');
+ if (note) note.textContent = '↗ Prefilled from Quick Post — pick your Page/IG account below, then Publish (leave Dry-run on for the first pass).';
+ }
+ }
+ } catch (_) {}
},
};
diff --git a/public/panels/quickpost.js b/public/panels/quickpost.js
index cc1d299..329f9cf 100644
--- a/public/panels/quickpost.js
+++ b/public/panels/quickpost.js
@@ -22,7 +22,8 @@ window.MCC_PANELS['quickpost'] = {
// wire the shared button row — payload is read live at click time
if (window.MCCQuickPost) {
window.MCCQuickPost.attach($('#qp-buttons'),
- () => ({ caption: cap.value.trim(), mediaUrl: media.value.trim(), source: 'quickpost-panel' }));
+ () => ({ caption: cap.value.trim(), mediaUrl: media.value.trim(), source: 'quickpost-panel' }),
+ { live: true });
} else {
$('#qp-buttons').innerHTML = '<div class="muted" style="font-size:12px">Quick-post component not loaded.</div>';
}
diff --git a/public/quickpost.js b/public/quickpost.js
index fe8463e..96bfa0e 100644
--- a/public/quickpost.js
+++ b/public/quickpost.js
@@ -10,7 +10,13 @@
{ id: 'tiktok', label: 'TikTok', icon: '🎵' },
{ id: 'facebook', label: 'Facebook', icon: '📘' },
{ id: 'linkedin', label: 'LinkedIn', icon: '💼' },
+ { id: 'threads', label: 'Threads', icon: '🧵' },
+ { id: 'bluesky', label: 'Bluesky', icon: '🦋' },
];
+ // Live posting hands off to the Composer (targeted account picker + dry-run +
+ // confirm) — Quick Post never fires a live post itself. This is the set of
+ // platforms enabled for the live handoff (Steve, per-platform go).
+ const LIVE_ENABLED = ['instagram', 'tiktok', 'facebook', 'linkedin', 'threads', 'bluesky'];
// one-time CSS
if (!document.getElementById('qp-css')) {
@@ -26,6 +32,8 @@
.qp-btn .qp-i{font-size:14px}
.qp-btn.mini{padding:5px 8px;font-size:10.5px;border-radius:7px}
.qp-all{border-style:dashed;color:var(--accent,#8a5a44)}
+ .qp-btn.qp-live{border-color:var(--accent,#8a5a44);color:var(--accent,#8a5a44);font-weight:700}
+ .qp-btn.qp-live:hover{background:var(--accent,#8a5a44);color:#fff}
.qp-hint{font-size:11px;color:var(--mut,#8a8275)}
#qp-toast{position:fixed;right:18px;bottom:18px;z-index:9999;display:flex;flex-direction:column;gap:8px}
#qp-toast .t{background:var(--ink,#14110f);color:#fff;border-radius:10px;padding:11px 15px;font:600 12.5px/1.3 Inter,sans-serif;
@@ -62,12 +70,34 @@
`<span class="qp-i">${p.icon}</span>${opts.mini ? '' : p.label}</button>`).join('');
if (opts.includeAll !== false && !opts.mini)
html += `<button type="button" class="qp-btn qp-all" data-qp="__all" title="Stage a draft to all platforms">+ All</button>`;
+ // Live handoff → opens the Composer prefilled (targeted account + confirm live there)
+ if (opts.live) {
+ const live = opts.mini
+ ? `<button type="button" class="qp-btn mini qp-live" data-qp="__live" title="Publish live in Composer (pick account + confirm there)">↗ Live</button>`
+ : `<button type="button" class="qp-btn qp-live" data-qp="__live" title="Publish live in Composer — pick the exact account + confirm there">↗ Publish live in Composer…</button>`;
+ html += live;
+ }
mount.insertAdjacentHTML('beforeend', `<div class="qp-row">${html}</div>`);
mount.querySelectorAll('[data-qp]').forEach(btn => btn.addEventListener('click', async () => {
const which = btn.getAttribute('data-qp');
- const targets = which === '__all' ? PLATFORMS.map(p => p.id) : [which];
const payload = (typeof getPayload === 'function' ? getPayload() : getPayload) || {};
+ // LIVE handoff — hand media+caption to the Composer, preselect the enabled
+ // platforms, switch there. The human picks the exact Page/IG account, does a
+ // dry-run, and confirms in the Composer. Quick Post never posts live itself.
+ if (which === '__live') {
+ if (!payload.mediaUrl && !payload.caption) { toast('Nothing to hand off — add a caption or pick media first.'); return; }
+ try {
+ sessionStorage.setItem('mccComposePrefill', JSON.stringify({
+ caption: payload.caption || '', mediaUrl: payload.mediaUrl || '',
+ channels: LIVE_ENABLED, source: payload.source || 'quickpost', ts: Date.now(),
+ }));
+ } catch (_) {}
+ toast('↗ Opening Composer — pick the account, then <span class="g">dry-run & confirm</span> to post live.');
+ location.hash = 'compose';
+ return;
+ }
+ const targets = which === '__all' ? PLATFORMS.map(p => p.id) : [which];
if (!payload.mediaUrl && !payload.caption) { toast('Nothing to post — add a caption or pick media first.'); return; }
btn.classList.add('busy'); const orig = btn.innerHTML;
let ok = 0;
← a03f7e3 deploy: Easy Post buttons live on marketing.dw (v1.10.0)
·
back to Marketing Command Center
·
auto-data-snapshot: 2026-08-12T16:33:05 (2 data files) — dat 941504a →