← back to Rentv 2026

public/admin/pr-intelligence/content-drafts.html

115 lines

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
<title>CRE PR Intelligence — Drafts</title>
<link rel="stylesheet" href="/admin/pr-intelligence/pr-shared.css">
<style>
  .dr-seg{display:inline-flex;border:1px solid var(--line,#d7dde3);border-radius:8px;overflow:hidden;margin:0 0 14px}
  .dr-seg button{font:inherit;font-size:12.5px;padding:6px 14px;border:0;background:#fff;color:var(--ink,#222);cursor:pointer}
  .dr-seg button.on{background:#0a66c2;color:#fff}
  .dr-src{font-size:11px;font-weight:700;border-radius:6px;padding:2px 7px}
  .dr-src.rentv{background:#e7f0ff;color:#0a66c2} .dr-src.wire{background:#fff2e0;color:#8a5a00}
  .dr-when{font-size:12px;color:var(--muted,#5b636b);white-space:nowrap}
  .dr-actions a{font-size:12px;font-weight:600;margin-right:12px;cursor:pointer;text-decoration:none}
  .dr-actions a.pub{color:#1c7a3e} .dr-actions a.del{color:#b23a1e}
  .dr-field{margin:0 0 10px} .dr-field label{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted,#5b636b);margin:0 0 4px}
  .dr-field input,.dr-field select,.dr-field textarea{width:100%;box-sizing:border-box;font:inherit;font-size:13px;padding:8px 10px;border:1px solid var(--line,#d7dde3);border-radius:8px;background:#fff}
  .dr-field textarea{min-height:220px;resize:vertical}
</style>
</head>
<body>
<script src="/admin/pr-intelligence/pr-shared.js"></script>
<script>
(async function () {
  const c = PR.frame('content-drafts', 'Drafts',
    'Blog drafts awaiting review. Outside-wire stories (copyright Option 1) land here as drafts — check the provenance, edit if needed, then Publish (the deliberate human gate) or Delete. Only you turn a wire story live.');

  const seg = document.createElement('div'); seg.className = 'dr-seg';
  seg.innerHTML = `<button data-s="draft" class="on">📝 Drafts</button><button data-s="published">✅ Published</button>`;
  c.appendChild(seg);
  const wrap = document.createElement('div'); c.appendChild(wrap);

  let status = 'draft';
  const isWire = (s) => s && s !== 'RENTV';
  const fmtWhen = (iso) => iso ? new Date(iso).toLocaleString(undefined, { year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: '2-digit' }) : '—';

  const tbl = PR.table([
    { key: 'title', label: 'Headline', render: (r) => `<b>${PR.esc(r.title || '(untitled)')}</b>${r.dek ? `<br><span style="color:var(--muted,#5b636b);font-size:12px">${PR.esc(String(r.dek).slice(0, 90))}</span>` : ''}` },
    { key: 'source', label: 'Source', render: (r) => `<span class="dr-src ${isWire(r.source) ? 'wire' : 'rentv'}" title="${isWire(r.source) ? 'Outside wire — publishing is your call' : 'RENTV original'}">${PR.esc(r.source || 'RENTV')}</span>` },
    { key: 'cat', label: 'Category', render: (r) => PR.badge(r.cat || 'RENTV', 'gray') },
    { key: 'created_at', label: 'Created', render: (r) => `<span class="dr-when" title="${PR.esc(r.created_at || '')}">🕓 ${fmtWhen(r.created_at)}</span>` },
    { key: '_a', label: '', render: (r) => `<div class="dr-actions">
        <a data-act="edit" data-id="${PR.esc(r.id)}">Edit</a>
        <a data-act="preview" data-id="${PR.esc(r.id)}" href="/post/${encodeURIComponent(r.id)}" target="_blank" rel="noopener noreferrer">Preview</a>
        ${status === 'draft'
          ? `<a class="pub" data-act="publish" data-id="${PR.esc(r.id)}">🚀 Publish</a>`
          : `<a data-act="unpublish" data-id="${PR.esc(r.id)}">Unpublish</a>`}
        <a class="del" data-act="delete" data-id="${PR.esc(r.id)}">Delete</a>
      </div>` },
  ]);
  wrap.appendChild(tbl.el);

  let rows = [];
  async function load() {
    try {
      const j = await PR.api('/../posts?status=' + status);
      rows = (j.items || []);
      tbl.render(rows);
    } catch (e) { PR.toast(e.message, true); }
  }

  seg.querySelectorAll('button').forEach((b) => b.addEventListener('click', () => {
    seg.querySelectorAll('button').forEach((x) => x.classList.remove('on'));
    b.classList.add('on'); status = b.dataset.s; load();
  }));

  // Delegated row actions
  wrap.addEventListener('click', (e) => {
    const a = e.target.closest('[data-act]'); if (!a) return;
    const act = a.getAttribute('data-act'), id = a.getAttribute('data-id');
    const row = rows.find((r) => String(r.id) === id);
    if (act === 'preview') return; // native link
    e.preventDefault();
    if (act === 'edit') return openEditor(row);
    if (act === 'publish') {
      if (!confirm(isWire(row.source) ? `Publish this OUTSIDE-WIRE story (${row.source}) live to /blog? This is your editorial call.` : 'Publish this entry live to /blog?')) return;
      return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'PUT', body: { status: 'published' } }); load(); }, 'Published');
    }
    if (act === 'unpublish') return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'PUT', body: { status: 'draft' } }); load(); }, 'Moved to draft');
    if (act === 'delete') {
      if (!confirm('Delete this draft permanently?')) return;
      return PR.guard(async () => { await PR.api('/../posts/' + id, { method: 'DELETE' }); load(); }, 'Deleted');
    }
  });

  function openEditor(r) {
    const d = PR.drawer();
    const body = d.open('Edit draft' + (isWire(r.source) ? ' — ' + PR.esc(r.source) : ''), `
      <div class="dr-field"><label>Headline</label><input id="e_title" value="${PR.esc(r.title || '')}"></div>
      <div class="dr-field"><label>Category</label>
        <select id="e_cat">${['Sales','Leases','Financing','Development','Multifamily','Office','Retail','Industrial','People','Markets','RENTV'].map((x) => `<option ${x === (r.cat || 'RENTV') ? 'selected' : ''}>${x}</option>`).join('')}</select></div>
      <div class="dr-field"><label>Standfirst (dek)</label><input id="e_dek" value="${PR.esc(r.dek || '')}"></div>
      <div class="dr-field"><label>Image URL</label><input id="e_image" value="${PR.esc(r.image || '')}"></div>
      <div class="dr-field"><label>Body</label><textarea id="e_body">${PR.esc(r.body || '')}</textarea></div>
      <div class="dr-field"><button class="btn" id="e_save">💾 Save</button>
        ${status === 'draft' ? '<button class="btn" id="e_savepub">💾 Save &amp; Publish</button>' : ''}
        <span class="dr-when" id="e_meta"></span></div>`);
    const val = (id) => body.querySelector('#' + id).value;
    const payload = () => ({ title: val('e_title').trim(), cat: val('e_cat'), dek: val('e_dek').trim(), image: val('e_image').trim(), body: val('e_body') });
    body.querySelector('#e_save').addEventListener('click', () => PR.guard(async () => {
      await PR.api('/../posts/' + r.id, { method: 'PUT', body: payload() }); d.close(); load();
    }, 'Saved'));
    const sp = body.querySelector('#e_savepub');
    if (sp) sp.addEventListener('click', () => {
      if (!confirm(isWire(r.source) ? `Publish this outside-wire story (${r.source}) live?` : 'Publish live?')) return;
      PR.guard(async () => { await PR.api('/../posts/' + r.id, { method: 'PUT', body: { ...payload(), status: 'published' } }); d.close(); load(); }, 'Published');
    });
  }

  load();
})();
</script>
</body>
</html>