Refactoring MarkDown behavior.
This commit is contained in:
parent
6357e5794f
commit
38bae34247
4 changed files with 108 additions and 122 deletions
30
inventory/static/js/components/markdown.js
Normal file
30
inventory/static/js/components/markdown.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
const MarkDown = {
|
||||
parseOptions: { gfm: true, breaks: false },
|
||||
sanitizeOptions: { ADD_ATTR: ['target', 'rel'] },
|
||||
|
||||
toHTML(md) {
|
||||
const raw = marked.parse(md || "", this.parseOptions);
|
||||
return DOMPurify.sanitize(raw, this.sanitizeOptions);
|
||||
},
|
||||
|
||||
enhance(root) {
|
||||
if (!root) return;
|
||||
for (const a of root.querySelectorAll('a[href]')) {
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
a.classList.add('link-success', 'link-underline', 'link-underline-opacity-0', 'fw-semibold');
|
||||
}
|
||||
for (const t of root.querySelectorAll('table')) {
|
||||
t.classList.add('table', 'table-sm', 'table-striped', 'table-bordered');
|
||||
}
|
||||
for (const q of root.querySelectorAll('blockquote')) {
|
||||
q.classList.add('blockquote', 'border-start', 'border-5', 'border-success', 'mt-3', 'ps-3');
|
||||
}
|
||||
},
|
||||
|
||||
renderInto(el, md) {
|
||||
if (!el) return;
|
||||
el.innerHTML = this.toHTML(md);
|
||||
this.enhance(el);
|
||||
}
|
||||
};
|
||||
7
inventory/static/js/utils/json.js
Normal file
7
inventory/static/js/utils/json.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
function readJSONScript(id, fallback = "") {
|
||||
const el = document.getElementById(id);
|
||||
if (!el) return fallback;
|
||||
const txt = el.textContent?.trim();
|
||||
if (!txt) return fallback;
|
||||
try { return JSON.parse(txt); } catch { return fallback; }
|
||||
}
|
||||
|
|
@ -28,43 +28,30 @@
|
|||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/components/markdown.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/utils/json.js') }}" defer></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
renderView(getMarkdown());
|
||||
MarkDown.renderInto(document.getElementById('editContainer'), getMarkdown());
|
||||
});
|
||||
|
||||
function getMarkdown() {
|
||||
const el = document.getElementById('noteContent');
|
||||
return el ? (JSON.parse(el.textContent) || "") : "";
|
||||
}
|
||||
// used by entry_buttons submit
|
||||
window.getMarkdown = function () {
|
||||
return readJSONScript('noteContent', "");
|
||||
};
|
||||
|
||||
function setMarkdown(md) {
|
||||
const el = document.getElementById('noteContent');
|
||||
if (el) el.textContent = JSON.stringify(md ?? "");
|
||||
}
|
||||
|
||||
function renderView(md) {
|
||||
const container = document.getElementById('editContainer');
|
||||
if (!container) return;
|
||||
const html = marked.parse(md || "", {gfm: true});
|
||||
container.innerHTML = DOMPurify.sanitize(html, { ADD_ATTR: ['target', 'rel'] });
|
||||
for (const a of container.querySelectorAll('a[href]')) {
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
a.classList.add('link-success', 'link-underline', 'link-underline-opacity-0', 'fw-semibold');
|
||||
}
|
||||
for (const t of container.querySelectorAll('table')) {
|
||||
t.classList.add('table', 'table-sm', 'table-striped', 'table-bordered');
|
||||
}
|
||||
for (const t of container.querySelectorAll('blockquote')) {
|
||||
t.classList.add('blockquote', 'border-start', 'border-5', 'border-success', 'mt-3', 'ps-3');
|
||||
}
|
||||
}
|
||||
|
||||
function changeMode() {
|
||||
const container = document.getElementById('editContainer');
|
||||
const toggle = document.getElementById('editSwitch');
|
||||
if (!toggle.checked) return renderView(getMarkdown());
|
||||
if (!toggle.checked) {
|
||||
MarkDown.renderInto(container, getMarkdown());
|
||||
return;
|
||||
}
|
||||
|
||||
const current = getMarkdown();
|
||||
container.innerHTML = `
|
||||
|
|
@ -85,13 +72,13 @@
|
|||
const ta = document.getElementById('editor');
|
||||
const value = ta ? ta.value : "";
|
||||
setMarkdown(value);
|
||||
renderView(value);
|
||||
MarkDown.renderInto(document.getElementById('editContainer'), value);
|
||||
document.getElementById('editSwitch').checked = false;
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
document.getElementById('editSwitch').checked = false;
|
||||
renderView(getMarkdown());
|
||||
MarkDown.renderInto(document.getElementById('editContainer'), getMarkdown());
|
||||
}
|
||||
|
||||
function togglePreview() {
|
||||
|
|
@ -99,13 +86,13 @@
|
|||
const preview = document.getElementById('preview');
|
||||
preview.classList.toggle('d-none');
|
||||
if (!preview.classList.contains('d-none')) {
|
||||
const html = marked.parse(ta ? ta.value : "");
|
||||
preview.innerHTML = DOMPurify.sanitize(html);
|
||||
MarkDown.renderInto(preview, ta ? ta.value : "");
|
||||
}
|
||||
}
|
||||
|
||||
function autoGrow(ta) {
|
||||
if (!ta) return;
|
||||
if (CSS?.supports?.('field-sizing: content')) return;
|
||||
ta.style.height = 'auto';
|
||||
ta.style.height = ta.scrollHeight + 'px';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
<ul class="list-group mt-3">
|
||||
{% for n in items %}
|
||||
<li class="list-group-item">
|
||||
<li class="list-group-item" id="note-{{ n.id }}">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="me-3 w-100 markdown-body" id="editContainer{{ n.id }}"></div>
|
||||
<script type="application/json" id="md-{{ n.id }}">{{ n.content | tojson }}</script>
|
||||
|
|
@ -63,52 +63,15 @@
|
|||
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
|
||||
<script src="{{ url_for('static', filename='js/components/markdown.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='js/utils/json.js') }}" defer></script>
|
||||
<script>
|
||||
// State (kept global for compatibility with your form serialization)
|
||||
window.newDrafts = window.newDrafts || [];
|
||||
window.deletedIds = window.deletedIds || [];
|
||||
|
||||
// ---------- DRY UTILITIES ----------
|
||||
function renderMarkdown(md) {
|
||||
// One place to parse + sanitize
|
||||
const raw = marked.parse(md || "");
|
||||
return DOMPurify.sanitize(raw, { ADD_ATTR: ['target', 'rel'] });
|
||||
}
|
||||
|
||||
function enhanceLinks(root) {
|
||||
if (!root) return;
|
||||
for (const a of root.querySelectorAll('a[href]')) {
|
||||
a.setAttribute('target', '_blank');
|
||||
a.setAttribute('rel', 'noopener noreferrer nofollow');
|
||||
a.classList.add('link-success', 'link-underline', 'link-underline-opacity-0', 'fw-semibold');
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceTables(root) {
|
||||
if (!root) return;
|
||||
for (const t of root.querySelectorAll('table')) {
|
||||
t.classList.add('table', 'table-sm', 'table-striped', 'table-bordered');
|
||||
}
|
||||
}
|
||||
|
||||
function enhanceBlockquotes(root) {
|
||||
if (!root) return;
|
||||
for (const t of root.querySelectorAll('blockquote')) {
|
||||
t.classList.add('blockquote', 'border-start', 'border-5', 'border-success', 'mt-3', 'ps-3');
|
||||
}
|
||||
}
|
||||
|
||||
function renderHTML(el, md) {
|
||||
if (!el) return;
|
||||
el.innerHTML = renderMarkdown(md);
|
||||
enhanceLinks(el);
|
||||
enhanceTables(el);
|
||||
enhanceBlockquotes(el);
|
||||
}
|
||||
|
||||
function getMarkdown(id) {
|
||||
const el = document.getElementById(`md-${id}`);
|
||||
return el ? JSON.parse(el.textContent || '""') : "";
|
||||
return readJSONScript(`md-${id}`, "");
|
||||
}
|
||||
|
||||
function setMarkdown(id, md) {
|
||||
|
|
@ -122,14 +85,14 @@
|
|||
|
||||
function autoGrow(ta) {
|
||||
if (!ta) return;
|
||||
if (CSS?.supports?.('field-sizing: content')) return;
|
||||
ta.style.height = 'auto';
|
||||
ta.style.height = (ta.scrollHeight + 5) + 'px';
|
||||
}
|
||||
|
||||
// ---------- RENDERERS ----------
|
||||
function renderExistingView(id) {
|
||||
const container = document.getElementById(`editContainer${id}`);
|
||||
renderHTML(container, getMarkdown(id));
|
||||
MarkDown.renderInto(document.getElementById(`editContainer${id}`), getMarkdown(id));
|
||||
}
|
||||
|
||||
function renderEditor(id) {
|
||||
|
|
@ -167,8 +130,7 @@
|
|||
|
||||
const left = document.createElement('div');
|
||||
left.className = 'w-100 markdown-body';
|
||||
left.innerHTML = renderMarkdown(md || '');
|
||||
enhanceLinks(left);
|
||||
MarkDown.renderInto(left, md || '');
|
||||
|
||||
const right = document.createElement('div');
|
||||
right.className = 'ms-3 d-flex flex-column align-items-end';
|
||||
|
|
@ -271,7 +233,7 @@
|
|||
if (!preview) return;
|
||||
preview.classList.toggle('d-none');
|
||||
if (!preview.classList.contains('d-none')) {
|
||||
preview.innerHTML = renderMarkdown(ta ? ta.value : "");
|
||||
MarkDown.renderInto(preview, ta ? ta.value : "");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue