/**
* Carl London Partner Desk — dynamic command center for xxxiii.io/authors/carl-london
*/
(function (global) {
const PARTNER_ID = 'carl-london';
const AUTHOR_ID = 'carl-london';
const CARL_DESKS = ['growth', 'markets', 'legislative', 'fraud', 'general'];
const VOICE_PREFIX =
'You are assisting Carl London, GMIIE System Partner for editorial and growth across Troptions and GMIIE. ';
const API = {
profile: '/api/gmiie/partners/carl-london',
links: '/api/gmiie/partners/carl-london/links',
activity: '/api/gmiie/partners/carl-london/activity',
link: '/api/gmiie/partners/carl-london/link',
outline: '/api/gmiie/partners/carl-london/syndicate-outline',
digest: '/api/gmiie/live/digest',
status: '/api/gmiie/live/status',
ask: '/api/gmiie/ask',
};
const QUERY_ENDPOINTS = [
{ id: 'digest', method: 'GET', path: '/api/gmiie/live/digest', label: 'Live digest', params: ['jurisdiction'] },
{ id: 'partner', method: 'GET', path: '/api/gmiie/partners/carl-london', label: 'Your profile + stats' },
{ id: 'links', method: 'GET', path: '/api/gmiie/partners/carl-london/links', label: 'Your linked portfolio' },
{ id: 'activity', method: 'GET', path: '/api/gmiie/partners/carl-london/activity', label: 'Your activity feed' },
{ id: 'pulse', method: 'GET', path: '/api/pulse', label: 'Pulse JSON' },
{ id: 'submit', method: 'POST', path: '/api/gmiie/content/submit', label: 'Submit draft', body: { title: 'Carl London desk note', body: 'Draft body…', author_id: AUTHOR_ID, desk: 'growth' } },
];
let state = {
profile: null,
articles: [],
activity: [],
digest: null,
queryActive: QUERY_ENDPOINTS[0],
lastOutline: null,
};
function $(id) {
return document.getElementById(id);
}
function esc(s) {
return String(s || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
function formatTs(iso) {
if (!iso) return '—';
try {
return new Intl.DateTimeFormat('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
}).format(new Date(iso));
} catch {
return iso;
}
}
function statusPill(status) {
const map = {
linked: 'pcl-linked',
syndicated: 'pcl-syndicated',
pending: 'pcl-pending',
published: 'pcl-published',
seed: 'pcl-seed',
};
const cls = map[status] || 'pcl-linked';
const label = status || 'linked';
return '' + esc(label) + '';
}
function deskTag(desk) {
if (!desk) return '';
return '' + esc(desk) + '';
}
function skeleton(n, cls) {
let html = '';
for (let i = 0; i < n; i++) {
html += '
';
}
return html;
}
function toast(msg, type) {
let el = $('pcl-toast');
if (!el) {
el = document.createElement('div');
el.id = 'pcl-toast';
el.className = 'pcl-toast';
el.setAttribute('role', 'status');
el.setAttribute('aria-live', 'polite');
document.body.appendChild(el);
}
el.textContent = msg;
el.className = 'pcl-toast pcl-toast--' + (type || 'info') + ' pcl-toast--show';
clearTimeout(el._t);
el._t = setTimeout(function () {
el.classList.remove('pcl-toast--show');
}, 4200);
}
async function fetchJson(url, opts) {
const r = await fetch(url, { signal: AbortSignal.timeout(15000), ...(opts || {}) });
const data = await r.json().catch(function () {
return { error: r.statusText };
});
if (!r.ok) throw new Error(data.error || 'Request failed');
return data;
}
function renderHeroStats() {
const root = $('pcl-stat-chips');
if (!root) return;
const stats = state.profile?.stats || {};
root.innerHTML =
'' +
(stats.articles_linked ?? '—') +
' Articles linked' +
'' +
(stats.submissions ?? '—') +
' Submissions' +
'' +
(stats.last_sync ? formatTs(stats.last_sync) : '—') +
' Last sync';
}
function renderToolkit() {
const grid = $('pcl-toolkit-grid');
if (!grid) return;
const tools = state.profile?.toolkit || [];
if (!tools.length) {
grid.innerHTML = skeleton(6, 'pcl-skeleton-card');
return;
}
grid.innerHTML = tools
.map(function (t) {
const external = String(t.href).startsWith('http');
const st = t.status === 'partial' ? 'PARTIAL' : t.status === 'live' ? 'LIVE' : '';
return (
'' +
'' +
(st ? '' + st + '' : '') +
'' +
esc(t.label) +
'
' +
esc(t.label) +
'
' +
esc(t.desc) +
'
'
);
})
.join('');
}
function filterCarlWeekItems(digest) {
if (!digest) return [];
const items = digest.this_week || digest.items || [];
return items.filter(function (item) {
const desk = String(item.desk || '').toLowerCase();
const text = String(item.text || item.headline || '').toLowerCase();
if (CARL_DESKS.includes(desk)) return true;
return (
text.includes('troptions') ||
text.includes('commerce') ||
text.includes('ecosystem') ||
text.includes('growth') ||
text.includes('editorial') ||
text.includes('pulse')
);
});
}
function renderWeekAhead() {
const list = $('pcl-week-list');
const meta = $('pcl-week-meta');
if (!list) return;
if (!state.digest) {
list.innerHTML = skeleton(4, 'pcl-skeleton-line');
return;
}
const items = filterCarlWeekItems(state.digest);
if (meta) {
meta.textContent =
'Digest · ' +
formatTs(state.digest.generated_at) +
' · ' +
(items.length ? items.length + ' routing suggestions' : 'No Carl-scoped items — showing desk map');
}
if (!items.length) {
list.innerHTML =
'No week-ahead items tagged to your desks yet. Use Query Console or Voice Ask to route Troptions editorial.' +
CARL_DESKS.map(function (d) {
return '' + esc(d) + ' — monitor via Query Console';
}).join('');
return;
}
list.innerHTML = items
.map(function (item) {
return (
'' +
(item.desk ? deskTag(item.desk) : '') +
'' +
esc(item.text || item.headline || item.summary) +
'' +
(item.action
? ' '
: '') +
''
);
})
.join('');
list.querySelectorAll('[data-week-action]').forEach(function (btn) {
btn.addEventListener('click', function () {
voiceAsk(btn.getAttribute('data-week-action') || '');
});
});
}
function renderPortfolio() {
const list = $('pcl-portfolio');
if (!list) return;
if (state._portfolioLoading) {
list.innerHTML = skeleton(3, 'pcl-skeleton-card');
return;
}
if (!state.articles.length) {
list.innerHTML =
'Your portfolio is empty — link a Medium URL below or submit via
Publishing OS.
';
return;
}
list.innerHTML = state.articles
.map(function (a) {
const date = a.published_at || a.linked_at || '';
const href = a.url.startsWith('/') ? a.url : a.url;
const isApi = href.startsWith('/api/');
return (
'' +
'' +
'
' +
statusPill(a.status || (a.source === 'gmiie_submit' ? 'pending' : 'linked')) +
'
' +
'' +
esc(formatTs(date)) +
' · ' +
esc(a.source || 'linked') +
(a.desk ? ' · ' : '') +
(a.desk ? deskTag(a.desk) : '') +
'
' +
(a.notes ? '' + esc(a.notes) + '
' : '') +
'' +
(!isApi
? ''
: '') +
'' +
'
'
);
})
.join('');
list.querySelectorAll('[data-outline-url]').forEach(function (btn) {
btn.addEventListener('click', function () {
runSyndicateOutline(btn.getAttribute('data-outline-url'), btn.getAttribute('data-outline-title'));
});
});
list.querySelectorAll('[data-copy-url]').forEach(function (btn) {
btn.addEventListener('click', function () {
navigator.clipboard.writeText(btn.getAttribute('data-copy-url') || '').then(function () {
toast('URL copied', 'ok');
});
});
});
}
function renderActivity() {
const list = $('pcl-activity-feed');
if (!list) return;
if (state._activityLoading) {
list.innerHTML = skeleton(5, 'pcl-skeleton-line');
return;
}
if (!state.activity.length) {
list.innerHTML = 'No activity yet — link an article or generate a syndication outline.';
return;
}
list.innerHTML = state.activity
.slice(0, 20)
.map(function (ev) {
return (
'' +
'' +
esc(ev.type || 'event') +
'' +
'' +
esc(ev.label) +
'' +
''
);
})
.join('');
}
function initQueryStrip() {
const list = $('pcl-query-eps');
const jsonEl = $('pcl-query-json');
const curlEl = $('pcl-query-curl');
const statusEl = $('pcl-query-status');
if (!list) return;
QUERY_ENDPOINTS.forEach(function (ep, i) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'pcl-query-ep' + (i === 0 ? ' active' : '');
btn.innerHTML = '' + ep.method + ' ' + esc(ep.label);
btn.addEventListener('click', function () {
state.queryActive = ep;
list.querySelectorAll('.pcl-query-ep').forEach(function (b, j) {
b.classList.toggle('active', QUERY_ENDPOINTS[j] === ep);
});
refreshQueryCurl();
});
list.appendChild(btn);
});
function buildUrl() {
const ep = state.queryActive;
const params = new URLSearchParams();
if (ep.params && ep.params.includes('jurisdiction')) {
params.set('jurisdiction', 'US-FED');
params.set('tz', Intl.DateTimeFormat().resolvedOptions().timeZone);
}
const q = params.toString();
return location.origin + ep.path + (q ? '?' + q : '');
}
function refreshQueryCurl() {
const ep = state.queryActive;
const url = buildUrl();
let curl = 'curl -s "' + url + '"';
if (ep.method === 'POST') {
const body = JSON.stringify(ep.body || {});
curl =
'curl -s -X POST "' +
url +
'" -H "Content-Type: application/json" -d \'' +
body.replace(/'/g, "'\\''") +
"'";
}
if (curlEl) curlEl.textContent = curl;
return curl;
}
refreshQueryCurl();
$('pcl-query-fetch')?.addEventListener('click', async function () {
const ep = state.queryActive;
if (statusEl) statusEl.textContent = 'Fetching…';
try {
const opts = {};
if (ep.method === 'POST') {
opts.method = 'POST';
opts.headers = { 'Content-Type': 'application/json' };
opts.body = JSON.stringify(ep.body || {});
}
const r = await fetch(buildUrl(), { ...opts, signal: AbortSignal.timeout(15000) });
const data = await r.json();
if (jsonEl) jsonEl.textContent = JSON.stringify(data, null, 2);
if (statusEl) statusEl.textContent = r.ok ? 'OK · ' + new Date().toISOString() : 'Error ' + r.status;
} catch (e) {
if (jsonEl) jsonEl.textContent = JSON.stringify({ error: e.message }, null, 2);
if (statusEl) statusEl.textContent = 'Failed';
}
});
$('pcl-query-copy')?.addEventListener('click', function () {
navigator.clipboard.writeText(refreshQueryCurl()).then(function () {
toast('curl copied', 'ok');
});
});
$('pcl-query-explain')?.addEventListener('click', function () {
voiceAsk('Explain the latest Query Console result for Carl London partner desks: digital commerce, ecosystem, editorial growth.');
});
}
function initSyndicatePanel() {
const urlInput = $('pcl-syndicate-url');
const titleInput = $('pcl-syndicate-title');
const preview = $('pcl-syndicate-preview');
const out = $('pcl-syndicate-out');
$('pcl-syndicate-preview-btn')?.addEventListener('click', async function () {
const url = (urlInput?.value || '').trim();
if (!url) {
toast('Paste a Medium URL first', 'err');
return;
}
if (preview) preview.textContent = 'Fetching title…';
try {
const data = await fetchJson(API.outline, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url, title: (titleInput?.value || '').trim(), preview_only: true }),
});
if (titleInput && !titleInput.value && (data.outline?.title || data.title)) {
titleInput.value = data.outline?.title || data.title;
}
if (preview) preview.textContent = data.outline?.title || data.title || 'Title unavailable — enter manually';
} catch (e) {
if (preview) preview.textContent = 'Preview failed — enter title manually';
}
});
$('pcl-syndicate-outline-btn')?.addEventListener('click', function () {
runSyndicateOutline((urlInput?.value || '').trim(), (titleInput?.value || '').trim(), out);
});
$('pcl-syndicate-copy')?.addEventListener('click', function () {
if (!state.lastOutline) {
toast('Generate an outline first', 'err');
return;
}
navigator.clipboard.writeText(JSON.stringify(state.lastOutline, null, 2)).then(function () {
toast('Pulse outline JSON copied', 'ok');
});
});
}
async function runSyndicateOutline(url, title, outEl) {
if (!url) {
toast('URL required', 'err');
return;
}
const out = outEl || $('pcl-syndicate-out');
if (out) {
out.textContent = 'Generating Pulse outline…';
out.classList.add('pcl-syndicate-out--loading');
}
try {
const data = await fetchJson(API.outline, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url, title: title || undefined }),
});
state.lastOutline = data.outline;
if (out) {
out.textContent = JSON.stringify(data.outline, null, 2);
out.classList.remove('pcl-syndicate-out--loading');
}
toast('Outline ready for Pulse', 'ok');
loadActivity();
} catch (e) {
if (out) out.textContent = 'Error: ' + e.message;
toast(e.message, 'err');
}
}
function initLinkForm() {
const form = $('pcl-link-form');
if (!form) return;
form.addEventListener('submit', async function (e) {
e.preventDefault();
const status = $('pcl-link-status');
const title = ($('pcl-link-title')?.value || '').trim();
const url = ($('pcl-link-url')?.value || '').trim();
const notes = ($('pcl-link-notes')?.value || '').trim();
const partnerKey = ($('pcl-partner-key')?.value || '').trim();
if (status) status.textContent = 'Saving…';
try {
const headers = { 'Content-Type': 'application/json' };
if (partnerKey) headers['X-GMIIE-Partner-Key'] = partnerKey;
const data = await fetchJson(API.link, {
method: 'POST',
headers: headers,
body: JSON.stringify({ title: title || undefined, url: url, notes: notes, source: 'medium' }),
});
if (status) status.textContent = 'Saved — ' + (data.link?.title || title);
form.reset();
toast('Article linked to your portfolio', 'ok');
await Promise.all([loadPortfolio(), loadProfile(), loadActivity()]);
} catch (err) {
if (status) status.textContent = err.message;
toast(err.message, 'err');
}
});
$('pcl-link-refresh')?.addEventListener('click', function () {
loadPortfolio();
loadActivity();
});
}
function initVoicePrompts() {
document.querySelectorAll('[data-carl-prompt]').forEach(function (btn) {
btn.addEventListener('click', function () {
voiceAsk(btn.getAttribute('data-carl-prompt') || '');
});
});
$('pcl-voice-ask-btn')?.addEventListener('click', function () {
const q = ($('pcl-voice-question')?.value || '').trim();
if (q) voiceAsk(q);
});
}
function voiceAsk(question) {
const q = VOICE_PREFIX + question;
if (global.GMIieVoiceDesk && typeof global.GMIieVoiceDesk.ask === 'function') {
global.GMIieVoiceDesk.ask(q);
return;
}
const input = $('pcl-voice-question');
if (input) input.value = question;
toast('Open Voice Desk (bottom-right) or use Query Explain', 'info');
}
async function loadProfile() {
try {
state.profile = await fetchJson(API.profile);
renderHeroStats();
renderToolkit();
} catch (e) {
toast('Profile load failed: ' + e.message, 'err');
}
}
async function loadPortfolio() {
state._portfolioLoading = true;
renderPortfolio();
try {
const data = await fetchJson(API.links);
state.articles = data.links || data.articles || [];
state._portfolioLoading = false;
renderPortfolio();
} catch (e) {
state._portfolioLoading = false;
const list = $('pcl-portfolio');
if (list) list.innerHTML = 'Could not load portfolio — ' + esc(e.message) + '
';
}
}
async function loadActivity() {
state._activityLoading = true;
renderActivity();
try {
const data = await fetchJson(API.activity);
state.activity = data.activity || [];
state._activityLoading = false;
renderActivity();
} catch (e) {
state._activityLoading = false;
renderActivity();
}
}
async function loadWeekAhead() {
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
try {
state.digest = await fetchJson(API.digest + '?jurisdiction=US-FED&tz=' + encodeURIComponent(tz));
} catch {
try {
const r = await fetch('/data/gmiie-daily-digest.json', { signal: AbortSignal.timeout(8000) });
if (r.ok) state.digest = await r.json();
} catch {
state.digest = { this_week: [], generated_at: null };
}
}
renderWeekAhead();
}
function initCollapsibles() {
document.querySelectorAll('[data-pcl-collapse]').forEach(function (btn) {
btn.addEventListener('click', function () {
const target = $(btn.getAttribute('data-pcl-collapse'));
if (!target) return;
const open = target.classList.toggle('pcl-panel--open');
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
});
});
}
function init() {
if (document.body.getAttribute('data-gmiie-page') !== 'carl-london') return;
initCollapsibles();
initQueryStrip();
initSyndicatePanel();
initLinkForm();
initVoicePrompts();
loadProfile();
loadPortfolio();
loadActivity();
loadWeekAhead();
setInterval(loadWeekAhead, 30 * 60 * 1000);
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
global.GMIIEPartnerCarl = {
init,
loadPortfolio,
loadProfile,
loadActivity,
loadWeekAhead,
voiceAsk,
};
})(typeof window !== 'undefined' ? window : globalThis);