import {Menu} from "../../../plugin/Menu"; import {hasClosestByClassName, hasTopClosestByClassName} from "../../util/hasClosest"; import {upDownHint} from "../../../util/upDownHint"; import {fetchPost} from "../../../util/fetch"; import {escapeGreat, escapeHtml} from "../../../util/escape"; import {transaction} from "../../wysiwyg/transaction"; import {genCellValueByElement, updateCellsValue} from "./cell"; import {updateAttrViewCellAnimation} from "./action"; import {focusBlock} from "../../util/selection"; import {setPosition} from "../../../util/setPosition"; const genSearchList = (element: Element, keyword: string, avId: string, cb?: () => void) => { fetchPost("/api/av/searchAttributeView", {keyword}, (response) => { let html = ""; response.data.results.forEach((item: { avID: string avName: string blockID: string hPath: string }, index: number) => { html += `
${escapeHtml(item.avName || window.siyuan.languages.title)}
${escapeGreat(item.hPath)}
`; }); element.innerHTML = html; if (cb) { cb(); } }); }; const setDatabase = (avId: string, element: HTMLElement, item: HTMLElement) => { element.dataset.avId = item.dataset.avId; element.dataset.blockId = item.dataset.blockId; element.querySelector(".b3-menu__accelerator").textContent = item.querySelector(".b3-list-item__hinticon").classList.contains("fn__none") ? item.querySelector(".b3-list-item__text").textContent : window.siyuan.languages.thisDatabase; const menuElement = hasClosestByClassName(element, "b3-menu__items"); if (menuElement) { toggleUpdateRelationBtn(menuElement, avId, true); } }; export const openSearchAV = (avId: string, target: HTMLElement, cb?: (element: HTMLElement) => void) => { window.siyuan.menus.menu.remove(); const menu = new Menu(); menu.addItem({ iconHTML: "", type: "empty", label: `
`, bind(element) { const listElement = element.querySelector(".b3-list"); const inputElement = element.querySelector("input"); inputElement.addEventListener("keydown", (event: KeyboardEvent) => { if (event.isComposing) { return; } const currentElement = upDownHint(listElement, event); if (currentElement) { event.stopPropagation(); } if (event.key === "Enter") { event.preventDefault(); event.stopPropagation(); const listItemElement = listElement.querySelector(".b3-list-item--focus") as HTMLElement; if (cb) { cb(listItemElement); } else { setDatabase(avId, target, listItemElement); } window.siyuan.menus.menu.remove(); } }); inputElement.addEventListener("input", (event: InputEvent) => { event.stopPropagation(); if (event.isComposing) { return; } genSearchList(listElement, inputElement.value, avId); }); inputElement.addEventListener("compositionend", () => { genSearchList(listElement, inputElement.value, avId); }); element.lastElementChild.addEventListener("click", (event) => { const listItemElement = hasClosestByClassName(event.target as HTMLElement, "b3-list-item"); if (listItemElement) { event.stopPropagation(); if (cb) { cb(listItemElement); } else { setDatabase(avId, target, listItemElement); } window.siyuan.menus.menu.remove(); } }); genSearchList(listElement, "", avId, () => { const rect = target.getBoundingClientRect(); menu.open({ x: rect.left, y: rect.bottom, h: rect.height, }); element.querySelector("input").focus(); }); } }); menu.element.querySelector(".b3-menu__items").setAttribute("style", "overflow: initial"); const popoverElement = hasTopClosestByClassName(target, "block__popover", true); menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app"); }; export const updateRelation = (options: { protyle: IProtyle, avID: string, avElement: Element, colsData: IAVColumn[], blockElement: Element, }) => { const inputElement = options.avElement.querySelector('input[data-type="colName"]') as HTMLInputElement; const goSearchAVElement = options.avElement.querySelector('.b3-menu__item[data-type="goSearchAV"]') as HTMLElement; const newAVId = goSearchAVElement.getAttribute("data-av-id"); const colId = options.avElement.querySelector(".b3-menu__item").getAttribute("data-col-id"); let colData: IAVColumn; options.colsData.find(item => { if (item.id === colId) { if (!item.relation) { item.relation = {}; } colData = item; return true; } }); const colNewName = (options.avElement.querySelector('[data-type="name"]') as HTMLInputElement).value; transaction(options.protyle, [{ action: "updateAttrViewColRelation", avID: options.avID, keyID: colId, id: newAVId || colData.relation.avID, backRelationKeyID: colData.relation.avID === newAVId ? colData.relation.backKeyID : Lute.NewNodeID(), isTwoWay: (options.avElement.querySelector(".b3-switch") as HTMLInputElement).checked, name: inputElement.value, format: colNewName }], [{ action: "updateAttrViewColRelation", avID: options.avID, keyID: colId, id: colData.relation.avID, backRelationKeyID: colData.relation.backKeyID, isTwoWay: colData.relation.isTwoWay, name: inputElement.dataset.oldValue, format: colData.name }]); options.avElement.remove(); updateAttrViewCellAnimation(options.blockElement.querySelector(`.av__row--header .av__cell[data-col-id="${colId}"]`), undefined, {name: colNewName}); focusBlock(options.blockElement); }; export const toggleUpdateRelationBtn = (menuItemsElement: HTMLElement, avId: string, resetData = false) => { const searchElement = menuItemsElement.querySelector('.b3-menu__item[data-type="goSearchAV"]') as HTMLElement; const switchItemElement = searchElement.nextElementSibling; const switchElement = switchItemElement.querySelector(".b3-switch") as HTMLInputElement; const inputItemElement = switchItemElement.nextElementSibling; const btnElement = inputItemElement.nextElementSibling; const oldValue = JSON.parse(searchElement.dataset.oldValue) as IAVCellRelationValue; if (oldValue.avID) { const inputElement = inputItemElement.querySelector("input") as HTMLInputElement; if (resetData) { if (searchElement.dataset.avId !== oldValue.avID) { inputElement.value = ""; switchElement.checked = false; } else { inputElement.value = inputElement.dataset.oldValue; switchElement.checked = oldValue.isTwoWay; } } if (searchElement.dataset.avId === avId && oldValue.avID === avId && oldValue.isTwoWay) { switchItemElement.classList.add("fn__none"); inputItemElement.classList.add("fn__none"); } else { switchItemElement.classList.remove("fn__none"); if (switchElement.checked) { inputItemElement.classList.remove("fn__none"); } else { inputItemElement.classList.add("fn__none"); } } if ((searchElement.dataset.avId && oldValue.avID !== searchElement.dataset.avId) || oldValue.isTwoWay !== switchElement.checked || inputElement.dataset.oldValue !== inputElement.value) { btnElement.classList.remove("fn__none"); } else { btnElement.classList.add("fn__none"); } } else if (searchElement.dataset.avId) { switchItemElement.classList.remove("fn__none"); if (switchElement.checked) { inputItemElement.classList.remove("fn__none"); } else { inputItemElement.classList.add("fn__none"); } btnElement.classList.remove("fn__none"); } }; const genSelectItemHTML = (type: "selected" | "empty" | "unselect", id?: string, isDetached?: boolean, text?: string) => { if (type === "selected") { return ` ${text} `; } if (type === "empty") { return ``; } if (type == "unselect") { return ``; } }; const filterItem = (menuElement: Element, cellElement: HTMLElement, keyword: string) => { fetchPost("/api/av/getAttributeViewPrimaryKeyValues", { id: menuElement.firstElementChild.getAttribute("data-av-id"), keyword, }, response => { const cells = response.data.rows.values as IAVCellValue[] || []; let html = ""; let selectHTML = ""; const hasIds: string[] = []; cellElement.querySelectorAll("span").forEach((item) => { hasIds.push(item.dataset.id); selectHTML += ``; }); cells.forEach((item) => { if (!hasIds.includes(item.block.id)) { html += genSelectItemHTML("unselect", item.block.id, item.isDetached, item.block.content || window.siyuan.languages.untitled); } }); menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML || genSelectItemHTML("empty")} ${html || genSelectItemHTML("empty")}`; menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current"); }); }; export const bindRelationEvent = (options: { menuElement: HTMLElement, protyle: IProtyle, blockElement: Element, cellElements: HTMLElement[] }) => { fetchPost("/api/av/getAttributeViewPrimaryKeyValues", { id: options.menuElement.firstElementChild.getAttribute("data-av-id"), keyword: "", }, response => { const cells = response.data.rows.values as IAVCellValue[] || []; let html = ""; let selectHTML = ""; const hasIds: string[] = []; options.cellElements[0].querySelectorAll("span").forEach((item) => { hasIds.push(item.dataset.id); selectHTML += ``; }); cells.forEach((item) => { if (!hasIds.includes(item.block.id)) { html += genSelectItemHTML("unselect", item.block.id, item.isDetached, item.block.content || window.siyuan.languages.untitled); } }); options.menuElement.querySelector(".b3-menu__label").innerHTML = response.data.name; options.menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML || genSelectItemHTML("empty")} ${html || genSelectItemHTML("empty")}`; const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); setPosition(options.menuElement, cellRect.left, cellRect.bottom, cellRect.height); options.menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current"); const inputElement = options.menuElement.querySelector("input"); inputElement.focus(); const listElement = options.menuElement.querySelector(".b3-menu__items"); inputElement.addEventListener("keydown", (event) => { event.stopPropagation(); if (event.isComposing) { return; } upDownHint(listElement, event, "b3-menu__item--current"); const currentElement = options.menuElement.querySelector(".b3-menu__item--current") as HTMLElement; if (event.key === "Enter" && currentElement && currentElement.getAttribute("data-type") === "setRelationCell") { setRelationCell(options.protyle, options.blockElement as HTMLElement, currentElement, options.cellElements); event.preventDefault(); event.stopPropagation(); } else if (event.key === "Escape") { options.menuElement.parentElement.remove(); event.preventDefault(); event.stopPropagation(); } }); inputElement.addEventListener("input", (event: InputEvent) => { if (event.isComposing) { return; } filterItem(options.menuElement, options.cellElements[0], inputElement.value); event.stopPropagation(); }); inputElement.addEventListener("compositionend", (event) => { event.stopPropagation(); filterItem(options.menuElement, options.cellElements[0], inputElement.value); }); }); }; export const getRelationHTML = (data: IAV, cellElements?: HTMLElement[]) => { let colRelationData: IAVCellRelationValue; data.view.columns.find(item => { if (item.id === cellElements[0].dataset.colId) { colRelationData = item.relation; return true; } }); if (colRelationData && colRelationData.avID) { return `
 
`; } else { return ""; } }; export const setRelationCell = (protyle: IProtyle, nodeElement: HTMLElement, target: HTMLElement, cellElements: HTMLElement[]) => { const menuElement = hasClosestByClassName(target, "b3-menu__items"); if (!menuElement) { return; } const rowElement = hasClosestByClassName(cellElements[0], "av__row"); if (!rowElement) { return; } if (!nodeElement.contains(cellElements[0])) { cellElements[0] = nodeElement.querySelector(`.av__row[data-id="${rowElement.dataset.id}"] .av__cell[data-col-id="${cellElements[0].dataset.colId}"]`) as HTMLElement; } const newValue = genCellValueByElement("relation", cellElements[0]).relation; if (target.classList.contains("b3-menu__item")) { const targetId = target.getAttribute("data-id"); const separatorElement = menuElement.querySelector(".b3-menu__separator"); if (target.getAttribute("draggable")) { if (!separatorElement.nextElementSibling.getAttribute("data-id")) { separatorElement.nextElementSibling.remove(); } const removeIndex = newValue.blockIDs.indexOf(targetId); newValue.blockIDs.splice(removeIndex, 1); newValue.contents.splice(removeIndex, 1); separatorElement.after(target); target.outerHTML = genSelectItemHTML("unselect", targetId, !target.querySelector(".popover__block"), target.querySelector(".b3-menu__label").textContent); if (!separatorElement.previousElementSibling) { separatorElement.insertAdjacentHTML("beforebegin", genSelectItemHTML("empty")); } } else { if (!separatorElement.previousElementSibling.getAttribute("data-id")) { separatorElement.previousElementSibling.remove(); } newValue.blockIDs.push(targetId); newValue.contents.push({ type: "block", block: { id: targetId, content: target.firstElementChild.textContent }, isDetached: !target.firstElementChild.getAttribute("style") }); separatorElement.before(target); target.outerHTML = ``; if (!separatorElement.nextElementSibling) { separatorElement.insertAdjacentHTML("afterend", genSelectItemHTML("empty")); } } menuElement.querySelector(".b3-menu__item--current")?.classList.remove("b3-menu__item--current"); menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current"); } updateCellsValue(protyle, nodeElement, newValue, cellElements); };