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  }