| 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)
|
|---|
| 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)
|
|---|
| 21 | participants [{uri, url, handle}] conversation partners → the mentions bar
|
|---|
| 22 | (u02): implicit mentions live here as removable chips, not in
|
|---|
| 23 | the text. Absent = no bar, legacy addressing.
|
|---|
| 24 | %>
|
|---|
| 25 | <% var _reInitHtml = (typeof initialHtml !== 'undefined' && initialHtml) || ''; %>
|
|---|
| 26 | <% var _reInitText = (typeof initialText !== 'undefined' && initialText) || ''; %>
|
|---|
| 27 | <% var _reNoAttach = (typeof noAttach !== 'undefined' && noAttach); %>
|
|---|
| 28 | <% var _reParts = (typeof participants !== 'undefined' && participants && participants.length) ? participants : null; %>
|
|---|
| 29 | <% var _reLang = (typeof defaultLang !== 'undefined' && defaultLang) || (typeof lang !== 'undefined' ? lang : 'en'); %>
|
|---|
| 30 | <form method="post" action="<%= action %>" class="re-form" data-re
|
|---|
| 31 | data-upload="/posts/upload-reply-media" data-upload-err="<%= t('re.attach_err') %>">
|
|---|
| 32 | <% (typeof hiddenFields !== 'undefined' ? hiddenFields : []).forEach(function (h) { %>
|
|---|
| 33 | <input type="hidden" name="<%= h.name %>" value="<%= h.value %>">
|
|---|
| 34 | <% }); %>
|
|---|
| 35 | <input type="hidden" name="content" value="">
|
|---|
| 36 | <%# Mentions bar (u02): the conversation partners as removable chips. The
|
|---|
| 37 | hidden field always carries the CURRENT kept list (server-rendered full,
|
|---|
| 38 | so the no-JS path addresses everyone). %>
|
|---|
| 39 | <% if (_reParts) { %>
|
|---|
| 40 | <input type="hidden" name="mentions" value="<%= JSON.stringify(_reParts) %>">
|
|---|
| 41 | <div class="re-mentions">
|
|---|
| 42 | <span class="re-mentions-label"><%= t('re.to') %></span>
|
|---|
| 43 | <% _reParts.forEach(function (p) { %>
|
|---|
| 44 | <span class="re-mention" data-uri="<%= p.uri %>"><%= p.handle && p.handle[0] === '@' ? p.handle : '@' + p.handle %><button type="button" class="re-mention-del" title="<%= t('re.mention_del') %>" aria-label="<%= t('re.mention_del') %>">×</button></span>
|
|---|
| 45 | <% }); %>
|
|---|
| 46 | </div>
|
|---|
| 47 | <% } %>
|
|---|
| 48 | <%# Full-screen top bar (mobile compose). Hidden until JS + fullscreen. %>
|
|---|
| 49 | <div class="re-head" hidden>
|
|---|
| 50 | <button type="button" class="re-cancel" aria-label="<%= t('comments.cancel') %>">×</button>
|
|---|
| 51 | <strong class="re-head-title"><%= t('re.title') %></strong>
|
|---|
| 52 | <button type="submit" class="btn re-send-top"><%= submitLabel %></button>
|
|---|
| 53 | </div>
|
|---|
| 54 | <div class="re-toolbar" role="toolbar" aria-label="<%= t('re.title') %>" hidden>
|
|---|
| 55 | <button type="button" data-cmd="bold" title="<%= t('re.bold') %>" aria-label="<%= t('re.bold') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 4h8a4 4 0 0 1 0 8H6z"/><path d="M6 12h9a4 4 0 0 1 0 8H6z"/></svg></button>
|
|---|
| 56 | <button type="button" data-cmd="italic" title="<%= t('re.italic') %>" aria-label="<%= t('re.italic') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="4" x2="10" y2="4"/><line x1="14" y1="20" x2="5" y2="20"/><line x1="15" y1="4" x2="9" y2="20"/></svg></button>
|
|---|
| 57 | <button type="button" data-cmd="link" title="<%= t('re.link') %>" aria-label="<%= t('re.link') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg></button>
|
|---|
| 58 | <button type="button" data-cmd="list" title="<%= t('re.list') %>" aria-label="<%= t('re.list') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="9" y1="6" x2="20" y2="6"/><line x1="9" y1="12" x2="20" y2="12"/><line x1="9" y1="18" x2="20" y2="18"/><circle cx="4.5" cy="6" r="1.2"/><circle cx="4.5" cy="12" r="1.2"/><circle cx="4.5" cy="18" r="1.2"/></svg></button>
|
|---|
| 59 | <button type="button" data-cmd="quote" title="<%= t('re.quote') %>" aria-label="<%= t('re.quote') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2H4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5C6 17.5 4 19 3 19v2z"/><path d="M14 21c3 0 7-1 7-8V5c0-1.25-.75-2-2-2h-4c-1.25 0-2 .75-2 2v6c0 1.25.75 2 2 2h2.5c-.5 4.5-2.5 6-3.5 6v2z"/></svg></button>
|
|---|
| 60 | <% if (!_reNoAttach) { %>
|
|---|
| 61 | <span class="re-toolbar-sep" aria-hidden="true"></span>
|
|---|
| 62 | <button type="button" data-cmd="attach" title="<%= t('re.attach') %>" aria-label="<%= t('re.attach') %>"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"/></svg></button>
|
|---|
| 63 | <input type="file" class="re-file" accept="image/*,audio/*,video/*" multiple hidden>
|
|---|
| 64 | <% } %>
|
|---|
| 65 | </div>
|
|---|
| 66 | <% if (!_reNoAttach) { %>
|
|---|
| 67 | <input type="hidden" name="attachments" value="">
|
|---|
| 68 | <div class="re-attachments" hidden></div>
|
|---|
| 69 | <% } %>
|
|---|
| 70 | <textarea name="text" rows="<%= typeof rows !== 'undefined' ? rows : 3 %>" required placeholder="<%= placeholder %>"><%= _reInitText %></textarea>
|
|---|
| 71 | <div class="re-editor" contenteditable="true" role="textbox" aria-multiline="true"
|
|---|
| 72 | data-ph="<%= placeholder %>" aria-label="<%= placeholder %>" hidden><%- _reInitHtml %></div>
|
|---|
| 73 | <div class="re-foot">
|
|---|
| 74 | <select name="language" class="re-lang" aria-label="<%= t('re.lang') %>" hidden>
|
|---|
| 75 | <% [['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) { %>
|
|---|
| 76 | <option value="<%= l[0] %>" <%= l[0] === _reLang ? 'selected' : '' %>><%= l[1] %></option>
|
|---|
| 77 | <% }); %>
|
|---|
| 78 | </select>
|
|---|
| 79 | <button type="submit" class="btn re-send"><%= submitLabel %></button>
|
|---|
| 80 | </div>
|
|---|
| 81 | </form>
|
|---|
| 82 | <%# Assets once per render, even when this partial repeats per comment. %>
|
|---|
| 83 | <% if (!locals.__reAssets) { locals.__reAssets = 1; %>
|
|---|
| 84 | <link rel="stylesheet" href="/assets/css/reply-editor.css">
|
|---|
| 85 | <script src="/assets/js/reply-editor.js" defer></script>
|
|---|
| 86 | <% } %>
|
|---|