Fix preview avatar: use persistent DOM element instead of innerHTML manipulation.

Move the preview author header (avatar + name + timestamp) inside the
preview div as a persistent child element that JS shows/hides, rather
than serializing it via innerHTML which was failing to render the image.
This commit is contained in:
russell@unturf.com 2026-02-08 04:17:46 -05:00
parent a42ed0dc7a
commit ac5970044e
2 changed files with 58 additions and 51 deletions

View file

@ -2,6 +2,23 @@
// previewTimer must live outside the functions.
var previewTimer = null;
function updatePreviewAuthor(previewDiv) {
// Show the persistent .preview-author header when preview has content.
// For anonymous users, sync the displayed name from the input field.
var author = previewDiv.querySelector('.preview-author');
if (!author) return;
author.style.display = '';
var form = previewDiv.closest('form');
if (form) {
var nameInput = form.querySelector('[name="anonymous_name"]');
if (nameInput) {
var name = nameInput.value.trim() || 'Anonymous';
var strong = author.querySelector('.preview-anon-name');
if (strong) strong.textContent = name;
}
}
}
function previewAjax(textarea, div, show_raw, mathjax) {
// set div to raw textarea while waiting for remote Markdown rendering.
if (show_raw) {
@ -9,12 +26,14 @@ function previewAjax(textarea, div, show_raw, mathjax) {
var el = document.getElementById(textarea);
var busted_textarea = el.value.replace(/&/g, '&amp;').replace(/</g, '&lt;');
var rawHtml = '<span class="preview-raw-markdown">' + busted_textarea + '</span>';
// Wrap raw preview as a live node for reply forms.
var form = el.closest('form');
if (form && isReplyForm(form)) {
rawHtml = wrapPreviewAsNode(rawHtml, form);
var previewDiv = document.getElementById(div);
var contentDiv = previewDiv.querySelector('.preview-content');
if (contentDiv) {
contentDiv.innerHTML = rawHtml;
updatePreviewAuthor(previewDiv);
} else {
previewDiv.innerHTML = rawHtml;
}
document.getElementById(div).innerHTML = rawHtml;
}
if (previewTimer) {
clearTimeout(previewTimer);
@ -38,13 +57,14 @@ function sendPreview(textarea, div, mathjax) {
})
.then(function(response) { return response.text(); })
.then(function(html) {
// Wrap rendered preview as a live node for reply forms.
var textareaEl = document.getElementById(textarea);
var form = textareaEl ? textareaEl.closest('form') : null;
if (form && isReplyForm(form)) {
html = wrapPreviewAsNode(html, form);
var previewDiv = document.getElementById(div);
var contentDiv = previewDiv.querySelector('.preview-content');
if (contentDiv) {
contentDiv.innerHTML = html;
updatePreviewAuthor(previewDiv);
} else {
previewDiv.innerHTML = html;
}
document.getElementById(div).innerHTML = html;
if (mathjax && typeof MathJax !== 'undefined') {
setTimeout(function() {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, div]);
@ -53,29 +73,6 @@ function sendPreview(textarea, div, mathjax) {
});
}
function isReplyForm(form) {
var action = form.getAttribute('action') || '';
return action.indexOf('/reply') !== -1;
}
function wrapPreviewAsNode(html, form) {
// Clone the hidden preview header (avatar + author + "just now")
// and wrap content in node-data div so it looks like a live comment.
var header = form.querySelector('.preview-header');
if (!header || !header.innerHTML.trim()) return html;
var headerHtml = header.innerHTML;
// For anonymous users, update the displayed name from the input field.
var nameInput = form.querySelector('[name="anonymous_name"]');
if (nameInput) {
var name = nameInput.value.trim() || 'Anonymous';
headerHtml = headerHtml.replace('>Anonymous<', '>' + escapeHtml(name) + '<');
}
return headerHtml + '<div class="node-data">' + html + '</div>';
}
// CSS-based toggle for smoother animations.
function toggle(target, button, off_text, on_text) {
if (typeof on_text === 'undefined') on_text = 'hide';
@ -291,7 +288,16 @@ function initAjaxCommentForms() {
if (textarea) textarea.value = '';
// Clear the preview.
var preview = form.querySelector('.preview');
if (preview) preview.innerHTML = '';
if (preview) {
var contentDiv = preview.querySelector('.preview-content');
if (contentDiv) {
contentDiv.innerHTML = '';
}
var authorDiv = preview.querySelector('.preview-author');
if (authorDiv) {
authorDiv.style.display = 'none';
}
}
submitBtn.disabled = false;
submitBtn.value = originalLabel;
})

View file

@ -26,24 +26,25 @@
{% set submit_button_value = request.namespace.submit_button_text or 'save message' %}
{% include 'submit.j2' %}
<div class="preview-header js-only" style="display:none">
{% if request.user and request.user.authenticated %}
<img src="{{ request.user.avatar_uri(size=request.avatar_size) }}" class="avatar nested-avatar" align="left" />
<span class="author-and-date">
<strong>{{ request.user.name }}</strong>
<span class="date">just now</span>
</span>
{% elif request.namespace.allow_anonymous %}
<span class="author-and-date">
<strong class="preview-anon-name">Anonymous</strong>
<span class="date">just now</span>
</span>
{% endif %}
</div>
<details class="preview-details js-only" open>
<summary class="action link preview-toggle"><span class="when-open">hide preview ▲</span><span class="when-closed">show preview ▼</span></summary>
<div id='preview-{{ node.id }}' name='preview' class='preview js-only'></div>
<div id='preview-{{ node.id }}' name='preview' class='preview js-only'>
<div class="preview-author" style="display:none">
{% if request.user and request.user.authenticated %}
<img src="{{ request.user.avatar_uri(size=request.avatar_size) }}" class="avatar nested-avatar" align="left" />
<span class="author-and-date">
<strong>{{ request.user.name }}</strong>
<span class="date">just now</span>
</span>
{% elif request.namespace.allow_anonymous %}
<span class="author-and-date">
<strong class="preview-anon-name">Anonymous</strong>
<span class="date">just now</span>
</span>
{% endif %}
</div>
<div class="preview-content node-data"></div>
</div>
</details>
{% include 'csrf.j2' %}