import {transaction} from "../../wysiwyg/transaction"; import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest"; import {openMenuPanel} from "./openMenuPanel"; import {updateAttrViewCellAnimation} from "./action"; import {isNotCtrl} from "../../util/compatibility"; import {isDynamicRef, objEquals} from "../../../util/functions"; import {fetchPost, fetchSyncPost} from "../../../util/fetch"; import {focusBlock, focusByRange} from "../../util/selection"; import * as dayjs from "dayjs"; import {unicode2Emoji} from "../../../emoji"; import {getColIconByType, getColId} from "./col"; import {genAVValueHTML} from "./blockAttr"; import {Constants} from "../../../constants"; import {hintRef} from "../../hint/extend"; import {getAssetName, pathPosix} from "../../../util/pathName"; import {mergeAddOption} from "./select"; import {escapeAttr, escapeHtml} from "../../../util/escape"; import {electronUndo} from "../../undo"; import {getFieldIdByCellElement} from "./row"; import {getFieldsByData} from "./view"; import {getCompressURL, removeCompressURL} from "../../../util/image"; import {callMobileAppShowKeyboard} from "../../../mobile/util/mobileAppUtil"; const renderCellURL = (urlContent: string) => { let host = urlContent; let suffix = ""; try { const urlObj = new URL(urlContent); if (urlObj.protocol.startsWith("http")) { host = urlObj.host; suffix = urlObj.href.replace(urlObj.origin, ""); if (suffix.length > 12) { suffix = suffix.substring(0, 4) + "..." + suffix.substring(suffix.length - 6); } } } catch (e) { // 不是 url 地址 host = Lute.EscapeHTMLStr(urlContent); } // https://github.com/siyuan-note/siyuan/issues/9291 return `${host}${suffix}`; }; export const getCellText = (cellElement: HTMLElement | false) => { if (!cellElement) { return ""; } let cellText = ""; const textElements = cellElement.querySelectorAll(".b3-chip, .av__celltext--ref, .av__celltext"); if (textElements.length > 0) { textElements.forEach(item => { if (item.querySelector(".av__cellicon")) { cellText += `${item.firstChild.textContent} → ${item.lastChild.textContent}, `; } else if (item.getAttribute("data-type") === "url") { cellText = item.getAttribute("data-href") + ", "; } else if (item.getAttribute("data-type") !== "block-more") { cellText += item.textContent + ", "; } }); cellText = cellText.substring(0, cellText.length - 2); } else { cellText = cellElement.textContent; } return cellText; }; export const genCellValueByElement = (colType: TAVCol, cellElement: HTMLElement) => { const cellValue: IAVCellValue = { type: colType, id: cellElement.dataset.id, }; if (colType === "number") { const value = cellElement.querySelector(".av__celltext").getAttribute("data-content"); cellValue.number = { content: parseFloat(value) || 0, isNotEmpty: !!value }; } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { const textElement = cellElement.querySelector(".av__celltext") as HTMLElement; cellValue[colType as "text"] = { content: colType === "url" ? textElement.dataset.href : textElement.textContent }; if (colType === "block" && textElement.dataset.id) { cellValue.block.id = textElement.dataset.id; if (textElement.previousElementSibling?.classList.contains("b3-menu__avemoji")) { const unicode = textElement.previousElementSibling.getAttribute("data-unicode"); if (unicode) { cellValue.block.icon = unicode; } } } } else if (colType === "mSelect" || colType === "select") { const mSelect: IAVCellSelectValue[] = []; cellElement.querySelectorAll(".b3-chip").forEach((item: HTMLElement) => { mSelect.push({ content: item.textContent.trim(), color: item.style.color.replace("var(--b3-font-color", "").replace(")", "") }); }); cellValue.mSelect = mSelect; } else if (["date", "created", "updated"].includes(colType)) { cellValue[colType as "date"] = JSON.parse(cellElement.querySelector(".av__celltext").getAttribute("data-value")); } else if (colType === "checkbox") { cellValue.checkbox = { checked: cellElement.querySelector("use").getAttribute("xlink:href") === "#iconCheck" ? true : false }; } else if (colType === "relation") { const blockIDs: string[] = []; const contents: IAVCellValue[] = []; Array.from(cellElement.querySelectorAll(".av__cell--relation")).forEach((relationItem: HTMLElement) => { const item = relationItem.querySelector(".av__celltext") as HTMLElement; blockIDs.push(relationItem.dataset.rowId); contents.push({ isDetached: !item.classList.contains("av__celltext--ref"), block: { content: item.textContent, id: item.dataset.id, }, type: "block" }); }); cellValue.relation = { blockIDs, contents }; } else if (colType === "mAsset") { const mAsset: IAVCellAssetValue[] = []; Array.from(cellElement.children).forEach((item) => { if (!item.classList.contains("av__celltext--url") && !item.classList.contains("av__cellassetimg")) { return; } const isImg = item.classList.contains("av__cellassetimg"); mAsset.push({ type: isImg ? "image" : "file", content: isImg ? removeCompressURL(item.getAttribute("src")) : item.getAttribute("data-url"), name: isImg ? "" : item.getAttribute("data-name") }); }); cellValue.mAsset = mAsset; } if (colType === "block") { cellValue.isDetached = cellElement.dataset.detached === "true"; } return cellValue; }; const getCellValueContent = (value: IAVCellValue): string => { if (["number", "text", "block", "url", "phone", "email", "template", "mAsset"].includes(value.type)) { return value[value.type as "text"].content; } if (["mSelect", "select"].includes(value.type)) { return value.mSelect[0].content; } if (value.type === "rollup") { return getCellValueContent(value.relation.contents[0]); } if (value.type === "checkbox") { return value.checkbox.checked ? "true" : "false"; } if (value.type === "relation") { return getCellValueContent(value.relation.contents[0]); } if (["date", "created", "updated"].includes(value.type)) { return dayjs(value[value.type as "date"].content).format("YYYY-MM-DD HH:mm"); } if (value.type === "lineNumber") { return ""; } }; const transformCellValue = (colType: TAVCol, value: IAVCellValue): IAVCellValue => { if (colType === value.type) { return value; } const newValue: IAVCellValue = { type: colType, }; if (colType === "number") { if (["date", "created", "updated"].includes(colType)) { newValue.number = { content: value[value.type as "date"].content, isNotEmpty: value[value.type as "date"].isNotEmpty }; } else { newValue.number = { content: parseFloat(getCellValueContent(value)) || 0, isNotEmpty: true }; } } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { newValue[colType as "text"] = { content: getCellValueContent(value).toString() }; } else if (colType === "mSelect" || colType === "select") { newValue.mSelect = [{ content: getCellValueContent(value).toString(), color: "1" }]; if (!newValue.mSelect[0].content) { newValue.mSelect = []; } } else if (colType === "rollup") { newValue.rollup = {contents: [value]}; } else if (colType === "checkbox") { newValue.checkbox = { checked: true }; } else if (colType === "relation") { if (value.type === "block") { newValue.relation = { blockIDs: [value.blockID], contents: [value] }; } else { newValue.relation = {blockIDs: [], contents: []}; } } else if (colType === "mAsset") { const content = getCellValueContent(value).toString(); newValue.mAsset = [{ type: Constants.SIYUAN_ASSETS_IMAGE.includes(pathPosix().extname(content).toLowerCase()) ? "image" : "file", content, name: "", }]; } else if (["date", "created", "updated"].includes(colType)) { if (["date", "created", "updated"].includes(value.type)) { newValue[colType as "date"] = JSON.parse(JSON.stringify(value[value.type as "date"])); } else { newValue[colType as "date"] = { content: null, isNotEmpty: false, content2: null, isNotEmpty2: false, hasEndDate: false, isNotTime: true, }; } } else if (colType === "lineNumber") { return { type: "lineNumber" }; } return newValue; }; export const genCellValue = (colType: TAVCol, value: string | any) => { let cellValue: IAVCellValue = { type: colType, [colType === "select" ? "mSelect" : colType]: value as IAVCellDateValue }; if (typeof value === "string" && value) { if (colType === "number") { cellValue = { type: colType, number: { content: parseFloat(value) || 0, isNotEmpty: true } }; } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { cellValue = { type: colType, [colType]: { content: value } }; } else if (colType === "mSelect" || colType === "select") { cellValue = { type: colType, mSelect: [{ content: value, color: "1" }] }; } else if (colType === "checkbox") { cellValue = { type: colType, checkbox: { checked: true } }; } else if (colType === "date") { let values = value.split("→"); if (values.length !== 2) { values = value.split("-"); if (values.length !== 2) { values = value.split("~"); } } const dateObj1 = dayjs(values[0]); const dateObj2 = dayjs(values[1] || ""); if (isNaN(dateObj1.valueOf())) { cellValue = { type: colType, date: { content: null, isNotEmpty: false, content2: null, isNotEmpty2: false, formattedContent: "", hasEndDate: false, isNotTime: true, } }; } else { cellValue = { type: colType, date: { content: dateObj1.valueOf(), isNotEmpty: true, content2: dateObj2.valueOf() || 0, isNotEmpty2: !isNaN(dateObj2.valueOf()), hasEndDate: !isNaN(dateObj2.valueOf()), isNotTime: dateObj1.hour() === 0 && values[0].split(":").length === 1, formattedContent: "", } }; } } else if (colType === "relation") { cellValue = { type: colType, relation: {blockIDs: [value], contents: []} }; } else if (colType === "mAsset") { const type = pathPosix().extname(value).toLowerCase(); cellValue = { type: colType, mAsset: [{ type: Constants.SIYUAN_ASSETS_IMAGE.includes(type) ? "image" : "file", content: value, name: "", }] }; } } else if (typeof value === "undefined" || !value) { if (colType === "number") { cellValue = { type: colType, number: { content: 0, isNotEmpty: false } }; } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { cellValue = { type: colType, [colType]: { content: "" } }; } else if (colType === "mSelect" || colType === "select" || colType === "mAsset") { cellValue = { type: colType, [colType === "select" ? "mSelect" : colType]: [] }; } else if (["date", "created", "updated"].includes(colType)) { cellValue = { type: colType, [colType]: { content: null, isNotEmpty: false, content2: null, isNotEmpty2: false, hasEndDate: false, isNotTime: true, } }; } else if (colType === "checkbox") { cellValue = { type: colType, checkbox: { checked: false } }; } else if (colType === "relation") { cellValue = { type: colType, relation: {blockIDs: [], contents: []} }; } else if (colType === "rollup") { cellValue = { type: colType, rollup: {contents: []} }; } } if (colType === "block") { if (typeof value === "object" && value && value.id) { cellValue.isDetached = false; } else { cellValue.isDetached = true; } } return cellValue; }; export const cellScrollIntoView = (blockElement: HTMLElement, cellElement: Element, onlyHeight = true) => { const cellRect = cellElement.getBoundingClientRect(); if (!onlyHeight) { const avScrollElement = blockElement.querySelector(".av__scroll"); const rowElement = hasClosestByClassName(cellElement, "av__row"); if (avScrollElement && rowElement) { const stickyElement = rowElement.querySelector(".av__colsticky"); if (!stickyElement.contains(cellElement)) { // https://github.com/siyuan-note/siyuan/issues/12162 const stickyRight = stickyElement.getBoundingClientRect().right; const avScrollRect = avScrollElement.getBoundingClientRect(); if (stickyRight > cellRect.left || avScrollRect.right < cellRect.left) { avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - stickyRight; } else if (stickyRight < cellRect.left && avScrollRect.right < cellRect.right) { if (cellRect.width + stickyRight > avScrollRect.right) { avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - stickyRight; } else { avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.right - avScrollRect.right; } } } } } /// #if MOBILE const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); if (contentElement && cellElement.getAttribute("data-dtype") !== "checkbox") { const keyboardToolbarTop = (window.siyuan.mobile.size.isLandscape ? window.siyuan.mobile.size.landscape.height2 : window.siyuan.mobile.size.portrait.height2) - 48; if (cellRect.bottom > keyboardToolbarTop) { contentElement.scrollTop = contentElement.scrollTop + (cellRect.bottom - keyboardToolbarTop); } else if (cellRect.top < 110) { contentElement.scrollTop -= 110 - cellRect.top; } } /// #else if (!blockElement.querySelector(".av__header")) { // 属性面板 return; } const bodyElement = hasClosestByClassName(cellElement, "av__body"); if (!bodyElement) { return; } const avHeaderRect = bodyElement.querySelector(".av__row--header").getBoundingClientRect(); if (avHeaderRect.bottom > cellRect.top) { const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); if (contentElement) { contentElement.scrollTop = contentElement.scrollTop + cellRect.top - avHeaderRect.bottom; } } else { const footerElement = bodyElement.querySelector(".av__row--footer"); if (footerElement?.querySelector(".av__calc--ashow")) { const avFooterRect = footerElement.getBoundingClientRect(); if (avFooterRect.top < cellRect.bottom) { const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); if (contentElement) { contentElement.scrollTop = contentElement.scrollTop + cellRect.bottom - avFooterRect.top; } } } else { const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); if (contentElement) { const contentRect = contentElement.getBoundingClientRect(); if (cellRect.bottom > contentRect.bottom) { contentElement.scrollTop = contentElement.scrollTop + (cellRect.bottom - contentRect.bottom); } } } } /// #endif }; export const getTypeByCellElement = (cellElement: Element) => { if (cellElement.parentElement.classList.contains("av__gallery-field")) { return cellElement.getAttribute("data-dtype") as TAVCol; } const scrollElement = hasClosestByClassName(cellElement, "av__scroll"); if (!scrollElement) { return; } return scrollElement.querySelector(".av__row--header").querySelector(`[data-col-id="${cellElement.getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol; }; export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type?: TAVCol) => { if (cellElements.length === 0 || (cellElements.length === 1 && !cellElements[0])) { return; } if (!type) { type = getTypeByCellElement(cellElements[0]); } if (type === "updated" || type === "created" || document.querySelector(".av__mask")) { return; } const blockElement = hasClosestBlock(cellElements[0]); if (!blockElement) { return; } const viewType = blockElement.getAttribute("data-av-type") as TAVView; let cellRect = cellElements[0].getBoundingClientRect(); const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); if (viewType === "table") { cellScrollIntoView(blockElement, cellElements[0], false); } cellRect = cellElements[0].getBoundingClientRect(); let html = ""; let height = cellRect.height; const cssStyle = getComputedStyle(cellElements[0]); let style = `font-family:${cssStyle.fontFamily};font-size:${cssStyle.fontSize};line-height:${cssStyle.lineHeight};padding:${cssStyle.padding};position:absolute;top: ${cellRect.top}px;`; if (contentElement) { const contentRect = contentElement.getBoundingClientRect(); if (cellRect.bottom > contentRect.bottom) { height = contentRect.bottom - cellRect.top; } const width = Math.min(Math.max(cellRect.width, 25), contentRect.width); style = `style='height: ${height}px;width:${width}px;left: ${(cellRect.left < contentRect.left || cellRect.left + width > contentRect.right) ? contentRect.left : cellRect.left}px;${style}'`; } else { style = `style='height: ${height}px;width:${Math.max(cellRect.width, 25)}px;left: ${cellRect.left}px;${style}'`; } if (["text", "email", "phone", "block", "template"].includes(type)) { html = ``; } else if (type === "url") { html = ``; } else if (type === "number") { html = ``; } else { if (["select", "mSelect"].includes(type)) { if (blockElement.getAttribute("data-rendering") === "true") { return; } openMenuPanel({protyle, blockElement, type: "select", cellElements}); } else if (type === "mAsset") { openMenuPanel({protyle, blockElement, type: "asset", cellElements}); focusBlock(blockElement); } else if (type === "date") { openMenuPanel({protyle, blockElement, type: "date", cellElements}); } else if (type === "checkbox") { updateCellValueByInput(protyle, type, blockElement, cellElements); } else if (type === "relation") { openMenuPanel({protyle, blockElement, type: "relation", cellElements}); } else if (type === "rollup") { openMenuPanel({ protyle, blockElement, type: "rollup", cellElements, colId: getColId(cellElements[0], viewType) }); } if (viewType === "table" && !hasClosestByClassName(cellElements[0], "custom-attr")) { cellElements[0].classList.add("av__cell--select"); addDragFill(cellElements[0]); } return; } window.siyuan.menus.menu.remove(); document.body.insertAdjacentHTML("beforeend", `
${html}
`); const avMaskElement = document.querySelector(".av__mask"); const inputElement = avMaskElement.querySelector(".b3-text-field") as HTMLInputElement; if (inputElement) { if (["text", "email", "phone", "block", "template"].includes(type)) { inputElement.value = cellElements[0].querySelector(".av__celltext")?.textContent || ""; } inputElement.select(); inputElement.focus(); callMobileAppShowKeyboard(); if (type === "template") { fetchPost("/api/av/renderAttributeView", { id: blockElement.dataset.avId, viewID: blockElement.getAttribute(Constants.CUSTOM_SY_AV_VIEW) }, (response) => { getFieldsByData(response.data).find((item: IAVColumn) => { if (item.id === getColId(cellElements[0], viewType)) { inputElement.value = item.template; inputElement.dataset.template = item.template; return true; } }); }); } if (type === "block") { inputElement.addEventListener("input", (event: InputEvent) => { if (Constants.BLOCK_HINT_KEYS.includes(inputElement.value.substring(0, 2))) { protyle.toolbar.range = document.createRange(); if (cellElements[0] && !blockElement.contains(cellElements[0])) { const rowID = getFieldIdByCellElement(cellElements[0], viewType); if (viewType === "table") { cellElements[0] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${cellElements[0].dataset.colId}"]`)) as HTMLElement; } else { cellElements[0] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${cellElements[0].dataset.fieldId}"]`)) as HTMLElement; } } protyle.toolbar.range.selectNodeContents(cellElements[0].lastChild); focusByRange(protyle.toolbar.range); if (viewType === "table") { cellElements[0].classList.add("av__cell--select"); addDragFill(cellElements[0]); } let textPlain = inputElement.value; if (isDynamicRef(textPlain)) { textPlain = textPlain.substring(2, 22 + 2); } else { textPlain = textPlain.substring(2); } hintRef(textPlain, protyle, "av"); avMaskElement?.remove(); event.preventDefault(); event.stopPropagation(); } }); } inputElement.addEventListener("keydown", (event) => { if (event.isComposing) { return; } if (electronUndo(event)) { return; } if (event.key === "Escape" || event.key === "Tab" || (event.key === "Enter" && !event.shiftKey && isNotCtrl(event))) { updateCellValueByInput(protyle, type, blockElement, cellElements); if (event.key === "Tab") { protyle.wysiwyg.element.dispatchEvent(new KeyboardEvent("keydown", { shiftKey: event.shiftKey, ctrlKey: event.ctrlKey, altKey: event.altKey, metaKey: event.metaKey, key: "Tab", keyCode: 9 })); } event.preventDefault(); event.stopPropagation(); } }); } const removeAvMask = (event: Event) => { if ((event.target as HTMLElement).classList.contains("av__mask") && document.activeElement.tagName !== "TEXTAREA" && document.activeElement.tagName !== "INPUT") { updateCellValueByInput(protyle, type, blockElement, cellElements); avMaskElement?.remove(); } }; avMaskElement.addEventListener("click", (event) => { removeAvMask(event); }); avMaskElement.addEventListener("contextmenu", (event) => { removeAvMask(event); }); avMaskElement.addEventListener("mousedown", (event: MouseEvent & { target: HTMLElement }) => { if (event.button === 1) { if (event.target.classList.contains("av__mask") && document.activeElement && document.activeElement.nodeType === 1) { (document.activeElement as HTMLElement).blur(); } removeAvMask(event); } }); }; const updateCellValueByInput = (protyle: IProtyle, type: TAVCol, blockElement: HTMLElement, cellElements: HTMLElement[]) => { const viewType = blockElement.getAttribute("data-av-type") as TAVView; if (viewType === "table") { const rowElement = hasClosestByClassName(cellElements[0], "av__row"); if (!rowElement) { return; } if (cellElements.length === 1 && cellElements[0].dataset.detached === "true" && !rowElement.dataset.id) { return; } } const avMaskElement = document.querySelector(".av__mask"); const avID = blockElement.getAttribute("data-av-id"); if (type === "template") { const colId = getColId(cellElements[0], viewType); const textElement = avMaskElement.querySelector(".b3-text-field") as HTMLInputElement; if (textElement.value !== textElement.dataset.template && !blockElement.getAttribute("data-loading")) { transaction(protyle, [{ action: "updateAttrViewColTemplate", id: colId, avID, data: textElement.value, type: "template", }], [{ action: "updateAttrViewColTemplate", id: colId, avID, data: textElement.dataset.template, type: "template", }]); blockElement.setAttribute("data-loading", "true"); } } else { updateCellsValue(protyle, blockElement, type === "checkbox" ? { checked: cellElements[0].querySelector("use").getAttribute("xlink:href") === "#iconUncheck" } : (avMaskElement.querySelector(".b3-text-field") as HTMLInputElement).value, cellElements); } if (viewType === "table" && // 兼容新增行后台隐藏 cellElements[0] && !hasClosestByClassName(cellElements[0], "custom-attr")) { cellElements[0].classList.add("av__cell--select"); addDragFill(cellElements[0]); } // 单元格编辑中 ctrl+p 光标定位 if (!document.querySelector(".b3-dialog")) { focusBlock(blockElement); } document.querySelectorAll(".av__mask").forEach((item) => { item.remove(); }); }; export const updateCellsValue = async (protyle: IProtyle, nodeElement: HTMLElement, value?: any, cElements?: HTMLElement[], columns?: IAVColumn[], html?: string, getOperations = false) => { const doOperations: IOperation[] = []; const undoOperations: IOperation[] = []; const avID = nodeElement.dataset.avId; const id = nodeElement.dataset.nodeId; let text = ""; const json: IAVCellValue[][] = []; let cellElements: Element[]; if (cElements?.length > 0) { cellElements = cElements; } else { cellElements = Array.from(nodeElement.querySelectorAll(".av__cell--active, .av__cell--select")); if (cellElements.length === 0) { nodeElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(rowElement => { rowElement.querySelectorAll(".av__cell").forEach(cellElement => { cellElements.push(cellElement); }); }); } } const isCustomAttr = hasClosestByClassName(cellElements[0], "custom-attr"); const viewType = nodeElement.getAttribute("data-av-type") as TAVView; for (let elementIndex = 0; elementIndex < cellElements.length; elementIndex++) { let item = cellElements[elementIndex] as HTMLElement; const rowID = getFieldIdByCellElement(item, viewType); if (!rowID) { break; } if (!nodeElement.contains(item)) { if (viewType === "table") { item = cellElements[elementIndex] = (nodeElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${item.dataset.colId}"]`) || nodeElement.querySelector(`.fn__flex-1[data-col-id="${item.dataset.colId}"]`)) as HTMLElement; } else { item = cellElements[elementIndex] = (nodeElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${item.dataset.fieldId}"]`)) as HTMLElement; } } if (!item) { // 兼容新增行后台隐藏 break; } const type = getTypeByCellElement(item) || item.dataset.type as TAVCol; if (["created", "updated", "template", "rollup"].includes(type)) { break; } const cellId = item.dataset.id; // 刚创建时无 id,更新需和 oldValue 保持一致 const colId = getColId(item, viewType); text += getCellText(item) + ((cellElements[elementIndex + 1] && item.nextElementSibling && item.nextElementSibling === cellElements[elementIndex + 1]) ? "\t" : "\n\n"); const oldValue = genCellValueByElement(type, item); if (elementIndex === 0 || cellElements[elementIndex - 1] !== item.previousElementSibling) { json.push([]); } json[json.length - 1].push(oldValue); let newValue = value; // relation 为全部更新,以下类型为添加 if (type === "mAsset") { if (Array.isArray(value)) { newValue = oldValue.mAsset.concat(value); } else if (typeof value !== "undefined" && typeof value !== "object") { // 不传入为删除,传入字符串不进行处理 let link = protyle.lute.GetLinkDest(value); let name = ""; let imgSrc = ""; // https://github.com/siyuan-note/siyuan/issues/13892 if (!link && value.startsWith("assets/")) { link = value; name = getAssetName(value) + pathPosix().extname(value); } if (html) { const tempElement = document.createElement("template"); tempElement.innerHTML = html; const aElement = tempElement.content.querySelector('[data-type~="a"]'); if (aElement) { link = aElement.getAttribute("data-href"); name = aElement.textContent; } else { const imgElement = tempElement.content.querySelector(".img img"); if (imgElement) { imgSrc = imgElement.getAttribute("data-src"); } } } // https://github.com/siyuan-note/siyuan/issues/12308 if (!link) { name = value; } if (!link && !name && !imgSrc) { break; } if (imgSrc) { // 支持解析 ![]() https://github.com/siyuan-note/siyuan/issues/11487 newValue = oldValue.mAsset.concat({ type: "image", content: imgSrc, name: "" }); } else { // 支持解析 https://github.com/siyuan-note/siyuan/issues/11463 newValue = oldValue.mAsset.concat({ type: "file", content: link, name }); } } } else if (type === "mSelect" || type === "select") { // 不传入为删除 if (typeof value === "string") { const newMSelectValue: IAVCellSelectValue[] = []; let colorIndex = oldValue.mSelect.length; // 以逗号分隔,去重,去空,去换行后做为选项 [...new Set(value.split(",").map(v => v.trim().replace(/\n|\r\n|\r|\u2028|\u2029/g, "")))].forEach((item) => { if (!item) { return; } let hasSameContent = false; oldValue.mSelect.find((mSelectItem) => { if (mSelectItem.content === item) { hasSameContent = true; return true; } }); if (hasSameContent) { return; } colorIndex++; newMSelectValue.push({ content: item, color: colorIndex.toString() }); }); newValue = oldValue.mSelect.concat(newMSelectValue); } } else if (type === "block" && typeof value === "string" && oldValue.block.id) { newValue = { content: value, id: oldValue.block.id, }; if (oldValue.block.icon) { newValue.icon = oldValue.block.icon; } } let cellValue: IAVCellValue; if (typeof newValue === "object" && newValue.type) { cellValue = transformCellValue(type, newValue); } else { cellValue = genCellValue(type, newValue); } cellValue.id = cellId; if ((cellValue.type === "date" && typeof cellValue.date === "string") || (cellValue.type === "relation" && typeof cellValue.relation === "string")) { break; } if (columns && (type === "select" || type === "mSelect")) { const operations = mergeAddOption(columns.find(e => e.id === colId), cellValue, avID); doOperations.push(...operations.doOperations); undoOperations.push(...operations.undoOperations); } // formattedContent 在单元格渲染时没有用到,需对比保持一致 if (type === "date") { if (!(value && typeof value === "object" && typeof value.isNotTime === "boolean")) { const response = await fetchSyncPost("/api/av/getAttributeViewKeysByID", {avID: avID, keyIDs: [colId]}); if (response.data[0].date) { cellValue.date.isNotTime = !response.data[0].date.fillSpecificTime; } } cellValue.date.formattedContent = oldValue.date.formattedContent; } if (objEquals(cellValue, oldValue)) { break; } doOperations.push({ action: "updateAttrViewCell", id: cellId, avID, keyID: colId, rowID, data: cellValue }); undoOperations.push({ action: "updateAttrViewCell", id: cellId, avID, keyID: colId, rowID, data: oldValue }); if (isCustomAttr) { item.innerHTML = genAVValueHTML(cellValue); } else { updateAttrViewCellAnimation(item, cellValue); } } if (getOperations) { return {doOperations, undoOperations}; } if (doOperations.length > 0) { doOperations.push({ action: "doUpdateUpdated", id, data: dayjs().format("YYYYMMDDHHmmss"), }); undoOperations.push({ action: "doUpdateUpdated", id, data: nodeElement.getAttribute("updated"), }); transaction(protyle, doOperations, undoOperations); } return {text: text.substring(0, text.length - 2), json}; }; export const renderCellAttr = (cellElement: Element, value: IAVCellValue) => { if (value.type === "checkbox") { if (value.checkbox.checked) { cellElement.classList.add("av__cell-check"); cellElement.classList.remove("av__cell-uncheck"); } else { cellElement.classList.remove("av__cell-check"); cellElement.classList.add("av__cell-uncheck"); } } else if (value.type === "block") { if (value.isDetached) { cellElement.setAttribute("data-detached", "true"); } else { cellElement.querySelector(".av__celltext").setAttribute("data-id", value.block.id); cellElement.removeAttribute("data-detached"); } } }; export const renderCell = (cellValue: IAVCellValue, rowIndex = 0, showIcon = true, type: TAVView = "table") => { let text = ""; if ("template" === cellValue.type) { text = `${cellValue ? (cellValue.template.content || "") : ""}`; } else if ("text" === cellValue.type) { text = `${cellValue ? Lute.EscapeHTMLStr(cellValue.text.content || "") : ""}`; } else if (["email", "phone"].includes(cellValue.type)) { text = `${cellValue ? Lute.EscapeHTMLStr(cellValue[cellValue.type as "email"].content || "") : ""}`; } else if ("url" === cellValue.type) { text = renderCellURL(cellValue?.url?.content || ""); } else if (cellValue.type === "block") { // 不可使用换行 https://github.com/siyuan-note/siyuan/issues/11365 if (cellValue?.isDetached) { text = `${Lute.EscapeHTMLStr(cellValue.block.content || "")}${window.siyuan.languages.more}`; } else { text = `${unicode2Emoji(cellValue.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}${Lute.EscapeHTMLStr(cellValue.block.content)}${window.siyuan.languages.update}`; } } else if (cellValue.type === "number") { text = `${cellValue?.number.formattedContent || cellValue?.number.content || ""}`; } else if (cellValue.type === "mSelect" || cellValue.type === "select") { cellValue?.mSelect?.forEach((item, index) => { if (cellValue.type === "select" && index > 0) { return; } text += `${escapeHtml(item.content)}`; }); } else if (cellValue.type === "date") { const dataValue = cellValue ? cellValue.date : null; text = ``; if (dataValue && dataValue.isNotEmpty) { text += dayjs(dataValue.content).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"); } if (dataValue && dataValue.hasEndDate && dataValue.isNotEmpty && dataValue.isNotEmpty2) { text += `${dayjs(dataValue.content2).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm")}`; } text += ""; } else if (["created", "updated"].includes(cellValue.type)) { const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null; text = ``; if (dataValue && dataValue.isNotEmpty) { text += dataValue.formattedContent; } text += ""; } else if (["lineNumber"].includes(cellValue.type)) { // 渲染行号 text = `${rowIndex + 1}`; } else if (cellValue.type === "mAsset") { cellValue?.mAsset?.forEach((item) => { if (item.type === "image") { text += ``; } else { text += `${item.name || item.content}`; } }); } else if (cellValue.type === "checkbox") { text += `
`; if (["gallery", "kanban"].includes(type) && cellValue?.checkbox?.content) { text += `${cellValue?.checkbox?.content}`; } text += "
"; } else if (cellValue.type === "rollup") { let rollupType; cellValue?.rollup?.contents?.forEach((item) => { const rollupText = ["template", "select", "mSelect", "mAsset", "relation"].includes(item.type) ? renderCell(item, rowIndex, showIcon, type) : renderRollup(item, showIcon); if (rollupText) { text += rollupText + (item.type === "checkbox" ? "" : ", "); } rollupType = item.type; }); if (text) { if (rollupType === "checkbox") { text = `
${text}
`; } else if (text.endsWith(", ")) { text = text.substring(0, text.length - 2); } } } else if (cellValue.type === "relation") { cellValue?.relation?.contents?.forEach((item, index) => { if (item && item.block) { const rowID = cellValue.relation.blockIDs[index]; if (item?.isDetached) { text += `${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}`; } else { // data-block-id 用于更新 emoji text += `${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}`; } } }); if (text && text.endsWith(", ")) { text = text.substring(0, text.length - 2); } } if ((["text", "template", "url", "email", "phone", "date", "created", "updated"].includes(cellValue.type) && cellValue[cellValue.type as "url"]?.content) || cellValue.type === "lineNumber" || (cellValue.type === "number" && cellValue.number?.isNotEmpty) || (cellValue.type === "block" && cellValue.block?.content)) { text += ``; } return text; }; const renderRollup = (cellValue: IAVCellValue, showIcon: boolean) => { let text = ""; if (["text"].includes(cellValue.type)) { text = cellValue ? (cellValue[cellValue.type as "text"].content || "") : ""; } else if (["email", "phone"].includes(cellValue.type)) { const emailContent = cellValue ? cellValue[cellValue.type as "email"].content : ""; if (emailContent) { text = `${emailContent}`; } } else if ("url" === cellValue.type) { const urlContent = cellValue?.url?.content || ""; if (urlContent) { text = renderCellURL(urlContent); } } else if (cellValue.type === "block") { if (cellValue?.isDetached) { text = `${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}`; } else { text = `${unicode2Emoji(cellValue.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}`; } } else if (cellValue.type === "number") { text = cellValue?.number.formattedContent || cellValue?.number.content.toString() || ""; } else if (cellValue.type === "checkbox") { text += ``; } else if (["date", "updated", "created"].includes(cellValue.type)) { const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null; if (dataValue.formattedContent) { text = dataValue.formattedContent; } else { if (dataValue && dataValue.isNotEmpty) { text = dayjs(dataValue.content).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"); } if (dataValue && dataValue.hasEndDate && dataValue.isNotEmpty && dataValue.isNotEmpty2) { text = `${dayjs(dataValue.content2).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm")}`; } } if (text) { text = `${text}`; } } return text; }; export const updateHeaderCell = (cellElement: HTMLElement, headerValue: { icon?: string, name?: string, pin?: boolean, }) => { if (typeof headerValue.icon !== "undefined") { cellElement.dataset.icon = headerValue.icon; cellElement.querySelector(".av__cellheadericon").outerHTML = headerValue.icon ? unicode2Emoji(headerValue.icon, "av__cellheadericon", true) : ``; } if (typeof headerValue.name !== "undefined") { cellElement.querySelector(".av__celltext").textContent = headerValue.name; } if (typeof headerValue.pin !== "undefined") { const textElement = cellElement.querySelector(".av__celltext"); if (headerValue.pin) { if (!cellElement.querySelector(".av__cellheadericon--pin")) { textElement.insertAdjacentHTML("afterend", ''); } } else { cellElement.querySelector(".av__cellheadericon--pin")?.remove(); } } }; export const getPositionByCellElement = (cellElement: HTMLElement) => { let rowElement = hasClosestByClassName(cellElement, "av__row"); if (!rowElement) { return; } let rowIndex = -1; while (rowElement) { rowElement = rowElement.previousElementSibling as HTMLElement; rowIndex++; } let celIndex = -2; while (cellElement) { cellElement = cellElement.previousElementSibling as HTMLElement; if (cellElement && cellElement.classList.contains("av__colsticky")) { cellElement = cellElement.lastElementChild as HTMLElement; } celIndex++; } return {rowIndex, celIndex}; }; export const dragFillCellsValue = (protyle: IProtyle, nodeElement: HTMLElement, originData: { [key: string]: IAVCellValue[] }, originCellIds: string[], activeElement: Element) => { nodeElement.querySelector(".av__drag-fill")?.remove(); const newData: { [key: string]: Array } = {}; nodeElement.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => { if (originCellIds.includes(item.dataset.id)) { return; } const rowElement = hasClosestByClassName(item, "av__row"); if (!rowElement) { return; } if (!newData[rowElement.dataset.id]) { newData[rowElement.dataset.id] = []; } const value: IAVCellValue & { colId?: string, element?: HTMLElement } = genCellValueByElement(getTypeByCellElement(item), item); value.colId = item.dataset.colId; value.element = item; newData[rowElement.dataset.id].push(value); }); const doOperations: IOperation[] = []; const undoOperations: IOperation[] = []; const avID = nodeElement.dataset.avId; const originKeys = Object.keys(originData); const showIcon = activeElement.querySelector(".b3-menu__avemoji") ? true : false; Object.keys(newData).forEach((rowID, index) => { newData[rowID].forEach((item, cellIndex) => { if (["rollup", "template", "created", "updated"].includes(item.type) || (item.type === "block" && item.element.getAttribute("data-detached") !== "true")) { return; } // https://ld246.com/article/1707975507571 数据库下拉填充数据后异常 const data = JSON.parse(JSON.stringify(originData[originKeys[index % originKeys.length]][cellIndex])); data.id = item.id; const keyID = item.colId; if (data.type === "block") { data.isDetached = true; delete data.block.id; } doOperations.push({ action: "updateAttrViewCell", id: item.id, avID, keyID, rowID, data }); item.element.innerHTML = renderCell(data, 0, showIcon); renderCellAttr(item.element, data); delete item.colId; delete item.element; undoOperations.push({ action: "updateAttrViewCell", id: item.id, avID, keyID, rowID, data: item }); }); }); focusBlock(nodeElement); if (doOperations.length > 0) { transaction(protyle, doOperations, undoOperations); } }; export const addDragFill = (cellElement: Element) => { if (!cellElement) { return; } cellElement.classList.add("av__cell--active"); if (!cellElement.querySelector(".av__drag-fill")) { const cellType = cellElement.getAttribute("data-dtype") as TAVCol; if (["template", "rollup", "lineNumber", "created", "updated"].includes(cellType)) { return; } cellElement.insertAdjacentHTML("beforeend", `
`); } }; export const cellValueIsEmpty = (value: IAVCellValue) => { if (value.type === "checkbox") { return false; } if (["text", "block", "url", "phone", "email", "template"].includes(value.type)) { return !value[value.type as "text"]?.content; } if (value.type === "number") { return value.number ? !value.number.isNotEmpty : true; } if (["mSelect", "mAsset", "select"].includes(value.type)) { if (value[(value.type === "select" ? "mSelect" : value.type) as "mSelect"]?.length > 0) { return false; } return true; } if (["date", "created", "updated"].includes(value.type)) { return !value[value.type as "date"]?.isNotEmpty && !value[value.type as "date"]?.isNotEmpty2; } if (value.type === "relation") { if (value.relation?.blockIDs && value.relation.blockIDs.length > 0) { return false; } return true; } if (value.type === "rollup") { if (value.rollup?.contents && value.rollup.contents.length > 0) { return false; } return true; } };