WIP: Feature: Markdown preview for markdown-supported fields #1

Closed
Francesco Bellini wants to merge 2 commits from markdown-preview into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
3 changed files with 113 additions and 2 deletions
Showing only changes of commit 15118f16d7 - Show all commits

View File

@ -0,0 +1,78 @@
const activeTabCls = "is-active"
function initMarkdownPreview(fieldName, texts) {
const field = document.querySelector(`textarea.form-control[name="${fieldName}"]`);
const markdown = document.querySelector(`#markdown-preview-${fieldName}`);
const tokenInput = document.querySelector(`input[name=csrfmiddlewaretoken]`)
const writeTab = document.querySelector(`#tab-write-${fieldName}`);
const previewTab = document.querySelector(`#tab-preview-${fieldName}`);
const write = document.querySelector(`#write-${fieldName}`);
const preview = document.querySelector(`#preview-${fieldName}`);
let lastText; // To avoid calling multiple times unchanged text
function toggleActiveTab() {
writeTab.classList.toggle(activeTabCls);
previewTab.classList.toggle(activeTabCls);
}
function opacity_75(text) {
return `<span class="opacity-75">${text}</span>`;
}
function nothingToPreview() {
markdown.innerHTML = opacity_75(texts.nothing_to_preview);
}
function switchTabs(on, off) {
on.style.display = "block";
off.style.display = "none";
toggleActiveTab();
}
if (previewTab && field) {
previewTab.addEventListener("click", function (e) {
e.preventDefault();
if (!previewTab.classList.contains(activeTabCls)) {
switchTabs(preview, write)
if (field.value !== lastText) {
markdown.innerHTML = opacity_75(texts.loading);
lastText = field.value;
if (field.value.trim()) {
fetch("/markdown/", {
method: "POST",
headers: {
"X-CSRFToken": tokenInput.value,
},
body: new URLSearchParams({
text: field.value,
}),
})
.then((response) => response.json())
.then((data) => {
const preview_markdown = data.markdown;
if (preview_markdown.trim()) {
markdown.innerHTML = preview_markdown;
} else {
nothingToPreview();
}
})
.catch((err) => {
lastText = undefined;
markdown.innerHTML = opacity_75(texts.error);
});
} else {
nothingToPreview();
}
}
}
});
}
if (writeTab) {
writeTab.addEventListener("click", function (e) {
e.preventDefault();
if (!writeTab.classList.contains(activeTabCls)) {
switchTabs(write, preview)
}
});
}
}

View File

@ -1,7 +1,8 @@
{% load common %} {% load i18n common %}
{% spaceless %} {% spaceless %}
{% with type=field.field.widget.input_type classes=classes|default:"" placeholder=placeholder|default:"" %} {% with type=field.field.widget.input_type classes=classes|default:"" placeholder=placeholder|default:"" %}
{% with field=field|remove_cols_rows|add_classes:classes|set_placeholder:placeholder %} {% with field=field|remove_cols_rows|add_classes:classes|set_placeholder:placeholder %}
{% with is_markdown=field.name|is_markdown_field %}
{% autoescape off %} {% autoescape off %}
{% firstof label field.label as label %} {% firstof label field.label as label %}
{% firstof help_text field.help_text as help_text %} {% firstof help_text field.help_text as help_text %}
@ -28,6 +29,16 @@
</label> </label>
{% endif %} {% endif %}
{% if is_markdown and not field.is_hidden %}
<div class="hero-tabs mb-2">
<a id="tab-write-{{field.name}}" href="#write-{{field.name}}" class="is-active">{% trans 'Write' %}</a>
<a id="tab-preview-{{field.name}}" href="#preview-{{field.name}}">{% trans 'Preview' %}</a>
</div>
<section id="preview-{{field.name}}" style="display: none;border-bottom: thin solid rgba(255, 255, 255, 0.1);">
<div id="markdown-preview-{{field.name}}" class="style-rich-text px-3 pt-2 pb-3">{% trans 'Loading...' %}</div>
</section>
{% endif %}
{% if icon %} {% if icon %}
<div class="input-group"> <div class="input-group">
<div class="input-group-prepend"> <div class="input-group-prepend">
@ -36,7 +47,9 @@
{{ field }} {{ field }}
</div> </div>
{% else %} {% else %}
{{ field }} <section id="write-{{field.name}}">
{{ field }}
</section>
{% endif %} {% endif %}
{% endif %} {% endif %}
@ -47,6 +60,22 @@
{% if field.errors %} {% if field.errors %}
<div class="invalid-feedback">{{ field.errors }}</div> <div class="invalid-feedback">{{ field.errors }}</div>
{% endif %} {% endif %}
{% block scripts %}
{% if is_markdown and not field.is_hidden %}
<script>
document.addEventListener('DOMContentLoaded', function() {
initMarkdownPreview("{{field.name}}", {
loading: "{% trans 'Loading...' %}",
error: "{% trans 'Error getting preview...' %}",
nothing_to_preview: "{% trans 'Nothing to preview...' %}"
})
})
</script>
{% endif %}
{% endblock scripts %}
{% endwith %}
{% endwith %} {% endwith %}
{% endwith %} {% endwith %}
{% endspaceless %} {% endspaceless %}

View File

@ -230,3 +230,7 @@ def to_int(value):
return int(value) return int(value)
except (ValueError, TypeError): except (ValueError, TypeError):
return 0 return 0
@register.filter
def is_markdown_field(name: str) -> bool:
return name in ['description', 'release_notes', 'message']