Ignore:
Timestamp:
07/19/2026 05:38:25 PM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
e9c9ae1
Parents:
feced2c
git-author:
Robin <roboburr@…> (07/19/2026 05:38:00 PM)
git-committer:
Robin <roboburr@…> (07/19/2026 05:38:25 PM)
Message:

Feature: rich editor on the edit forms too (klonkt-demo-c7f, last slice)

Editing a sent reply (Messages + the interact page's manage list) now opens the
same shared editor as new replies, instead of a bare textarea. Prutter stays
plain on purpose (Robin: not part of this).

  • reply-editor partial: initialHtml/initialText prefill for edit mode, and a noAttach flag that omits the media UI (an edit never touches attachments). JS grew canAttach guards so the component runs without the attach elements; pasted files are still swallowed there rather than becoming base64 blobs.
  • deliverOutboxUpdate(site, id, text, {html, language}): rich path with the same sanitize + enrichment + mention-first-paragraph logic as deliverReply; language updated via COALESCE (bogus codes keep the old one); attachments survive untouched. Plain path unchanged.
  • /fediverse/:id/edit passes content + language; listOutbox and the Messages sent-projection carry language so the select preselects correctly.
  • The interact page's edit-toggle focuses the rich editor when present.

2 new tests (93 green). Browser-verified on /messages: prefilled editor
(mention-stripped HTML + plain fallback + language preselected, no paperclip),
edit saved -> content replaced with markup, mention re-attached inline,
language nl->en, no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/ActivityPubService.js

    rfeced2c r5190152  
    513513      items.push({
    514514        type: 'sent', outboxId: m.id, to_handle: m.to_handle, in_reply_to: m.in_reply_to,
    515         content: m.content, editable: m.editable, created_at: m.created_at,
     515        content: m.content, editable: m.editable, language: m.language, created_at: m.created_at,
    516516      });
    517517    }
     
    20492049}
    20502050export function listOutbox(siteSlug) {
    2051   return db.prepare('SELECT id, content, to_handle, in_reply_to, created_at FROM ap_outbox WHERE site_slug = ? ORDER BY created_at DESC')
     2051  return db.prepare('SELECT id, content, to_handle, in_reply_to, language, created_at FROM ap_outbox WHERE site_slug = ? ORDER BY created_at DESC')
    20522052    .all(siteSlug).map((r) => { const c = stripLeadingMentions(r.content); return { ...r, content: c, editable: outboxEditableText(c) }; });
    20532053}
     
    20772077// Edit one of our outbound replies: rewrite the stored content (mention re-added + #tags
    20782078// re-linked) and send an Update(Note) so recipients refresh their cached copy.
    2079 export async function deliverOutboxUpdate(site, outboxId, newText) {
     2079export async function deliverOutboxUpdate(site, outboxId, newText, opts = {}) {
    20802080  const row = iStmts().getO.get(outboxId);
    20812081  if (!row || row.site_slug !== site.slug) return false;
    20822082  const text = String(newText || '').trim();
    2083   if (!text) return false;
     2083  // Rich edit: same sanitize + enrichment pipeline as deliverReply.
     2084  const richClean = opts.html ? HtmlSanitizerService.sanitize(String(opts.html)) : '';
     2085  const rich = richClean && HtmlSanitizerService.toPlainText(richClean).trim() ? richClean : '';
     2086  if (!text && !rich) return false;
    20842087  const base = (process.env.PUBLIC_BASE_URL || '').replace(/\/+$/, '');
    20852088  if (!base) return false;
     
    20912094  const mention = row.to_actor
    20922095    ? `<a href="${escHtml(toProfile)}" class="u-url mention" data-actor="${escHtml(row.to_actor)}">${escHtml(toHandle)}</a> ` : '';
    2093   const mres = await resolveMentionsInText(base, escHtml(text).replace(/\r?\n/g, '<br>'));
    2094   const content = `<p>${mention}${linkUrls(linkHashtags(base, mres.html))}</p>`;
    2095   db.prepare('UPDATE ap_outbox SET content = ? WHERE id = ?').run(content, outboxId);
     2096  let content;
     2097  let mres;
     2098  if (rich) {
     2099    mres = await resolveMentionsInText(base, rich);
     2100    const processed = linkUrls(linkHashtags(base, mres.html));
     2101    if (processed.startsWith('<p>')) content = processed.replace('<p>', `<p>${mention}`);
     2102    else if (/^<(blockquote|ul|ol|pre|h[1-6]|div|hr)\b/i.test(processed)) content = `<p>${mention}</p>${processed}`;
     2103    else content = `<p>${mention}${processed}</p>`;
     2104  } else {
     2105    mres = await resolveMentionsInText(base, escHtml(text).replace(/\r?\n/g, '<br>'));
     2106    content = `<p>${mention}${linkUrls(linkHashtags(base, mres.html))}</p>`;
     2107  }
     2108  // Language may be updated with the edit; attachments always survive untouched.
     2109  const newLang = /^[a-z]{2,3}(-[A-Za-z0-9-]+)?$/.test(String(opts.language || '')) ? opts.language : null;
     2110  db.prepare('UPDATE ap_outbox SET content = ?, language = COALESCE(?, language) WHERE id = ?').run(content, newLang, outboxId);
    20962111  const note = buildReplyNote(base, site, iStmts().getO.get(outboxId));
    20972112  note.updated = new Date().toISOString();
Note: See TracChangeset for help on using the changeset viewer.