2022-08-05 04:03:45 +00:00
|
|
|
const snippetEditTemplate = makeTemplateCloner("snippet-edit");
|
|
|
|
const snippetEditProjectTemplate = makeTemplateCloner("snippet-edit-project");
|
|
|
|
|
|
|
|
function readableByteSize(numBytes) {
|
|
|
|
const scales = [
|
|
|
|
" bytes",
|
|
|
|
"kb",
|
|
|
|
"mb",
|
|
|
|
"gb"
|
|
|
|
];
|
|
|
|
let scale = 0;
|
2024-05-21 02:37:28 +00:00
|
|
|
while (numBytes > 1024 && scale < scales.length - 1) {
|
2022-08-05 04:03:45 +00:00
|
|
|
numBytes /= 1024;
|
|
|
|
scale++;
|
|
|
|
}
|
|
|
|
return new Intl.NumberFormat([], { maximumFractionDigits: (scale > 0 ? 2 : 0) }).format(numBytes) + scales[scale];
|
|
|
|
}
|
|
|
|
|
2022-08-06 21:45:07 +00:00
|
|
|
function makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, date, text, attachmentElement, projectIds, stickyProjectId, snippetId, originalSnippetEl) {
|
2022-08-05 04:03:45 +00:00
|
|
|
let snippetEdit = snippetEditTemplate();
|
|
|
|
let projectSelector = null;
|
|
|
|
let originalAttachment = null;
|
|
|
|
let originalText = text;
|
|
|
|
let attachmentChanged = false;
|
|
|
|
let hasAttachment = false;
|
|
|
|
snippetEdit.redirect.value = location.href;
|
2024-06-23 14:12:20 +00:00
|
|
|
if (ownerAvatar) {
|
|
|
|
snippetEdit.avatarImg.src = ownerAvatar;
|
|
|
|
snippetEdit.avatarLink.href = ownerUrl;
|
|
|
|
snippetEdit.avatarImg.hidden = false;
|
|
|
|
} else {
|
|
|
|
snippetEdit.avatarImg.hidden = true;
|
|
|
|
}
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.username.textContent = ownerName;
|
|
|
|
snippetEdit.username.href = ownerUrl;
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.date.textContent = new Intl.DateTimeFormat([], { month: "2-digit", day: "2-digit", year: "numeric" }).format(date);
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.text.value = text;
|
|
|
|
if (attachmentElement) {
|
|
|
|
originalAttachment = attachmentElement.cloneNode(true);
|
|
|
|
clearAttachment(true);
|
|
|
|
}
|
|
|
|
if (snippetId !== undefined && snippetId !== null) {
|
|
|
|
snippetEdit.snippetId.value = snippetId;
|
|
|
|
} else {
|
|
|
|
snippetEdit.deleteButton.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < projectIds.length; ++i) {
|
|
|
|
let proj = null;
|
|
|
|
for (let j = 0; j < availableProjects.length; ++j) {
|
|
|
|
if (projectIds[i] == availableProjects[j].id) {
|
|
|
|
proj = availableProjects[j];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (proj) {
|
|
|
|
addProject(proj);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateProjectSelector();
|
|
|
|
|
2022-08-06 21:51:29 +00:00
|
|
|
if (originalSnippetEl) {
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.cancelLink.addEventListener("click", function () {
|
2022-08-06 21:51:29 +00:00
|
|
|
cancel();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
snippetEdit.cancelLink.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
function cancel() {
|
|
|
|
if (originalSnippetEl) {
|
|
|
|
snippetEdit.root.parentElement.insertBefore(originalSnippetEl, snippetEdit.root);
|
|
|
|
}
|
|
|
|
snippetEdit.root.remove();
|
|
|
|
}
|
|
|
|
|
2022-08-05 04:03:45 +00:00
|
|
|
function addProject(proj) {
|
|
|
|
let projEl = snippetEditProjectTemplate();
|
|
|
|
projEl.projectId.value = proj.id;
|
|
|
|
projEl.projectLogo.src = proj.logo;
|
|
|
|
projEl.projectName.textContent = proj.name;
|
2022-08-06 21:45:07 +00:00
|
|
|
if (proj.id == stickyProjectId) {
|
|
|
|
projEl.removeButton.remove();
|
|
|
|
} else {
|
2024-05-21 02:37:28 +00:00
|
|
|
projEl.removeButton.addEventListener("click", function (ev) {
|
2022-08-06 21:45:07 +00:00
|
|
|
projEl.root.remove();
|
|
|
|
updateProjectSelector();
|
|
|
|
});
|
|
|
|
}
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.projectList.appendChild(projEl.root);
|
|
|
|
}
|
|
|
|
|
|
|
|
function updateProjectSelector() {
|
|
|
|
if (projectSelector) {
|
|
|
|
projectSelector.remove();
|
|
|
|
}
|
|
|
|
|
|
|
|
let remainingProjects = [];
|
|
|
|
let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]");
|
|
|
|
let assignedIds = [];
|
|
|
|
for (let i = 0; i < projInputs.length; ++i) {
|
|
|
|
let id = parseInt(projInputs[i].value, 10);
|
|
|
|
if (!isNaN(id)) {
|
|
|
|
assignedIds.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (let i = 0; i < availableProjects.length; ++i) {
|
|
|
|
let found = false;
|
|
|
|
for (let j = 0; j < assignedIds.length; ++j) {
|
|
|
|
if (assignedIds[j] == availableProjects[i].id) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
remainingProjects.push(availableProjects[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (remainingProjects.length > 0) {
|
|
|
|
projectSelector = document.createElement("SELECT");
|
|
|
|
let option = document.createElement("OPTION");
|
|
|
|
option.textContent = "Add to project...";
|
|
|
|
option.selected = true;
|
|
|
|
projectSelector.appendChild(option);
|
|
|
|
for (let i = 0; i < remainingProjects.length; ++i) {
|
|
|
|
option = document.createElement("OPTION");
|
|
|
|
option.value = remainingProjects[i].id;
|
|
|
|
option.selected = false;
|
|
|
|
option.textContent = remainingProjects[i].name;
|
|
|
|
projectSelector.appendChild(option);
|
|
|
|
}
|
2024-05-21 02:37:28 +00:00
|
|
|
projectSelector.addEventListener("change", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
if (projectSelector.selectedOptions.length > 0) {
|
|
|
|
let selected = projectSelector.selectedOptions[0];
|
|
|
|
if (selected.value != "") {
|
|
|
|
let id = parseInt(selected.value, 10);
|
|
|
|
if (!isNaN(id)) {
|
|
|
|
for (let i = 0; i < availableProjects.length; ++i) {
|
|
|
|
if (availableProjects[i].id == id) {
|
|
|
|
addProject(availableProjects[i]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
updateProjectSelector();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
snippetEdit.projectList.appendChild(projectSelector);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
function setFile(file) {
|
|
|
|
let dt = new DataTransfer();
|
|
|
|
dt.items.add(file);
|
|
|
|
snippetEdit.file.files = dt.files;
|
|
|
|
|
|
|
|
attachmentChanged = true;
|
|
|
|
snippetEdit.removeAttachment.value = "false";
|
|
|
|
hasAttachment = true;
|
|
|
|
|
|
|
|
let el = null;
|
|
|
|
if (file.type.startsWith("image/")) {
|
|
|
|
el = document.createElement("img");
|
|
|
|
el.src = URL.createObjectURL(file);
|
|
|
|
} else if (file.type.startsWith("video/")) {
|
|
|
|
el = document.createElement("video");
|
|
|
|
el.src = URL.createObjectURL(file);
|
|
|
|
el.controls = true;
|
|
|
|
} else if (file.type.startsWith("audio/")) {
|
|
|
|
el = document.createElement("audio");
|
|
|
|
el.src = URL.createObjectURL(file);
|
|
|
|
} else {
|
|
|
|
el = document.createElement("div");
|
|
|
|
el.classList.add("project-card", "br2", "pv1", "ph2");
|
|
|
|
let anchor = document.createElement("a");
|
|
|
|
anchor.href = URL.createObjectURL(file);
|
|
|
|
anchor.setAttribute("target", "_blank");
|
|
|
|
anchor.textContent = file.name + " (" + readableByteSize(file.size) + ")";
|
|
|
|
el.appendChild(anchor);
|
|
|
|
}
|
|
|
|
setPreview(el);
|
|
|
|
validate();
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearAttachment(restoreOriginal) {
|
|
|
|
snippetEdit.file.value = "";
|
|
|
|
let el = null;
|
|
|
|
attachmentChanged = false;
|
|
|
|
hasAttachment = false;
|
|
|
|
snippetEdit.removeAttachment.value = "false";
|
|
|
|
if (originalAttachment) {
|
|
|
|
if (restoreOriginal) {
|
|
|
|
hasAttachment = true;
|
|
|
|
el = originalAttachment;
|
|
|
|
} else {
|
|
|
|
attachmentChanged = true;
|
|
|
|
snippetEdit.removeAttachment.value = "true";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
setPreview(el);
|
|
|
|
validate();
|
|
|
|
}
|
|
|
|
|
|
|
|
function setPreview(el) {
|
|
|
|
if (el) {
|
|
|
|
snippetEdit.uploadBox.style.display = "none";
|
|
|
|
snippetEdit.previewBox.style.display = "block";
|
2022-08-07 03:32:29 +00:00
|
|
|
snippetEdit.uploadResetBox.style.display = "none";
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.previewContent = emptyElement(snippetEdit.previewContent);
|
|
|
|
snippetEdit.previewContent.appendChild(el);
|
|
|
|
snippetEdit.resetLink.style.display = (!originalAttachment || el == originalAttachment) ? "none" : "inline-block";
|
|
|
|
} else {
|
2022-08-07 03:32:29 +00:00
|
|
|
snippetEdit.uploadBox.style.display = "block";
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.previewBox.style.display = "none";
|
|
|
|
if (originalAttachment) {
|
2022-08-07 03:32:29 +00:00
|
|
|
snippetEdit.uploadResetBox.style.display = "block";
|
2022-08-05 04:03:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function validate() {
|
|
|
|
let sizeGood = true;
|
|
|
|
if (snippetEdit.file.files.length > 0 && snippetEdit.file.files[0].size > maxFilesize) {
|
|
|
|
// NOTE(asaf): Writing this out in bytes to make the limit exactly clear to the user.
|
|
|
|
let readableSize = new Intl.NumberFormat([], { useGrouping: "always" }).format(maxFilesize);
|
2022-08-07 03:32:29 +00:00
|
|
|
snippetEdit.errors.textContent = "File is too big! Max filesize is " + readableSize + " bytes.";
|
2022-08-05 04:03:45 +00:00
|
|
|
sizeGood = false;
|
|
|
|
} else {
|
|
|
|
snippetEdit.errors.textContent = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
let hasText = snippetEdit.text.value.trim().length > 0;
|
|
|
|
|
|
|
|
if ((hasText || hasAttachment) && sizeGood) {
|
|
|
|
snippetEdit.saveButton.disabled = false;
|
|
|
|
} else {
|
|
|
|
snippetEdit.saveButton.disabled = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.uploadLink.addEventListener("click", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.file.click();
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.removeLink.addEventListener("click", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
clearAttachment(false);
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.replaceLink.addEventListener("click", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.file.click();
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.resetLink.addEventListener("click", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
clearAttachment(true);
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.uploadResetLink.addEventListener("click", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
clearAttachment(true);
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.file.addEventListener("change", function () {
|
2022-08-05 04:03:45 +00:00
|
|
|
if (snippetEdit.file.files.length > 0) {
|
|
|
|
setFile(snippetEdit.file.files[0]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.root.addEventListener("dragover", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
let effect = "none";
|
|
|
|
for (let i = 0; i < ev.dataTransfer.items.length; ++i) {
|
|
|
|
if (ev.dataTransfer.items[i].kind.toLowerCase() == "file") {
|
|
|
|
effect = "copy";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ev.dataTransfer.dropEffect = effect;
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
|
|
|
|
let enterCounter = 0;
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.root.addEventListener("dragenter", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
enterCounter++;
|
2022-08-07 03:32:29 +00:00
|
|
|
let droppable = Array.from(ev.dataTransfer.items).some(
|
|
|
|
item => item.kind.toLowerCase() === "file"
|
|
|
|
);
|
|
|
|
if (droppable) {
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.root.classList.add("drop");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.root.addEventListener("dragleave", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
enterCounter--;
|
|
|
|
if (enterCounter == 0) {
|
|
|
|
snippetEdit.root.classList.remove("drop");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.root.addEventListener("drop", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
enterCounter = 0;
|
|
|
|
snippetEdit.root.classList.remove("drop");
|
|
|
|
|
|
|
|
if (ev.dataTransfer && ev.dataTransfer.files && ev.dataTransfer.files.length > 0) {
|
|
|
|
setFile(ev.dataTransfer.files[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
ev.preventDefault();
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.text.addEventListener("paste", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
const files = ev.clipboardData?.files ?? [];
|
|
|
|
if (files.length > 0) {
|
|
|
|
setFile(files[0]);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.text.addEventListener("input", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
validate();
|
|
|
|
});
|
|
|
|
|
2024-05-21 02:37:28 +00:00
|
|
|
snippetEdit.saveButton.addEventListener("click", function (ev) {
|
2022-08-05 04:03:45 +00:00
|
|
|
let projectsChanged = false;
|
|
|
|
let projInputs = snippetEdit.projectList.querySelectorAll("input[name=project_id]");
|
|
|
|
let assignedIds = [];
|
|
|
|
for (let i = 0; i < projInputs.length; ++i) {
|
|
|
|
let id = parseInt(projInputs[i].value, 10);
|
|
|
|
if (!isNaN(id)) {
|
|
|
|
assignedIds.push(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (projectIds.length != assignedIds.length) {
|
|
|
|
projectsChanged = true;
|
|
|
|
} else {
|
|
|
|
for (let i = 0; i < projectIds.length; ++i) {
|
|
|
|
let found = false;
|
|
|
|
for (let j = 0; j < assignedIds.length; ++j) {
|
|
|
|
if (projectIds[i] == assignedIds[j]) {
|
|
|
|
found = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
projectsChanged = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (originalSnippetEl && (!attachmentChanged && originalText == snippetEdit.text.value.trim() && !projectsChanged)) {
|
|
|
|
// NOTE(asaf): We're in edit mode and nothing changed, so no need to submit to the server.
|
|
|
|
ev.preventDefault();
|
2022-08-06 21:51:29 +00:00
|
|
|
cancel();
|
2022-08-05 04:03:45 +00:00
|
|
|
}
|
|
|
|
});
|
2024-05-21 02:37:28 +00:00
|
|
|
|
|
|
|
snippetEdit.deleteButton.addEventListener("click", function (ev) {
|
2022-08-07 03:32:29 +00:00
|
|
|
if (!window.confirm("Are you sure you want to delete this snippet?")) {
|
|
|
|
ev.preventDefault();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-08-05 04:03:45 +00:00
|
|
|
snippetEdit.file.value = "";
|
|
|
|
});
|
|
|
|
|
|
|
|
validate();
|
|
|
|
|
|
|
|
return snippetEdit;
|
|
|
|
}
|
|
|
|
|
2022-08-06 21:45:07 +00:00
|
|
|
function editTimelineSnippet(timelineItemEl, stickyProjectId) {
|
2022-08-05 04:03:45 +00:00
|
|
|
let ownerName = timelineItemEl.querySelector(".user")?.textContent;
|
|
|
|
let ownerUrl = timelineItemEl.querySelector(".user")?.href;
|
2024-05-21 02:37:28 +00:00
|
|
|
let ownerAvatar = timelineItemEl.querySelector(".avatar")?.src;
|
2022-08-05 04:03:45 +00:00
|
|
|
let creationDate = new Date(timelineItemEl.querySelector("time").dateTime);
|
|
|
|
let rawDesc = timelineItemEl.querySelector(".rawdesc").textContent;
|
2024-06-23 18:05:36 +00:00
|
|
|
let attachment = timelineItemEl.querySelector(".timeline-media")?.children?.[0];
|
2022-08-05 04:03:45 +00:00
|
|
|
let projectIds = [];
|
|
|
|
let projectEls = timelineItemEl.querySelectorAll(".projects > a");
|
|
|
|
for (let i = 0; i < projectEls.length; ++i) {
|
|
|
|
let projid = projectEls[i].getAttribute("data-projid");
|
|
|
|
if (projid) {
|
|
|
|
projectIds.push(projid);
|
|
|
|
}
|
|
|
|
}
|
2022-08-06 21:45:07 +00:00
|
|
|
let snippetEdit = makeSnippetEdit(ownerName, ownerAvatar, ownerUrl, creationDate, rawDesc, attachment, projectIds, stickyProjectId, timelineItemEl.getAttribute("data-id"), timelineItemEl);
|
2022-08-05 04:03:45 +00:00
|
|
|
timelineItemEl.parentElement.insertBefore(snippetEdit.root, timelineItemEl);
|
|
|
|
timelineItemEl.remove();
|
|
|
|
}
|