198 lines
5.4 KiB
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>
|
|
|