github.com/kyleu/dbaudit@v0.0.2-0.20240321155047-ff2f2c940496/client/src/tags.ts (about) 1 // Content managed by Project Forge, see [projectforge.md] for details. 2 import {els, opt, req, setDisplay} from "./dom"; 3 import {svgRef} from "./util"; 4 5 function compareOrder(elem1: HTMLElement, elem2: HTMLElement) { 6 if (elem1.parentElement !== elem2.parentElement) { 7 return null; 8 } 9 if (elem1 === elem2) { 10 return 0; 11 } 12 if (elem1.compareDocumentPosition(elem2) & Node.DOCUMENT_POSITION_FOLLOWING) { 13 return -1; 14 } 15 return 1; 16 } 17 18 let draggedElement: HTMLElement; 19 20 function tagsUpdate(editorEl: HTMLElement) { 21 const values: string[] = []; 22 const elements = els(".item .value", editorEl); 23 for (const el of elements) { 24 values.push(el.innerText); 25 } 26 const inputEl = req<HTMLInputElement>("input.result", editorEl); 27 inputEl.value = values.join(", "); 28 } 29 30 function tagsRemove(itemEl: HTMLElement) { 31 const editorEl = itemEl.parentElement?.parentElement; 32 itemEl.remove(); 33 if (editorEl) { 34 tagsUpdate(editorEl); 35 } 36 } 37 38 function tagsEdit(itemEl: HTMLElement) { 39 const value = req(".value", itemEl); 40 const edit = req<HTMLInputElement>(".editor", itemEl); 41 edit.value = value.innerText; 42 const apply = () => { 43 if (edit.value === "") { 44 itemEl.remove(); 45 return; 46 } 47 value.innerText = edit.value; 48 setDisplay(value, true); 49 setDisplay(edit, false); 50 const editorEl = itemEl.parentElement?.parentElement; 51 if (editorEl) { 52 tagsUpdate(editorEl); 53 } 54 }; 55 edit.onblur = apply; 56 edit.onkeydown = (event) => { 57 if (event.code === "Enter") { 58 event.preventDefault(); 59 apply(); 60 return false; 61 } 62 }; 63 setDisplay(value, false); 64 setDisplay(edit, true); 65 edit.focus(); 66 } 67 68 function tagsRender(v: string, editorEl: HTMLElement): HTMLDivElement { 69 const item = document.createElement("div"); 70 item.className = "item"; 71 item.draggable = true; 72 item.ondragstart = (e) => { 73 e.dataTransfer?.setDragImage(document.createElement("div"), 0, 0); 74 item.classList.add("dragging"); 75 draggedElement = item; 76 }; 77 item.ondragover = () => { 78 const order = compareOrder(item, draggedElement); 79 if (!order) { 80 return; 81 } 82 const baseElement = order === -1 ? item : item.nextSibling; 83 draggedElement.parentElement?.insertBefore(draggedElement, baseElement); 84 tagsUpdate(editorEl); 85 }; 86 item.ondrop = (e) => { 87 e.preventDefault(); 88 }; 89 item.ondragend = (e) => { 90 item.classList.remove("dragging"); 91 e.preventDefault(); 92 }; 93 94 const value = document.createElement("div"); 95 value.innerText = v; 96 value.className = "value"; 97 value.onclick = () => { 98 tagsEdit(item); 99 }; 100 item.appendChild(value); 101 102 const editor = document.createElement("input"); 103 editor.className = "editor"; 104 item.appendChild(editor); 105 106 const close = document.createElement("div"); 107 close.innerHTML = svgRef("times", 13); 108 close.className = "close"; 109 close.onclick = () => { 110 tagsRemove(item); 111 }; 112 item.appendChild(close); 113 114 return item; 115 } 116 117 function tagsAdd(tagContainerEl: HTMLElement, editorEl: HTMLElement) { 118 const itemEl = tagsRender("", editorEl); 119 tagContainerEl.appendChild(itemEl); 120 tagsEdit(itemEl); 121 } 122 123 export function tagsWire(el: HTMLElement) { 124 const input = req<HTMLInputElement>("input.result", el); 125 const tagContainer = req<HTMLDivElement>(".tags", el); 126 const vals = input.value.split(",").map((x) => x.trim()).filter((k) => k !== ""); 127 128 setDisplay(input, false); 129 tagContainer.innerHTML = ""; 130 for (const v of vals) { 131 tagContainer.appendChild(tagsRender(v, el)); 132 } 133 134 opt(".add-item", el)?.remove(); 135 136 const add = document.createElement("div"); 137 add.className = "add-item"; 138 add.innerHTML = svgRef("plus", 22); 139 add.onclick = () => { 140 tagsAdd(tagContainer, el); 141 }; 142 el.insertBefore(add, req(".clear", el)); 143 } 144 145 export function tagsInit() { 146 for (const el of els(".tag-editor")) { 147 tagsWire(el); 148 } 149 return tagsWire; 150 }