hmn/src/templates/src/include/link_editor.html

198 lines
5.4 KiB
HTML

<fieldset>
<legend class="flex justify-between">
<span>Links</span>
<a href="#" class="normal" onclick="addLink(event)">+ Add Link</a>
</legend>
<div class="pa3 input-group">
<div id="links" class="flex flex-column g2 relative">
<div class="b primary_links">Primary Links</div>
<div class="b drop_slot secondary_links">Secondary Links</div>
</div>
<template id="link_row">
<div class="link_row drop_slot w-100 flex items-center" data-tmpl="root">
<span class="link_handle svgicon pr2 pr3-ns pointer grab" onmousedown="startLinkDrag(event)">{{ svg "draggable" }}</span>
<div class="flex-grow-1 flex flex-column flex-row-ns g2-ns">
<input data-tmpl="nameInput" class="link_name w5-ns" type="text" placeholder="Name" oninput="linkInput(event)" />
<input data-tmpl="urlInput" class="link_url flex-grow-1" type="url" placeholder="Link" oninput="linkInput(event)" />
</div>
<a class="delete_link svgicon link-normal pl2 pl3-ns f3" href="javascript:;" onclick="deleteLink(event)">{{ svg "delete" }}</a>
</div>
</template>
<template id="link_row_dummy">
<div class="link_row_dummy drop_slot flex flex-row" data-tmpl="root">
<input class="o-0">
</div>
</template>
</div>
<input id="links_json" type="hidden" name="links">
<script>
const linksContainer = document.querySelector("#links");
const parentForm = linksContainer.closest("form");
const secondaryLinksTitle = linksContainer.querySelector(".secondary_links");
const linksJSONInput = document.querySelector("#links_json");
const linkTemplate = makeTemplateCloner("link_row");
const dummyLinkTemplate = makeTemplateCloner("link_row_dummy");
parentForm.addEventListener("submit", function() {
updateLinksJSON();
});
{{ if . }}
const initialLinks = JSON.parse("{{ . }}");
{{ else }}
const initialLinks = [];
{{ end }}
for (const link of initialLinks) {
const l = linkTemplate();
l.nameInput.value = link.name;
l.urlInput.value = link.url;
if (link.primary) {
secondaryLinksTitle.insertAdjacentElement("beforebegin", l.root);
} else {
linksContainer.appendChild(l.root);
}
}
ensureLinksEmptyState();
function addLink(e) {
e.preventDefault();
linksContainer.appendChild(linkTemplate().root);
fireLinkEditEvent();
}
function deleteLink(e) {
e.preventDefault();
const l = e.target.closest(".link_row");
l.remove();
ensureLinksEmptyState();
fireLinkEditEvent();
}
function ensureLinksEmptyState() {
if (!linksContainer.querySelector(".link_row")) {
// Empty state is a single row
linksContainer.appendChild(linkTemplate().root);
}
}
function updateLinksJSON() {
const links = [];
let primary = true;
let els = linksContainer.children;
for (let i = 0; i < els.length; ++i) {
let el = els[i];
if (el.classList.contains("secondary_links")) {
primary = false;
continue;
}
if (el.classList.contains("link_row")) {
const name = el.querySelector(".link_name").value;
const url = el.querySelector(".link_url").value;
if (!url) {
continue;
}
links.push({
"name": name,
"url": url,
"primary": primary,
});
}
}
linksJSONInput.value = JSON.stringify(links);
}
let draggingLink = null;
let linkDragStartY = 0;
let linkDragStartMouseY = 0;
function startLinkDrag(e) {
e.preventDefault();
const l = e.target.closest(".link_row");
const top = l.offsetTop;
l.insertAdjacentElement("beforebegin", dummyLinkTemplate().root);
document.body.classList.add("grabbing");
l.style.position = "absolute";
l.style.top = `${top}px`;
l.classList.add("link_dragging");
l.classList.remove("drop_slot");
draggingLink = l;
linkDragStartY = top;
linkDragStartMouseY = e.pageY;
}
function doLinkDrag(e) {
if (!draggingLink) {
return;
}
const maxTop = linksContainer.offsetHeight - draggingLink.offsetHeight;
const delta = e.pageY - linkDragStartMouseY;
const top = Math.max(0, Math.min(maxTop, linkDragStartY + delta));
const middle = top + draggingLink.offsetHeight/2;
draggingLink.style.top = `${top}px`;
const slots = linksContainer.querySelectorAll(".drop_slot");
let closestSlot = null;
let slotDist = Number.MAX_VALUE;
for (let i = 0; i < slots.length; ++i) {
let slotMiddle = slots[i].offsetTop + slots[i].offsetHeight/2;
let dist = Math.abs(middle - slotMiddle);
if (dist < slotDist) {
closestSlot = slots[i];
slotDist = dist;
}
}
const dummy = linksContainer.querySelector(".link_row_dummy");
if (!closestSlot.classList.contains("link_row_dummy")) {
let replaceType = "afterend";
if (closestSlot.offsetTop < dummy.offsetTop) {
replaceType = "beforebegin";
}
closestSlot.insertAdjacentElement(replaceType, dummy);
}
}
function endLinkDrag(e) {
if (!draggingLink) {
return;
}
const dummy = linksContainer.querySelector(".link_row_dummy");
draggingLink.remove();
dummy.insertAdjacentElement("beforebegin", draggingLink);
dummy.remove();
draggingLink.style.position = null;
draggingLink.style.top = null;
draggingLink.classList.remove("link_dragging");
draggingLink.classList.add("drop_slot");
document.body.classList.remove("grabbing");
draggingLink = null;
fireLinkEditEvent();
}
window.addEventListener("mouseup", endLinkDrag);
window.addEventListener("mousemove", doLinkDrag);
function linkInput(e) {
fireLinkEditEvent();
}
function fireLinkEditEvent() {
window.dispatchEvent(new Event("linkedit"));
}
</script>
</fieldset>