Early work for saving work_logs.
This commit is contained in:
parent
01c6bb3d09
commit
85b0e576c7
3 changed files with 112 additions and 87 deletions
|
|
@ -191,11 +191,14 @@ def init_entry_routes(app):
|
|||
"start_time",
|
||||
"end_time",
|
||||
"complete",
|
||||
"updates",
|
||||
]
|
||||
}
|
||||
else:
|
||||
raise TypeError("Invalid model.")
|
||||
|
||||
print(payload)
|
||||
|
||||
service = crudkit.crud.get_service(cls)
|
||||
service.update(id, data=payload, actor="update_entry")
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<button type="submit" class="btn btn-primary" id="submit">Save</button>
|
||||
<script>
|
||||
window.newDrafts = window.newDrafts || [];
|
||||
window.deletedIds = window.deletedIds || [];
|
||||
|
||||
function formToJson(form) {
|
||||
const fd = new FormData(form);
|
||||
const out = {};
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<li class="list-group-item">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="me-3 w-100" id="editContainer{{ n.id }}"></div>
|
||||
<script type="application/json" id="md-{{ n.id }}">{{ n.content | tojson }}</script>
|
||||
|
||||
<div class="d-flex flex-column align-items-end">
|
||||
<div id="editView{{ n.id }}">
|
||||
|
|
@ -13,8 +14,8 @@
|
|||
</small>
|
||||
</div>
|
||||
<div class="form-check form-switch">
|
||||
<input type="checkbox" class="form-check-input" id="editSwitch{{ n.id }}" switch onchange="changeMode({{ n.id }})">
|
||||
<label for="editSwitch" class="form-check-label">Edit</label>
|
||||
<input class="form-check-input" type="checkbox" id="editSwitch{{ n.id }}" onchange="changeMode({{ n.id }})" role="switch" aria-label="Edit mode for update {{ n.id }}">
|
||||
<label for="editSwitch{{ n.id }}" class="form-check-label">Edit</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -24,83 +25,101 @@
|
|||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<style>
|
||||
textarea.auto-md {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-height: 60vh;
|
||||
overflow: hidden;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
@supports (field-sizing: content) {
|
||||
text-area.auto-md {
|
||||
field-sizing: content;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<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>
|
||||
const contents = {};
|
||||
{% for n in items %}
|
||||
contents[{{ n.id }}] = {{ n.content | tojson }};
|
||||
{% endfor %}
|
||||
|
||||
// Initial render
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
for (const [id, md] of Object.entries(contents)) {
|
||||
renderView(+id, md);
|
||||
}
|
||||
const ids = [ {% for n in items %} {{ n.id }}{% if not loop.last %}, {% endif %}{% endfor %} ];
|
||||
for (const id of ids) renderView(id, getMarkdown(id));
|
||||
});
|
||||
|
||||
function getMarkdown(id) {
|
||||
const el = document.getElementById(`md-${id}`);
|
||||
return el ? JSON.parse(el.textContent) : "";
|
||||
}
|
||||
function setMarkdown(id, md) {
|
||||
const el = document.getElementById(`md-${id}`);
|
||||
if (el) el.textContent = JSON.stringify(md ?? "");
|
||||
}
|
||||
|
||||
function renderView(id, md) {
|
||||
const container = document.getElementById(`editContainer${id}`);
|
||||
const html = marked.parse(md ?? "");
|
||||
|
||||
container.innerHTML = DOMPurify.sanitize(html, {
|
||||
ADD_ATTR: ['target', 'rel'],
|
||||
});
|
||||
if (!container) return;
|
||||
const html = marked.parse(md || "");
|
||||
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.setAttribute('rel','noopener noreferrer nofollow');
|
||||
}
|
||||
}
|
||||
|
||||
function changeMode(id) {
|
||||
const container = document.getElementById(`editContainer${id}`);
|
||||
const toggle = document.getElementById(`editSwitch${id}`);
|
||||
if (!toggle.checked) return renderView(id, getMarkdown(id));
|
||||
|
||||
if (toggle.checked) {
|
||||
// Switch to editor mode
|
||||
const current = contents[id] ?? "";
|
||||
container.dataset.prev = container.innerHTML;
|
||||
|
||||
const current = getMarkdown(id);
|
||||
container.innerHTML = `
|
||||
<textarea class="form-control w-100" id="editor${id}" rows="6">${escapeForTextarea(current)}</textarea>
|
||||
<textarea class="form-control w-100 auto-md" id="editor${id}">${escapeForTextarea(current)}</textarea>
|
||||
<div class="mt-2 d-flex gap-2">
|
||||
<button class="btn btn-primary btn-sm" onclick="saveEdit(${id})">Save</button>
|
||||
<button class="btn btn-secondary btn-sm" onclick="cancelEdit(${id})">Cancel</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" onclick="togglePreview(${id})">Preview</button>
|
||||
</div>
|
||||
<div class="mt-2 border rounded p-2 d-none" id="preview${id}"></div>
|
||||
<div class="mt-2 border rounded p-2 d-none" id="preview${id}" aria-live="polite"></div>
|
||||
`;
|
||||
} else {
|
||||
// Switch to viewer mode
|
||||
renderView(id, contents[id]);
|
||||
}
|
||||
const ta = document.getElementById(`editor${id}`);
|
||||
autoGrow(ta);
|
||||
ta.addEventListener('input', () => autoGrow(ta));
|
||||
}
|
||||
|
||||
function saveEdit(id) {
|
||||
const textarea = document.getElementById(`editor${id}`);
|
||||
const value = textarea.value;
|
||||
contents[id] = value;
|
||||
renderView(id, value);
|
||||
|
||||
const ta = document.getElementById(`editor${id}`);
|
||||
const value = ta ? ta.value : "";
|
||||
setMarkdown(id, value); // persist into the JSON tag
|
||||
renderView(id, value); // show it
|
||||
document.getElementById(`editSwitch${id}`).checked = false;
|
||||
}
|
||||
|
||||
function cancelEdit(id) {
|
||||
document.getElementById(`editSwitch${id}`).checked = false;
|
||||
renderView(id, contents[id]);
|
||||
renderView(id, getMarkdown(id));
|
||||
}
|
||||
|
||||
function togglePreview(id) {
|
||||
const textarea = document.getElementById(`editor${id}`);
|
||||
const ta = document.getElementById(`editor${id}`);
|
||||
const preview = document.getElementById(`preview${id}`);
|
||||
preview.classList.toggle('d-none');
|
||||
if (!preview.classList.contains('d-none')) {
|
||||
const html = marked.parse(textarea.value ?? "");
|
||||
const html = marked.parse(ta ? ta.value : "");
|
||||
preview.innerHTML = DOMPurify.sanitize(html);
|
||||
}
|
||||
}
|
||||
|
||||
function autoGrow(ta) {
|
||||
if (!ta) return;
|
||||
ta.style.height = 'auto';
|
||||
ta.style.height = ta.scrollHeight + 'px';
|
||||
}
|
||||
|
||||
function escapeForTextarea(s) {
|
||||
// Keep control of what goes inside the textarea
|
||||
return (s ?? "").replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue