source: Klonkt/src/views/partials/reply-editor.ejs@ 5190152

main
Last change on this file since 5190152 was 5190152, checked in by Robin <roboburr@…>, 7 weeks ago

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@…>

  • Property mode set to 100644
File size: 4.5 KB
RevLine 
[33e1dbd]1<%#
2 Rich reply editor (shared component) — progressive enhancement.
3
4 Renders a plain <textarea name="text"> form that works without JS; the shared
5 /assets/js/reply-editor.js upgrades it to a rich contenteditable with a small
6 toolbar, a language select and (on ≤700px) a full-screen compose overlay,
7 which is the right pattern on mobile. On submit the JS fills the hidden
8 `content` field with the editor HTML and `text` with a plain-text fallback;
9 the server prefers `content` and sanitizes it (fedi-reply / authorize_interaction).
10
11 Locals:
12 action POST target (required)
13 hiddenFields [{ name, value }] extra hidden inputs (optional)
14 placeholder textarea/editor placeholder (required)
15 submitLabel submit button label (required)
16 rows textarea rows for the no-JS fallback (default 3)
17 defaultLang preselected reply language (default: UI lang)
[5190152]18 initialHtml prefill for EDIT mode (our own sanitized HTML) (optional)
19 initialText plain-text prefill for the no-JS textarea (optional)
20 noAttach true hides the media button (edits do not touch attachments)
[33e1dbd]21%>
[5190152]22<% var _reInitHtml = (typeof initialHtml !== 'undefined' && initialHtml) || ''; %>
23<% var _reInitText = (typeof initialText !== 'undefined' && initialText) || ''; %>
24<% var _reNoAttach = (typeof noAttach !== 'undefined' && noAttach); %>
[33e1dbd]25<% var _reLang = (typeof defaultLang !== 'undefined' && defaultLang) || (typeof lang !== 'undefined' ? lang : 'en'); %>
[feced2c]26<form method="post" action="<%= action %>" class="re-form" data-re
27 data-upload="/posts/upload-reply-media" data-upload-err="<%= t('re.attach_err') %>">
[33e1dbd]28 <% (typeof hiddenFields !== 'undefined' ? hiddenFields : []).forEach(function (h) { %>
29 <input type="hidden" name="<%= h.name %>" value="<%= h.value %>">
30 <% }); %>
31 <input type="hidden" name="content" value="">
32 <%# Full-screen top bar (mobile compose). Hidden until JS + fullscreen. %>
33 <div class="re-head" hidden>
34 <button type="button" class="re-cancel" aria-label="<%= t('comments.cancel') %>">×</button>
35 <strong class="re-head-title"><%= t('re.title') %></strong>
36 <button type="submit" class="btn re-send-top"><%= submitLabel %></button>
37 </div>
38 <div class="re-toolbar" role="toolbar" aria-label="<%= t('re.title') %>" hidden>
39 <button type="button" data-cmd="bold" title="<%= t('re.bold') %>" aria-label="<%= t('re.bold') %>"><b>B</b></button>
40 <button type="button" data-cmd="italic" title="<%= t('re.italic') %>" aria-label="<%= t('re.italic') %>"><i>I</i></button>
41 <button type="button" data-cmd="link" title="<%= t('re.link') %>" aria-label="<%= t('re.link') %>">🔗</button>
42 <button type="button" data-cmd="list" title="<%= t('re.list') %>" aria-label="<%= t('re.list') %>">•≡</button>
43 <button type="button" data-cmd="quote" title="<%= t('re.quote') %>" aria-label="<%= t('re.quote') %>">❝</button>
[5190152]44 <% if (!_reNoAttach) { %>
45 <button type="button" data-cmd="attach" title="<%= t('re.attach') %>" aria-label="<%= t('re.attach') %>">📎</button>
46 <input type="file" class="re-file" accept="image/*,audio/*,video/*" multiple hidden>
47 <% } %>
[33e1dbd]48 </div>
[5190152]49 <% if (!_reNoAttach) { %>
50 <input type="hidden" name="attachments" value="">
51 <div class="re-attachments" hidden></div>
52 <% } %>
53 <textarea name="text" rows="<%= typeof rows !== 'undefined' ? rows : 3 %>" required placeholder="<%= placeholder %>"><%= _reInitText %></textarea>
[33e1dbd]54 <div class="re-editor" contenteditable="true" role="textbox" aria-multiline="true"
[5190152]55 data-ph="<%= placeholder %>" aria-label="<%= placeholder %>" hidden><%- _reInitHtml %></div>
[33e1dbd]56 <div class="re-foot">
57 <select name="language" class="re-lang" aria-label="<%= t('re.lang') %>" hidden>
58 <% [['en','English'],['nl','Nederlands'],['de','Deutsch'],['fr','Français'],['es','Español'],['it','Italiano'],['pt','Português'],['pl','Polski'],['ru','Русский'],['uk','Українська'],['sv','Svenska'],['da','Dansk'],['nb','Norsk'],['fi','Suomi'],['tr','Türkçe'],['ja','日本語'],['zh','中文'],['ar','العربية']].forEach(function (l) { %>
59 <option value="<%= l[0] %>" <%= l[0] === _reLang ? 'selected' : '' %>><%= l[1] %></option>
60 <% }); %>
61 </select>
62 <button type="submit" class="btn re-send"><%= submitLabel %></button>
63 </div>
64</form>
65<%# Assets once per render, even when this partial repeats per comment. %>
66<% if (!locals.__reAssets) { locals.__reAssets = 1; %>
67<link rel="stylesheet" href="/assets/css/reply-editor.css">
68<script src="/assets/js/reply-editor.js" defer></script>
69<% } %>
Note: See TracBrowser for help on using the repository browser.