Updating notes support done!
This commit is contained in:
parent
bcf14cf251
commit
53efc8551d
4 changed files with 129 additions and 16 deletions
|
|
@ -48,7 +48,9 @@ def init_entry_routes(app):
|
||||||
"device_type", "owner", "location", "condition", "image", "notes"]
|
"device_type", "owner", "location", "condition", "image", "notes"]
|
||||||
fields_spec = [
|
fields_spec = [
|
||||||
{"name": "label", "type": "display", "label": "", "row": "label",
|
{"name": "label", "type": "display", "label": "", "row": "label",
|
||||||
"attrs": {"class": "display-6 mb-3"}},
|
"attrs": {"class": "display-6 mb-3"}, "wrap": {"class": "col"}},
|
||||||
|
{"name": "submit", "label": "", "row": "label", "type": "template", "template": "submit_button.html",
|
||||||
|
"wrap": {"class": "col-auto text-end me-2"}, "attrs": {"data-model": model}},
|
||||||
{"name": "name", "row": "names", "label": "Name", "wrap": {"class": "col-3"},
|
{"name": "name", "row": "names", "label": "Name", "wrap": {"class": "col-3"},
|
||||||
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
"attrs": {"class": "form-control"}, "label_attrs": {"class": "form-label"}},
|
||||||
{"name": "serial", "row": "names", "label": "Serial #", "wrap": {"class": "col"},
|
{"name": "serial", "row": "names", "label": "Serial #", "wrap": {"class": "col"},
|
||||||
|
|
@ -72,12 +74,11 @@ def init_entry_routes(app):
|
||||||
{"name": "image", "label": "", "row": "image", "type": "template", "label_spec": "{filename}",
|
{"name": "image", "label": "", "row": "image", "type": "template", "label_spec": "{filename}",
|
||||||
"template": "image_display.html", "attrs": {"class": "img-fluid img-thumbnail h-auto"},
|
"template": "image_display.html", "attrs": {"class": "img-fluid img-thumbnail h-auto"},
|
||||||
"wrap": {"class": "h-100 w-100"}},
|
"wrap": {"class": "h-100 w-100"}},
|
||||||
{"name": "notes", "type": "textarea", "label": "Notes", "row": "notes", "wrap": {"class": "col"},
|
{"name": "notes", "type": "template", "label": "Notes", "row": "notes", "wrap": {"class": "col"},
|
||||||
"attrs": {"class": "form-control", "rows": 10, "style": "resize: none;"},
|
"template": "inventory_note.html"},
|
||||||
"label_attrs": {"class": "form-label"}},
|
|
||||||
]
|
]
|
||||||
layout = [
|
layout = [
|
||||||
{"name": "label", "order": 5},
|
{"name": "label", "order": 5, "attrs": {"class": "row align-items-center"}},
|
||||||
{"name": "kitchen_sink", "order": 6, "attrs": {"class": "row"}},
|
{"name": "kitchen_sink", "order": 6, "attrs": {"class": "row"}},
|
||||||
{"name": "everything", "order": 10, "attrs": {"class": "col"}, "parent": "kitchen_sink"},
|
{"name": "everything", "order": 10, "attrs": {"class": "col"}, "parent": "kitchen_sink"},
|
||||||
{"name": "names", "order": 20, "attrs": {"class": "row"}, "parent": "everything"},
|
{"name": "names", "order": 20, "attrs": {"class": "row"}, "parent": "everything"},
|
||||||
|
|
|
||||||
108
inventory/templates/inventory_note.html
Normal file
108
inventory/templates/inventory_note.html
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<label class="form-label">Notes</label>
|
||||||
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
|
<div class="me-3 w-100" id="editContainer"></div>
|
||||||
|
<script type="application/json" id="noteContent">{{ field['template_ctx']['values']['notes'] | tojson }}</script>
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input type="checkbox" class="form-check-input" id="editSwitch" onchange="changeMode()" role="switch"
|
||||||
|
aria-label="Edit mode switch for inventory notes.">
|
||||||
|
<label for="editSwitch" class="form-check-label">Edit</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
renderView(getMarkdown());
|
||||||
|
});
|
||||||
|
|
||||||
|
function getMarkdown() {
|
||||||
|
const el = document.getElementById('noteContent');
|
||||||
|
return el ? JSON.parse(el.textContent) : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
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 || "");
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeMode() {
|
||||||
|
const container = document.getElementById('editContainer');
|
||||||
|
const toggle = document.getElementById('editSwitch');
|
||||||
|
if (!toggle.checked) return renderView(getMarkdown());
|
||||||
|
|
||||||
|
const current = getMarkdown();
|
||||||
|
container.innerHTML = `
|
||||||
|
<textarea class="form-control w-100 auto-md" id="editor" name="notes">${escapeForTextarea(current)}</textarea>
|
||||||
|
<div class="mt-2 d-flex gap-2">
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" onclick="saveEdit()">Save</button>
|
||||||
|
<button type="button" class="btn btn-secondary btn-sm" onclick="cancelEdit()">Cancel</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="togglePreview()">Preview</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 border rounded p-2 d-none" id="preview" aria-live="polite"></div>
|
||||||
|
`;
|
||||||
|
const ta = document.getElementById('editor');
|
||||||
|
autoGrow(ta);
|
||||||
|
ta.addEventListener('input', () => autoGrow(ta));
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveEdit() {
|
||||||
|
const ta = document.getElementById('editor');
|
||||||
|
const value = ta ? ta.value : "";
|
||||||
|
setMarkdown(value);
|
||||||
|
renderView(value);
|
||||||
|
document.getElementById('editSwitch').checked = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelEdit() {
|
||||||
|
document.getElementById('editSwitch').checked = false;
|
||||||
|
renderView(getMarkdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
function togglePreview() {
|
||||||
|
const ta = document.getElementById('editor');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function autoGrow(ta) {
|
||||||
|
if (!ta) return;
|
||||||
|
ta.style.height = 'auto';
|
||||||
|
ta.style.height = ta.scrollHeight + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForTextarea(s) {
|
||||||
|
return (s ?? "").replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -39,6 +39,8 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(out);
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,13 +71,15 @@
|
||||||
const json = formToJson(e.target);
|
const json = formToJson(e.target);
|
||||||
json.id = {{ field['template_ctx']['values']['id'] }};
|
json.id = {{ field['template_ctx']['values']['id'] }};
|
||||||
|
|
||||||
// map friendly names to real FK columns if needed
|
const model_name = {{ field['attrs']['data-model'] | tojson }};
|
||||||
if (json.contact && !json.contact_id) json.contact_id = Number(json.contact) || null;
|
|
||||||
if (json.work_item && !json.work_item_id) json.work_item_id = Number(json.work_item) || null;
|
|
||||||
|
|
||||||
|
if(model_name === 'inventory') {
|
||||||
|
json.notes = getMarkdown().trim();
|
||||||
|
} else if (model_name === 'worklog') {
|
||||||
// child mutations
|
// child mutations
|
||||||
json.updates = collectEditedUpdates();
|
json.updates = collectEditedUpdates();
|
||||||
json.delete_update_ids = collectDeletedIds();
|
json.delete_update_ids = collectDeletedIds();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
|
|
|
||||||
|
|
@ -79,9 +79,9 @@
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<textarea class="form-control w-100 auto-md" id="editor${id}">${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">
|
<div class="mt-2 d-flex gap-2">
|
||||||
<button class="btn btn-primary btn-sm" onclick="saveEdit(${id})">Save</button>
|
<button type="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 type="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>
|
<button type="button" class="btn btn-outline-secondary btn-sm" onclick="togglePreview(${id})">Preview</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2 border rounded p-2 d-none" id="preview${id}" aria-live="polite"></div>
|
<div class="mt-2 border rounded p-2 d-none" id="preview${id}" aria-live="polite"></div>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue