diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts index 75c946c99..c0aa3a893 100644 --- a/app/src/protyle/render/av/cell.ts +++ b/app/src/protyle/render/av/cell.ts @@ -70,6 +70,13 @@ const genCellValueByElement = (colType: TAVCol, cellElement: HTMLElement) => { checked: cellElement.querySelector("use").getAttribute("xlink:href") === "#iconCheck" ? true : false } }; + }else if (colType === "relation") { + cellValue = { + type: colType, + relation: { + blockIDs: Array.from(cellElement.querySelectorAll("span")).map((item: HTMLElement) => item.getAttribute("data-id")), + } + }; } if (colType === "block") { cellValue.isDetached = cellElement.dataset.detached === "true"; @@ -156,6 +163,13 @@ export const genCellValue = (colType: TAVCol, value: string | any) => { checked: value ? true : false } }; + }else if (colType === "relation") { + cellValue = { + type: colType, + relation: { + blockIDs: value as string[], + } + }; } } if (colType === "block") { @@ -258,6 +272,8 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type openMenuPanel({protyle, blockElement, type: "date", cellElements}); } else if (type === "checkbox") { updateCellValueByInput(protyle, type, cellElements); + } else if (type === "relation") { + openMenuPanel({protyle, blockElement, type: "relation", cellElements}); } if (!hasClosestByClassName(cellElements[0], "custom-attr")) { cellElements[0].classList.add("av__cell--select"); @@ -464,7 +480,7 @@ const updateCellValueByInput = (protyle: IProtyle, type: TAVCol, cellElements: H }); }; -export const updateCellsValue = (protyle: IProtyle, nodeElement: HTMLElement, value = "") => { +export const updateCellsValue = (protyle: IProtyle, nodeElement: HTMLElement, value: string | any = "") => { const doOperations: IOperation[] = []; const undoOperations: IOperation[] = []; @@ -583,6 +599,10 @@ export const renderCell = (cellValue: IAVCellValue, wrap: boolean) => { }); } else if (cellValue.type === "checkbox") { text += ``; + } else if (cellValue.type === "relation") { + cellValue?.relation?.contents?.forEach((item, index) => { + text += `${item}`; + }) } if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated"].includes(cellValue.type) && cellValue && cellValue[cellValue.type as "url"].content) { diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index 5a4f18450..e497a6365 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -26,14 +26,14 @@ import {removeBlock} from "../../wysiwyg/remove"; import {getEditorRange} from "../../util/selection"; import {avRender} from "./render"; import {setPageSize} from "./row"; -import {openSearchAV, updateRelation} from "./relation"; +import {bindRelationEvent, getRelationHTML, openSearchAV, setRelationCell, updateRelation} from "./relation"; export const openMenuPanel = (options: { protyle: IProtyle, blockElement: Element, - type: "select" | "properties" | "config" | "sorts" | "filters" | "edit" | "date" | "asset" | "switcher", + type: "select" | "properties" | "config" | "sorts" | "filters" | "edit" | "date" | "asset" | "switcher" | "relation", colId?: string, // for edit - cellElements?: HTMLElement[], // for select & date + cellElements?: HTMLElement[], // for select & date & relation & asset cb?: (avPanelElement: Element) => void }) => { let avPanelElement = document.querySelector(".av__panel"); @@ -67,6 +67,17 @@ export const openMenuPanel = (options: { html = getEditHTML({protyle: options.protyle, data, colId: options.colId}); } else if (options.type === "date") { html = getDateHTML(data.view, options.cellElements); + } else if (options.type === "relation") { + html = getRelationHTML(data, options.cellElements); + if (!html) { + openMenuPanel({ + protyle: options.protyle, + blockElement: options.blockElement, + type: "edit", + colId: options.cellElements[0].dataset.colId + }); + return; + } } document.body.insertAdjacentHTML("beforeend", `
@@ -76,7 +87,7 @@ export const openMenuPanel = (options: { avPanelElement = document.querySelector(".av__panel"); const menuElement = avPanelElement.lastElementChild as HTMLElement; const tabRect = options.blockElement.querySelector(".av__views")?.getBoundingClientRect(); - if (["select", "date", "asset"].includes(options.type)) { + if (["select", "date", "asset", "relation"].includes(options.type)) { const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); if (options.type === "select") { bindSelectEvent(options.protyle, data, menuElement, options.cellElements); @@ -86,12 +97,16 @@ export const openMenuPanel = (options: { bindAssetEvent({protyle: options.protyle, data, menuElement, cellElements: options.cellElements}); setTimeout(() => { setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); - }, Constants.TIMEOUT_LOAD); // 等待图片加载 + }, Constants.TIMEOUT_LOAD); // 等待加载 + } else if (options.type === "relation") { + bindRelationEvent({protyle: options.protyle, data, menuElement, cellElements: options.cellElements}); } - if (["select", "date"].includes(options.type)) { + if (["select", "date", "relation"].includes(options.type)) { const inputElement = menuElement.querySelector("input"); - inputElement.select(); - inputElement.focus(); + if (inputElement) { + inputElement.select(); + inputElement.focus(); + } setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); } } else { @@ -861,6 +876,11 @@ export const openMenuPanel = (options: { event.preventDefault(); event.stopPropagation(); break; + } else if (type === "setRelationCell") { + setRelationCell(options.protyle, data, options.blockElement as HTMLElement, target); + event.preventDefault(); + event.stopPropagation(); + break; } else if (type === "addColOptionOrCell") { addColOptionOrCell(options.protyle, data, options.cellElements, target, menuElement); window.siyuan.menus.menu.remove(); diff --git a/app/src/protyle/render/av/relation.ts b/app/src/protyle/render/av/relation.ts index 9cc6c2bf6..b3312d79b 100644 --- a/app/src/protyle/render/av/relation.ts +++ b/app/src/protyle/render/av/relation.ts @@ -4,6 +4,7 @@ import {upDownHint} from "../../../util/upDownHint"; import {fetchPost} from "../../../util/fetch"; import {escapeHtml} from "../../../util/escape"; import {transaction} from "../../wysiwyg/transaction"; +import {updateCellsValue} from "./cell"; const genSearchList = (element: Element, keyword: string, avId: string, cb?: () => void) => { fetchPost("/api/av/searchAttributeView", {keyword}, (response) => { @@ -135,7 +136,7 @@ export const toggleUpdateRelationBtn = (menuItemsElement: HTMLElement, avId: str inputItemElement.classList.add("fn__none"); } const inputElement = inputItemElement.querySelector("input") as HTMLInputElement; - if ((searchElement.dataset.avId && oldValue.avID !== searchElement.dataset.avId)|| oldValue.isTwoWay !== switchElement.checked || inputElement.dataset.oldValue !== inputElement.value) { + 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"); @@ -150,3 +151,105 @@ export const toggleUpdateRelationBtn = (menuItemsElement: HTMLElement, avId: str btnElement.classList.remove("fn__none"); } } + +export const bindRelationEvent = (options: { + protyle: IProtyle, + data: IAV, + menuElement: HTMLElement, + cellElements: HTMLElement[] +}) => { + const hasIds = options.menuElement.textContent.split(","); + fetchPost("/api/av/renderAttributeView", { + id: options.menuElement.firstElementChild.getAttribute("data-av-id"), + }, response => { + const avData = response.data as IAV; + let cellIndex = 0 + avData.view.columns.find((item, index) => { + if (item.type === "block") { + cellIndex = index + return; + } + }) + let html = "" + let selectHTML = "" + avData.view.rows.forEach((item) => { + const text = item.cells[cellIndex].value.block.content || item.cells[cellIndex].value.block.id; + if (hasIds.includes(item.id)) { + selectHTML += `` + } else { + html += `` + } + }) + const empty = `` + options.menuElement.innerHTML = `
${selectHTML || empty} + +${html || empty}
` + }) +} + +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) { + let ids = "" + cellElements[0].querySelectorAll("span").forEach((item) => { + ids += `${item.getAttribute("data-id")},`; + }); + return `${ids}` + } else { + return "" + } +} + +export const setRelationCell = (protyle: IProtyle, data: IAV, nodeElement: HTMLElement, target: HTMLElement) => { + const menuElement = hasClosestByClassName(target, "b3-menu__items"); + if (!menuElement) { + return + } + const ids: string[] = []; + Array.from(menuElement.children).forEach((item) => { + const id = item.getAttribute("data-id") + if (item.getAttribute("draggable") && id) { + ids.push(id); + } + }) + const empty = `` + 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(); + } + ids.splice(ids.indexOf(targetId), 1); + separatorElement.after(target); + // TODO + if (!separatorElement.previousElementSibling) { + separatorElement.insertAdjacentHTML("beforebegin", empty); + } + } else { + if (!separatorElement.previousElementSibling.getAttribute("data-id")) { + separatorElement.previousElementSibling.remove(); + } + ids.push(targetId); + separatorElement.before(target); + // TODO + if (!separatorElement.nextElementSibling) { + separatorElement.insertAdjacentHTML("afterend", empty); + } + } + updateCellsValue(protyle, nodeElement, ids); +}; diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 88d1da3c4..e9dd3f4dc 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -1084,11 +1084,7 @@ interface IAVColumn { name: string, color: string, }[], - relation?: { - avID: string - backKeyID: string - isTwoWay: boolean - } + relation?: IAVCellRelationValue } interface IAVRow { @@ -1138,6 +1134,10 @@ interface IAVCellValue { checkbox?: { checked: boolean } + relation?: { + blockIDs: string[] + contents?: string[] + } date?: IAVCellDateValue created?: IAVCellDateValue updated?: IAVCellDateValue @@ -1162,3 +1162,9 @@ interface IAVCellAssetValue { name: string, type: "file" | "image" } + +interface IAVCellRelationValue { + avID: string + backKeyID: string + isTwoWay: boolean +}