156 lines
4.5 KiB
JavaScript
156 lines
4.5 KiB
JavaScript
|
|
(function () {
|
||
|
|
"use strict";
|
||
|
|
|
||
|
|
var cfg = window.AZA_DOC;
|
||
|
|
if (!cfg) return;
|
||
|
|
|
||
|
|
var editor = document.getElementById("doc-editor");
|
||
|
|
var titleEl = document.getElementById("doc-title");
|
||
|
|
var categoryEl = document.getElementById("doc-category");
|
||
|
|
var taskEl = document.getElementById("doc-task");
|
||
|
|
var statusEl = document.getElementById("save-status");
|
||
|
|
var saveBtn = document.getElementById("btn-save");
|
||
|
|
var saving = false;
|
||
|
|
var autoTimer = null;
|
||
|
|
var lastSaved = "";
|
||
|
|
|
||
|
|
function setStatus(state, text) {
|
||
|
|
statusEl.className = "save-status save-status-" + state;
|
||
|
|
statusEl.textContent = text;
|
||
|
|
}
|
||
|
|
|
||
|
|
function payloadSnapshot() {
|
||
|
|
return JSON.stringify({
|
||
|
|
title: titleEl.value,
|
||
|
|
category: categoryEl.value,
|
||
|
|
task: taskEl.value,
|
||
|
|
html: editor.innerHTML,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function markDirty() {
|
||
|
|
if (!saving) {
|
||
|
|
setStatus("unsaved", "Nicht gespeicherte Änderungen");
|
||
|
|
}
|
||
|
|
clearTimeout(autoTimer);
|
||
|
|
autoTimer = setTimeout(function () {
|
||
|
|
saveDocument(false);
|
||
|
|
}, 3000);
|
||
|
|
}
|
||
|
|
|
||
|
|
function saveDocument(manual) {
|
||
|
|
if (saving) return;
|
||
|
|
var snap = payloadSnapshot();
|
||
|
|
if (!manual && snap === lastSaved) {
|
||
|
|
setStatus("saved", "Gespeichert");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
saving = true;
|
||
|
|
setStatus("saving", "Speichert...");
|
||
|
|
|
||
|
|
var fd = new FormData();
|
||
|
|
fd.append("csrf_token", cfg.csrf);
|
||
|
|
fd.append("title", titleEl.value.trim() || "Unbenanntes Dokument");
|
||
|
|
fd.append("content_html", editor.innerHTML);
|
||
|
|
fd.append("category", categoryEl.value);
|
||
|
|
fd.append("task_id", taskEl.value);
|
||
|
|
|
||
|
|
fetch("/api/documents/" + cfg.id + "/save", {
|
||
|
|
method: "POST",
|
||
|
|
body: fd,
|
||
|
|
credentials: "same-origin",
|
||
|
|
})
|
||
|
|
.then(function (r) { return r.json(); })
|
||
|
|
.then(function (data) {
|
||
|
|
saving = false;
|
||
|
|
if (data.ok) {
|
||
|
|
lastSaved = snap;
|
||
|
|
setStatus("saved", "Gespeichert");
|
||
|
|
} else {
|
||
|
|
setStatus("error", "Fehler beim Speichern");
|
||
|
|
}
|
||
|
|
})
|
||
|
|
.catch(function () {
|
||
|
|
saving = false;
|
||
|
|
setStatus("error", "Fehler beim Speichern");
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function findListItem(node) {
|
||
|
|
var el = node && node.nodeType === 3 ? node.parentElement : node;
|
||
|
|
while (el && el !== editor) {
|
||
|
|
if (el.tagName === "LI") return el;
|
||
|
|
el = el.parentElement;
|
||
|
|
}
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isEmptyListItem(li) {
|
||
|
|
if (!li) return false;
|
||
|
|
var text = (li.textContent || "").replace(/\u00a0/g, " ").replace(/\u200B/g, "").trim();
|
||
|
|
return text === "";
|
||
|
|
}
|
||
|
|
|
||
|
|
editor.addEventListener("keydown", function (e) {
|
||
|
|
if (e.key !== "Enter" || e.shiftKey) return;
|
||
|
|
var li = findListItem(window.getSelection().anchorNode);
|
||
|
|
if (!li) return;
|
||
|
|
if (isEmptyListItem(li)) {
|
||
|
|
e.preventDefault();
|
||
|
|
document.execCommand("outdent");
|
||
|
|
if (findListItem(window.getSelection().anchorNode) && isEmptyListItem(findListItem(window.getSelection().anchorNode))) {
|
||
|
|
document.execCommand("outdent");
|
||
|
|
}
|
||
|
|
var p = document.createElement("p");
|
||
|
|
p.appendChild(document.createElement("br"));
|
||
|
|
var parent = li.parentElement;
|
||
|
|
if (parent && (parent.tagName === "UL" || parent.tagName === "OL") && parent.children.length === 1) {
|
||
|
|
parent.replaceWith(p);
|
||
|
|
} else {
|
||
|
|
li.replaceWith(p);
|
||
|
|
}
|
||
|
|
var sel = window.getSelection();
|
||
|
|
var range = document.createRange();
|
||
|
|
range.selectNodeContents(p);
|
||
|
|
range.collapse(true);
|
||
|
|
sel.removeAllRanges();
|
||
|
|
sel.addRange(range);
|
||
|
|
markDirty();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
[titleEl, categoryEl, taskEl].forEach(function (el) {
|
||
|
|
el.addEventListener("input", markDirty);
|
||
|
|
el.addEventListener("change", markDirty);
|
||
|
|
});
|
||
|
|
|
||
|
|
editor.addEventListener("input", markDirty);
|
||
|
|
|
||
|
|
saveBtn.addEventListener("click", function () {
|
||
|
|
clearTimeout(autoTimer);
|
||
|
|
saveDocument(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
document.querySelectorAll(".doc-tb-btn").forEach(function (btn) {
|
||
|
|
btn.addEventListener("click", function (e) {
|
||
|
|
e.preventDefault();
|
||
|
|
var cmd = btn.getAttribute("data-cmd");
|
||
|
|
var val = btn.getAttribute("data-value");
|
||
|
|
editor.focus();
|
||
|
|
if (cmd === "link") {
|
||
|
|
var url = window.prompt("Link-URL (https://...):", "https://");
|
||
|
|
if (url && !url.toLowerCase().startsWith("javascript:")) {
|
||
|
|
document.execCommand("createLink", false, url);
|
||
|
|
}
|
||
|
|
} else if (cmd === "formatBlock" && val) {
|
||
|
|
document.execCommand(cmd, false, val);
|
||
|
|
} else {
|
||
|
|
document.execCommand(cmd, false, null);
|
||
|
|
}
|
||
|
|
markDirty();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
lastSaved = payloadSnapshot();
|
||
|
|
})();
|