瀏覽代碼

:art: https://github.com/siyuan-note/siyuan/issues/9888

Vanessa 1 年之前
父節點
當前提交
f42873c893

+ 21 - 1
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 += `<svg class="av__checkbox"><use xlink:href="#icon${cellValue?.checkbox?.checked ? "Check" : "Uncheck"}"></use></svg>`;
+    } else if (cellValue.type === "relation") {
+        cellValue?.relation?.contents?.forEach((item, index) => {
+            text += `<span data-id="${cellValue?.relation?.blockIDs[index]}">${item}</span>`;
+        })
     }
     if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated"].includes(cellValue.type) &&
         cellValue && cellValue[cellValue.type as "url"].content) {

+ 28 - 8
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", `<div class="av__panel" style="z-index: ${++window.siyuan.zIndex}">
@@ -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();

+ 104 - 1
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 += `<button data-id="${item.id}" data-type="setRelationCell" data-type="setRelationCell" class="b3-menu__item" draggable="true">
+    <svg class="b3-menu__icon"><use xlink:href="#iconDrag"></use></svg>
+    <span class="b3-menu__label">${text}</span>
+</button>`
+            } else {
+                html += `<button data-id="${item.id}" class="b3-menu__item" data-type="setRelationCell">
+    <span class="b3-menu__label">${text}</span>
+</button>`
+            }
+        })
+        const empty = `<button class="b3-menu__item">
+    <span class="b3-menu__label">${window.siyuan.languages.emptyContent}</span>
+</button>`
+        options.menuElement.innerHTML = `<div class="b3-menu__items">${selectHTML || empty}
+<button class="b3-menu__separator"></button>
+${html || empty}</div>`
+    })
+}
+
+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 `<span data-av-id="${colRelationData.avID}">${ids}</span>`
+    } 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 = `<button class="b3-menu__item">
+    <span class="b3-menu__label">${window.siyuan.languages.emptyContent}</span>
+</button>`
+    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);
+};

+ 11 - 5
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
+}