Rich solo composer: WYSIWYG editor, sanitized HTML bodies, media uploads, CTA labels
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+53
-2
@@ -330,6 +330,46 @@
|
||||
+ '. Readers earn ' + (lastRates.soloReadCredits || 2) + ' credits for a real read, so your message gets opened.';
|
||||
}
|
||||
$('cBudget').addEventListener('input', soloHint);
|
||||
// rich solo editor: small toolbar over contenteditable (CSP allows no external editor);
|
||||
// the server whitelist-sanitizes whatever HTML arrives, this is just authoring comfort
|
||||
let soloMedia = null; // { url, type } from /api/my/upload
|
||||
document.querySelectorAll('.ed-bar [data-cmd]').forEach(btn =>
|
||||
btn.addEventListener('click', () => { $('cSoloEd').focus(); document.execCommand(btn.dataset.cmd, false, null); }));
|
||||
document.querySelectorAll('.ed-bar [data-block]').forEach(btn =>
|
||||
btn.addEventListener('click', () => { $('cSoloEd').focus(); document.execCommand('formatBlock', false, btn.dataset.block); }));
|
||||
$('edLinkBtn').addEventListener('click', () => {
|
||||
const url = prompt('Link URL (https://…)');
|
||||
if (!url) return;
|
||||
$('cSoloEd').focus();
|
||||
document.execCommand('createLink', false, url);
|
||||
});
|
||||
$('edAttachBtn').addEventListener('click', () => $('cSoloFile').click());
|
||||
$('cSoloFile').addEventListener('change', async () => {
|
||||
const f = $('cSoloFile').files[0];
|
||||
if (!f) return;
|
||||
$('edMediaInfo').textContent = 'Uploading ' + f.name + '…';
|
||||
try {
|
||||
const r = await (await fetch('/api/my/upload', { method: 'POST',
|
||||
headers: { 'Content-Type': f.type }, body: f })).json();
|
||||
if (r.error) { $('edMediaInfo').textContent = r.error; $('cSoloFile').value = ''; return; }
|
||||
soloMedia = r;
|
||||
$('edMediaInfo').textContent = f.name + ' attached';
|
||||
$('edMediaRemove').hidden = false;
|
||||
const pv = $('edMediaPrev');
|
||||
pv.hidden = false;
|
||||
pv.innerHTML = r.type === 'video'
|
||||
? '<video src="' + r.url + '" controls style="max-width:320px;border-radius:10px"></video>'
|
||||
: '<img src="' + r.url + '" alt="attachment preview" style="max-width:320px;border-radius:10px">';
|
||||
} catch (e) { $('edMediaInfo').textContent = 'Upload failed. Try again.'; }
|
||||
$('cSoloFile').value = '';
|
||||
});
|
||||
$('edMediaRemove').addEventListener('click', () => {
|
||||
soloMedia = null;
|
||||
$('edMediaInfo').textContent = '';
|
||||
$('edMediaRemove').hidden = true;
|
||||
$('edMediaPrev').hidden = true;
|
||||
$('edMediaPrev').innerHTML = '';
|
||||
});
|
||||
$('cType').addEventListener('change', () => {
|
||||
const t = $('cType').value;
|
||||
$('cImageRow').hidden = t === 'text' || t === 'solo';
|
||||
@@ -344,10 +384,14 @@
|
||||
await api('/api/my/campaigns', { type: $('cType').value, name: $('cName').value,
|
||||
targetUrl: $('cTarget').value, imageUrl: $('cImage').value,
|
||||
title: $('cTitle').value,
|
||||
body: $('cType').value === 'solo' ? $('cSoloBody').value : $('cBody').value,
|
||||
body: $('cType').value === 'solo' ? $('cSoloEd').innerHTML : $('cBody').value,
|
||||
mediaUrl: $('cType').value === 'solo' && soloMedia ? soloMedia.url : '',
|
||||
ctaLabel: $('cCtaLabel').value,
|
||||
budget: Number($('cBudget').value) });
|
||||
IAP.status('Campaign is live. It starts serving right away.', 'ok');
|
||||
$('cName').value = ''; $('cBudget').value = '';
|
||||
$('cSoloEd').innerHTML = ''; $('cCtaLabel').value = '';
|
||||
if ($('edMediaRemove') && !$('edMediaRemove').hidden) $('edMediaRemove').click();
|
||||
await loadCampaigns();
|
||||
}));
|
||||
// defers the busy() lookup to click time (busy is declared below)
|
||||
@@ -395,8 +439,15 @@
|
||||
$('inboxReadCard').hidden = false;
|
||||
$('ibSubject').textContent = r.subject || '(no subject)';
|
||||
$('ibMeta').textContent = 'from ' + (r.fromName || 'a member') + ' · ' + new Date(r.delivered).toLocaleString();
|
||||
$('ibBody').textContent = r.body || '';
|
||||
$('ibBody').innerHTML = r.body || ''; // whitelist-sanitized on the server at submit
|
||||
const mv = $('ibMedia');
|
||||
mv.hidden = !r.mediaUrl;
|
||||
mv.innerHTML = !r.mediaUrl ? ''
|
||||
: r.mediaType === 'video'
|
||||
? '<video src="' + r.mediaUrl + '" controls style="max-width:100%;border-radius:12px"></video>'
|
||||
: '<img src="' + r.mediaUrl + '" alt="attachment" style="max-width:100%;border-radius:12px">';
|
||||
$('ibVisit').href = r.url;
|
||||
$('ibVisit').textContent = r.ctaLabel || 'Learn more';
|
||||
const btn = $('ibClaimBtn');
|
||||
clearInterval(ibTimer);
|
||||
if (r.rewarded) {
|
||||
|
||||
Reference in New Issue
Block a user