A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 1250 lines 56 kB view raw
1import {transaction} from "../../wysiwyg/transaction"; 2import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest"; 3import {openMenuPanel} from "./openMenuPanel"; 4import {updateAttrViewCellAnimation} from "./action"; 5import {isNotCtrl} from "../../util/compatibility"; 6import {isDynamicRef, objEquals} from "../../../util/functions"; 7import {fetchPost, fetchSyncPost} from "../../../util/fetch"; 8import {focusBlock, focusByRange} from "../../util/selection"; 9import * as dayjs from "dayjs"; 10import {unicode2Emoji} from "../../../emoji"; 11import {getColIconByType, getColId} from "./col"; 12import {genAVValueHTML} from "./blockAttr"; 13import {Constants} from "../../../constants"; 14import {hintRef} from "../../hint/extend"; 15import {getAssetName, pathPosix} from "../../../util/pathName"; 16import {mergeAddOption} from "./select"; 17import {escapeAttr, escapeHtml} from "../../../util/escape"; 18import {electronUndo} from "../../undo"; 19import {getFieldIdByCellElement} from "./row"; 20import {getFieldsByData} from "./view"; 21import {getCompressURL, removeCompressURL} from "../../../util/image"; 22 23const renderCellURL = (urlContent: string) => { 24 let host = urlContent; 25 let suffix = ""; 26 try { 27 const urlObj = new URL(urlContent); 28 if (urlObj.protocol.startsWith("http")) { 29 host = urlObj.host; 30 suffix = urlObj.href.replace(urlObj.origin, ""); 31 if (suffix.length > 12) { 32 suffix = suffix.substring(0, 4) + "..." + suffix.substring(suffix.length - 6); 33 } 34 } 35 } catch (e) { 36 // 不是 url 地址 37 host = Lute.EscapeHTMLStr(urlContent); 38 } 39 // https://github.com/siyuan-note/siyuan/issues/9291 40 return `<span class="av__celltext av__celltext--url" data-type="url" data-href="${escapeAttr(urlContent)}"><span>${host}</span><span class="ft__on-surface">${suffix}</span></span>`; 41}; 42 43export const getCellText = (cellElement: HTMLElement | false) => { 44 if (!cellElement) { 45 return ""; 46 } 47 let cellText = ""; 48 const textElements = cellElement.querySelectorAll(".b3-chip, .av__celltext--ref, .av__celltext"); 49 if (textElements.length > 0) { 50 textElements.forEach(item => { 51 if (item.querySelector(".av__cellicon")) { 52 cellText += `${item.firstChild.textContent}${item.lastChild.textContent}, `; 53 } else if (item.getAttribute("data-type") === "url") { 54 cellText = item.getAttribute("data-href") + ", "; 55 } else if (item.getAttribute("data-type") !== "block-more") { 56 cellText += item.textContent + ", "; 57 } 58 }); 59 cellText = cellText.substring(0, cellText.length - 2); 60 } else { 61 cellText = cellElement.textContent; 62 } 63 return cellText; 64}; 65 66export const genCellValueByElement = (colType: TAVCol, cellElement: HTMLElement) => { 67 const cellValue: IAVCellValue = { 68 type: colType, 69 id: cellElement.dataset.id, 70 }; 71 if (colType === "number") { 72 const value = cellElement.querySelector(".av__celltext").getAttribute("data-content"); 73 cellValue.number = { 74 content: parseFloat(value) || 0, 75 isNotEmpty: !!value 76 }; 77 } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { 78 const textElement = cellElement.querySelector(".av__celltext") as HTMLElement; 79 cellValue[colType as "text"] = { 80 content: colType === "url" ? textElement.dataset.href : textElement.textContent 81 }; 82 if (colType === "block" && textElement.dataset.id) { 83 cellValue.block.id = textElement.dataset.id; 84 if (textElement.previousElementSibling?.classList.contains("b3-menu__avemoji")) { 85 const unicode = textElement.previousElementSibling.getAttribute("data-unicode"); 86 if (unicode) { 87 cellValue.block.icon = unicode; 88 } 89 } 90 } 91 } else if (colType === "mSelect" || colType === "select") { 92 const mSelect: IAVCellSelectValue[] = []; 93 cellElement.querySelectorAll(".b3-chip").forEach((item: HTMLElement) => { 94 mSelect.push({ 95 content: item.textContent.trim(), 96 color: item.style.color.replace("var(--b3-font-color", "").replace(")", "") 97 }); 98 }); 99 cellValue.mSelect = mSelect; 100 } else if (["date", "created", "updated"].includes(colType)) { 101 cellValue[colType as "date"] = JSON.parse(cellElement.querySelector(".av__celltext").getAttribute("data-value")); 102 } else if (colType === "checkbox") { 103 cellValue.checkbox = { 104 checked: cellElement.querySelector("use").getAttribute("xlink:href") === "#iconCheck" ? true : false 105 }; 106 } else if (colType === "relation") { 107 const blockIDs: string[] = []; 108 const contents: IAVCellValue[] = []; 109 Array.from(cellElement.querySelectorAll(".av__cell--relation")).forEach((relationItem: HTMLElement) => { 110 const item = relationItem.querySelector(".av__celltext") as HTMLElement; 111 blockIDs.push(relationItem.dataset.rowId); 112 contents.push({ 113 isDetached: !item.classList.contains("av__celltext--ref"), 114 block: { 115 content: item.textContent, 116 id: item.dataset.id, 117 }, 118 type: "block" 119 }); 120 }); 121 cellValue.relation = { 122 blockIDs, 123 contents 124 }; 125 } else if (colType === "mAsset") { 126 const mAsset: IAVCellAssetValue[] = []; 127 Array.from(cellElement.children).forEach((item) => { 128 if (!item.classList.contains("av__celltext--url") && !item.classList.contains("av__cellassetimg")) { 129 return; 130 } 131 const isImg = item.classList.contains("av__cellassetimg"); 132 mAsset.push({ 133 type: isImg ? "image" : "file", 134 content: isImg ? removeCompressURL(item.getAttribute("src")) : item.getAttribute("data-url"), 135 name: isImg ? "" : item.getAttribute("data-name") 136 }); 137 }); 138 cellValue.mAsset = mAsset; 139 } 140 if (colType === "block") { 141 cellValue.isDetached = cellElement.dataset.detached === "true"; 142 } 143 return cellValue; 144}; 145 146const getCellValueContent = (value: IAVCellValue): string => { 147 if (["number", "text", "block", "url", "phone", "email", "template", "mAsset"].includes(value.type)) { 148 return value[value.type as "text"].content; 149 } 150 if (["mSelect", "select"].includes(value.type)) { 151 return value.mSelect[0].content; 152 } 153 if (value.type === "rollup") { 154 return getCellValueContent(value.relation.contents[0]); 155 } 156 if (value.type === "checkbox") { 157 return value.checkbox.checked ? "true" : "false"; 158 } 159 if (value.type === "relation") { 160 return getCellValueContent(value.relation.contents[0]); 161 } 162 if (["date", "created", "updated"].includes(value.type)) { 163 return dayjs(value[value.type as "date"].content).format("YYYY-MM-DD HH:mm"); 164 } 165 if (value.type === "lineNumber") { 166 return ""; 167 } 168}; 169 170const transformCellValue = (colType: TAVCol, value: IAVCellValue): IAVCellValue => { 171 if (colType === value.type) { 172 return value; 173 } 174 const newValue: IAVCellValue = { 175 type: colType, 176 }; 177 if (colType === "number") { 178 if (["date", "created", "updated"].includes(colType)) { 179 newValue.number = { 180 content: value[value.type as "date"].content, 181 isNotEmpty: value[value.type as "date"].isNotEmpty 182 }; 183 } else { 184 newValue.number = { 185 content: parseFloat(getCellValueContent(value)) || 0, 186 isNotEmpty: true 187 }; 188 } 189 } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { 190 newValue[colType as "text"] = { 191 content: getCellValueContent(value).toString() 192 }; 193 } else if (colType === "mSelect" || colType === "select") { 194 newValue.mSelect = [{ 195 content: getCellValueContent(value).toString(), 196 color: "1" 197 }]; 198 if (!newValue.mSelect[0].content) { 199 newValue.mSelect = []; 200 } 201 } else if (colType === "rollup") { 202 newValue.rollup = {contents: [value]}; 203 } else if (colType === "checkbox") { 204 newValue.checkbox = { 205 checked: true 206 }; 207 } else if (colType === "relation") { 208 if (value.type === "block") { 209 newValue.relation = { 210 blockIDs: [value.blockID], 211 contents: [value] 212 }; 213 } else { 214 newValue.relation = {blockIDs: [], contents: []}; 215 } 216 } else if (colType === "mAsset") { 217 const content = getCellValueContent(value).toString(); 218 newValue.mAsset = [{ 219 type: Constants.SIYUAN_ASSETS_IMAGE.includes(pathPosix().extname(content).toLowerCase()) ? "image" : "file", 220 content, 221 name: "", 222 }]; 223 } else if (["date", "created", "updated"].includes(colType)) { 224 if (["date", "created", "updated"].includes(value.type)) { 225 newValue[colType as "date"] = JSON.parse(JSON.stringify(value[value.type as "date"])); 226 } else { 227 newValue[colType as "date"] = { 228 content: null, 229 isNotEmpty: false, 230 content2: null, 231 isNotEmpty2: false, 232 hasEndDate: false, 233 isNotTime: true, 234 }; 235 } 236 } else if (colType === "lineNumber") { 237 return { 238 type: "lineNumber" 239 }; 240 } 241 return newValue; 242}; 243 244export const genCellValue = (colType: TAVCol, value: string | any) => { 245 let cellValue: IAVCellValue = { 246 type: colType, 247 [colType === "select" ? "mSelect" : colType]: value as IAVCellDateValue 248 }; 249 if (typeof value === "string" && value) { 250 if (colType === "number") { 251 cellValue = { 252 type: colType, 253 number: { 254 content: parseFloat(value) || 0, 255 isNotEmpty: true 256 } 257 }; 258 } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { 259 cellValue = { 260 type: colType, 261 [colType]: { 262 content: value 263 } 264 }; 265 } else if (colType === "mSelect" || colType === "select") { 266 cellValue = { 267 type: colType, 268 mSelect: [{ 269 content: value, 270 color: "1" 271 }] 272 }; 273 } else if (colType === "checkbox") { 274 cellValue = { 275 type: colType, 276 checkbox: { 277 checked: true 278 } 279 }; 280 } else if (colType === "date") { 281 let values = value.split("→"); 282 if (values.length !== 2) { 283 values = value.split("-"); 284 if (values.length !== 2) { 285 values = value.split("~"); 286 } 287 } 288 const dateObj1 = dayjs(values[0]); 289 const dateObj2 = dayjs(values[1] || ""); 290 if (isNaN(dateObj1.valueOf())) { 291 cellValue = { 292 type: colType, 293 date: { 294 content: null, 295 isNotEmpty: false, 296 content2: null, 297 isNotEmpty2: false, 298 formattedContent: "", 299 hasEndDate: false, 300 isNotTime: true, 301 } 302 }; 303 } else { 304 cellValue = { 305 type: colType, 306 date: { 307 content: dateObj1.valueOf(), 308 isNotEmpty: true, 309 content2: dateObj2.valueOf() || 0, 310 isNotEmpty2: !isNaN(dateObj2.valueOf()), 311 hasEndDate: !isNaN(dateObj2.valueOf()), 312 isNotTime: dateObj1.hour() === 0 && values[0].split(":").length === 1, 313 formattedContent: "", 314 } 315 }; 316 } 317 } else if (colType === "relation") { 318 cellValue = { 319 type: colType, 320 relation: {blockIDs: [value], contents: []} 321 }; 322 } else if (colType === "mAsset") { 323 const type = pathPosix().extname(value).toLowerCase(); 324 cellValue = { 325 type: colType, 326 mAsset: [{ 327 type: Constants.SIYUAN_ASSETS_IMAGE.includes(type) ? "image" : "file", 328 content: value, 329 name: "", 330 }] 331 }; 332 } 333 } else if (typeof value === "undefined" || !value) { 334 if (colType === "number") { 335 cellValue = { 336 type: colType, 337 number: { 338 content: 0, 339 isNotEmpty: false 340 } 341 }; 342 } else if (["text", "block", "url", "phone", "email", "template"].includes(colType)) { 343 cellValue = { 344 type: colType, 345 [colType]: { 346 content: "" 347 } 348 }; 349 } else if (colType === "mSelect" || colType === "select" || colType === "mAsset") { 350 cellValue = { 351 type: colType, 352 [colType === "select" ? "mSelect" : colType]: [] 353 }; 354 } else if (["date", "created", "updated"].includes(colType)) { 355 cellValue = { 356 type: colType, 357 [colType]: { 358 content: null, 359 isNotEmpty: false, 360 content2: null, 361 isNotEmpty2: false, 362 hasEndDate: false, 363 isNotTime: true, 364 } 365 }; 366 } else if (colType === "checkbox") { 367 cellValue = { 368 type: colType, 369 checkbox: { 370 checked: false 371 } 372 }; 373 } else if (colType === "relation") { 374 cellValue = { 375 type: colType, 376 relation: {blockIDs: [], contents: []} 377 }; 378 } else if (colType === "rollup") { 379 cellValue = { 380 type: colType, 381 rollup: {contents: []} 382 }; 383 } 384 } 385 if (colType === "block") { 386 if (typeof value === "object" && value && value.id) { 387 cellValue.isDetached = false; 388 } else { 389 cellValue.isDetached = true; 390 } 391 } 392 return cellValue; 393}; 394 395export const cellScrollIntoView = (blockElement: HTMLElement, cellElement: Element, onlyHeight = true) => { 396 const cellRect = cellElement.getBoundingClientRect(); 397 if (!onlyHeight) { 398 const avScrollElement = blockElement.querySelector(".av__scroll"); 399 const rowElement = hasClosestByClassName(cellElement, "av__row"); 400 if (avScrollElement && rowElement) { 401 const stickyElement = rowElement.querySelector(".av__colsticky"); 402 if (!stickyElement.contains(cellElement)) { // https://github.com/siyuan-note/siyuan/issues/12162 403 const stickyRight = stickyElement.getBoundingClientRect().right; 404 const avScrollRect = avScrollElement.getBoundingClientRect(); 405 if (stickyRight > cellRect.left || avScrollRect.right < cellRect.left) { 406 avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - stickyRight; 407 } else if (stickyRight < cellRect.left && avScrollRect.right < cellRect.right) { 408 if (cellRect.width + stickyRight > avScrollRect.right) { 409 avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - stickyRight; 410 } else { 411 avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.right - avScrollRect.right; 412 } 413 } 414 } 415 } 416 } 417 /// #if MOBILE 418 const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); 419 if (contentElement && cellElement.getAttribute("data-dtype") !== "checkbox") { 420 const keyboardToolbarElement = document.getElementById("keyboardToolbar"); 421 const keyboardH = parseInt(keyboardToolbarElement.getAttribute("data-keyboardheight")) || (window.outerHeight / 2 - 42); 422 if (cellRect.bottom > window.innerHeight - keyboardH - 42) { 423 contentElement.scrollTop += cellRect.bottom - window.innerHeight + 42 + keyboardH; 424 } else if (cellRect.top < 110) { 425 contentElement.scrollTop -= 110 - cellRect.top; 426 } 427 } 428 /// #else 429 if (!blockElement.querySelector(".av__header")) { 430 // 属性面板 431 return; 432 } 433 const bodyElement = hasClosestByClassName(cellElement, "av__body"); 434 if (!bodyElement) { 435 return; 436 } 437 const avHeaderRect = bodyElement.querySelector(".av__row--header").getBoundingClientRect(); 438 if (avHeaderRect.bottom > cellRect.top) { 439 const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); 440 if (contentElement) { 441 contentElement.scrollTop = contentElement.scrollTop + cellRect.top - avHeaderRect.bottom; 442 } 443 } else { 444 const footerElement = bodyElement.querySelector(".av__row--footer"); 445 if (footerElement?.querySelector(".av__calc--ashow")) { 446 const avFooterRect = footerElement.getBoundingClientRect(); 447 if (avFooterRect.top < cellRect.bottom) { 448 const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); 449 if (contentElement) { 450 contentElement.scrollTop = contentElement.scrollTop + cellRect.bottom - avFooterRect.top; 451 } 452 } 453 } else { 454 const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); 455 if (contentElement) { 456 const contentRect = contentElement.getBoundingClientRect(); 457 if (cellRect.bottom > contentRect.bottom) { 458 contentElement.scrollTop = contentElement.scrollTop + (cellRect.bottom - contentRect.bottom); 459 } 460 } 461 } 462 } 463 /// #endif 464}; 465 466export const getTypeByCellElement = (cellElement: Element) => { 467 if (cellElement.parentElement.classList.contains("av__gallery-field")) { 468 return cellElement.getAttribute("data-dtype") as TAVCol; 469 } 470 const scrollElement = hasClosestByClassName(cellElement, "av__scroll"); 471 if (!scrollElement) { 472 return; 473 } 474 return scrollElement.querySelector(".av__row--header").querySelector(`[data-col-id="${cellElement.getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol; 475}; 476 477export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type?: TAVCol) => { 478 if (cellElements.length === 0 || (cellElements.length === 1 && !cellElements[0])) { 479 return; 480 } 481 if (!type) { 482 type = getTypeByCellElement(cellElements[0]); 483 } 484 if (type === "updated" || type === "created" || document.querySelector(".av__mask")) { 485 return; 486 } 487 const blockElement = hasClosestBlock(cellElements[0]); 488 if (!blockElement) { 489 return; 490 } 491 const viewType = blockElement.getAttribute("data-av-type") as TAVView; 492 let cellRect = cellElements[0].getBoundingClientRect(); 493 const contentElement = hasClosestByClassName(blockElement, "protyle-content", true); 494 if (viewType === "table") { 495 cellScrollIntoView(blockElement, cellElements[0], false); 496 } 497 cellRect = cellElements[0].getBoundingClientRect(); 498 let html = ""; 499 let height = cellRect.height; 500 const cssStyle = getComputedStyle(cellElements[0]); 501 let style = `font-family:${cssStyle.fontFamily};font-size:${cssStyle.fontSize};line-height:${cssStyle.lineHeight};padding:${cssStyle.padding};position:absolute;top: ${cellRect.top}px;`; 502 if (contentElement) { 503 const contentRect = contentElement.getBoundingClientRect(); 504 if (cellRect.bottom > contentRect.bottom) { 505 height = contentRect.bottom - cellRect.top; 506 } 507 const width = Math.min(Math.max(cellRect.width, 25), contentRect.width); 508 style = `style='height: ${height}px;width:${width}px;left: ${(cellRect.left < contentRect.left || cellRect.left + width > contentRect.right) ? contentRect.left : cellRect.left}px;${style}'`; 509 } else { 510 style = `style='height: ${height}px;width:${Math.max(cellRect.width, 25)}px;left: ${cellRect.left}px;${style}'`; 511 } 512 513 if (["text", "email", "phone", "block", "template"].includes(type)) { 514 html = `<textarea ${style} spellcheck="false" class="b3-text-field"></textarea>`; 515 } else if (type === "url") { 516 html = `<textarea ${style} spellcheck="false" class="b3-text-field">${cellElements[0].firstElementChild.getAttribute("data-href")}</textarea>`; 517 } else if (type === "number") { 518 html = `<input type="number" spellcheck="false" value="${cellElements[0].firstElementChild.getAttribute("data-content")}" ${style} class="b3-text-field">`; 519 } else { 520 if (["select", "mSelect"].includes(type)) { 521 if (blockElement.getAttribute("data-rendering") === "true") { 522 return; 523 } 524 openMenuPanel({protyle, blockElement, type: "select", cellElements}); 525 } else if (type === "mAsset") { 526 openMenuPanel({protyle, blockElement, type: "asset", cellElements}); 527 focusBlock(blockElement); 528 } else if (type === "date") { 529 openMenuPanel({protyle, blockElement, type: "date", cellElements}); 530 } else if (type === "checkbox") { 531 updateCellValueByInput(protyle, type, blockElement, cellElements); 532 } else if (type === "relation") { 533 openMenuPanel({protyle, blockElement, type: "relation", cellElements}); 534 } else if (type === "rollup") { 535 openMenuPanel({ 536 protyle, 537 blockElement, 538 type: "rollup", 539 cellElements, 540 colId: getColId(cellElements[0], viewType) 541 }); 542 } 543 if (viewType === "table" && !hasClosestByClassName(cellElements[0], "custom-attr")) { 544 cellElements[0].classList.add("av__cell--select"); 545 addDragFill(cellElements[0]); 546 } 547 return; 548 } 549 window.siyuan.menus.menu.remove(); 550 document.body.insertAdjacentHTML("beforeend", `<div class="av__mask" style="z-index: ${++window.siyuan.zIndex}"> 551 ${html} 552</div>`); 553 const avMaskElement = document.querySelector(".av__mask"); 554 const inputElement = avMaskElement.querySelector(".b3-text-field") as HTMLInputElement; 555 if (inputElement) { 556 if (["text", "email", "phone", "block", "template"].includes(type)) { 557 inputElement.value = cellElements[0].querySelector(".av__celltext")?.textContent || ""; 558 } 559 inputElement.select(); 560 inputElement.focus(); 561 if (type === "template") { 562 fetchPost("/api/av/renderAttributeView", { 563 id: blockElement.dataset.avId, 564 viewID: blockElement.getAttribute(Constants.CUSTOM_SY_AV_VIEW) 565 }, (response) => { 566 getFieldsByData(response.data).find((item: IAVColumn) => { 567 if (item.id === getColId(cellElements[0], viewType)) { 568 inputElement.value = item.template; 569 inputElement.dataset.template = item.template; 570 return true; 571 } 572 }); 573 }); 574 } 575 if (type === "block") { 576 inputElement.addEventListener("input", (event: InputEvent) => { 577 if (Constants.BLOCK_HINT_KEYS.includes(inputElement.value.substring(0, 2))) { 578 protyle.toolbar.range = document.createRange(); 579 if (cellElements[0] && !blockElement.contains(cellElements[0])) { 580 const rowID = getFieldIdByCellElement(cellElements[0], viewType); 581 if (viewType === "table") { 582 cellElements[0] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${cellElements[0].dataset.colId}"]`)) as HTMLElement; 583 } else { 584 cellElements[0] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${cellElements[0].dataset.fieldId}"]`)) as HTMLElement; 585 } 586 } 587 protyle.toolbar.range.selectNodeContents(cellElements[0].lastChild); 588 focusByRange(protyle.toolbar.range); 589 if (viewType === "table") { 590 cellElements[0].classList.add("av__cell--select"); 591 addDragFill(cellElements[0]); 592 } 593 let textPlain = inputElement.value; 594 if (isDynamicRef(textPlain)) { 595 textPlain = textPlain.substring(2, 22 + 2); 596 } else { 597 textPlain = textPlain.substring(2); 598 } 599 hintRef(textPlain, protyle, "av"); 600 avMaskElement?.remove(); 601 event.preventDefault(); 602 event.stopPropagation(); 603 } 604 }); 605 } 606 inputElement.addEventListener("keydown", (event) => { 607 if (event.isComposing) { 608 return; 609 } 610 if (electronUndo(event)) { 611 return; 612 } 613 if (event.key === "Escape" || event.key === "Tab" || 614 (event.key === "Enter" && !event.shiftKey && isNotCtrl(event))) { 615 updateCellValueByInput(protyle, type, blockElement, cellElements); 616 if (event.key === "Tab") { 617 protyle.wysiwyg.element.dispatchEvent(new KeyboardEvent("keydown", { 618 shiftKey: event.shiftKey, 619 ctrlKey: event.ctrlKey, 620 altKey: event.altKey, 621 metaKey: event.metaKey, 622 key: "Tab", 623 keyCode: 9 624 })); 625 } 626 event.preventDefault(); 627 event.stopPropagation(); 628 } 629 }); 630 } 631 632 const removeAvMask = (event: Event) => { 633 if ((event.target as HTMLElement).classList.contains("av__mask") 634 && document.activeElement.tagName !== "TEXTAREA" && document.activeElement.tagName !== "INPUT") { 635 updateCellValueByInput(protyle, type, blockElement, cellElements); 636 avMaskElement?.remove(); 637 } 638 }; 639 avMaskElement.addEventListener("click", (event) => { 640 removeAvMask(event); 641 }); 642 avMaskElement.addEventListener("contextmenu", (event) => { 643 removeAvMask(event); 644 }); 645 avMaskElement.addEventListener("mousedown", (event: MouseEvent & { target: HTMLElement }) => { 646 if (event.button === 1) { 647 if (event.target.classList.contains("av__mask") && document.activeElement && document.activeElement.nodeType === 1) { 648 (document.activeElement as HTMLElement).blur(); 649 } 650 removeAvMask(event); 651 } 652 }); 653}; 654 655const updateCellValueByInput = (protyle: IProtyle, type: TAVCol, blockElement: HTMLElement, cellElements: HTMLElement[]) => { 656 const viewType = blockElement.getAttribute("data-av-type") as TAVView; 657 if (viewType === "table") { 658 const rowElement = hasClosestByClassName(cellElements[0], "av__row"); 659 if (!rowElement) { 660 return; 661 } 662 if (cellElements.length === 1 && cellElements[0].dataset.detached === "true" && !rowElement.dataset.id) { 663 return; 664 } 665 } 666 const avMaskElement = document.querySelector(".av__mask"); 667 const avID = blockElement.getAttribute("data-av-id"); 668 if (type === "template") { 669 const colId = getColId(cellElements[0], viewType); 670 const textElement = avMaskElement.querySelector(".b3-text-field") as HTMLInputElement; 671 if (textElement.value !== textElement.dataset.template && !blockElement.getAttribute("data-loading")) { 672 transaction(protyle, [{ 673 action: "updateAttrViewColTemplate", 674 id: colId, 675 avID, 676 data: textElement.value, 677 type: "template", 678 }], [{ 679 action: "updateAttrViewColTemplate", 680 id: colId, 681 avID, 682 data: textElement.dataset.template, 683 type: "template", 684 }]); 685 blockElement.setAttribute("data-loading", "true"); 686 } 687 } else { 688 updateCellsValue(protyle, blockElement, type === "checkbox" ? { 689 checked: cellElements[0].querySelector("use").getAttribute("xlink:href") === "#iconUncheck" 690 } : (avMaskElement.querySelector(".b3-text-field") as HTMLInputElement).value, cellElements); 691 } 692 if (viewType === "table" && 693 // 兼容新增行后台隐藏 694 cellElements[0] && 695 !hasClosestByClassName(cellElements[0], "custom-attr")) { 696 cellElements[0].classList.add("av__cell--select"); 697 addDragFill(cellElements[0]); 698 } 699 // 单元格编辑中 ctrl+p 光标定位 700 if (!document.querySelector(".b3-dialog")) { 701 focusBlock(blockElement); 702 } 703 document.querySelectorAll(".av__mask").forEach((item) => { 704 item.remove(); 705 }); 706}; 707 708export const updateCellsValue = async (protyle: IProtyle, nodeElement: HTMLElement, value?: any, 709 cElements?: HTMLElement[], columns?: IAVColumn[], html?: string, getOperations = false) => { 710 const doOperations: IOperation[] = []; 711 const undoOperations: IOperation[] = []; 712 713 const avID = nodeElement.dataset.avId; 714 const id = nodeElement.dataset.nodeId; 715 let text = ""; 716 const json: IAVCellValue[][] = []; 717 let cellElements: Element[]; 718 if (cElements?.length > 0) { 719 cellElements = cElements; 720 } else { 721 cellElements = Array.from(nodeElement.querySelectorAll(".av__cell--active, .av__cell--select")); 722 if (cellElements.length === 0) { 723 nodeElement.querySelectorAll(".av__row--select:not(.av__row--header)").forEach(rowElement => { 724 rowElement.querySelectorAll(".av__cell").forEach(cellElement => { 725 cellElements.push(cellElement); 726 }); 727 }); 728 } 729 } 730 const isCustomAttr = hasClosestByClassName(cellElements[0], "custom-attr"); 731 const viewType = nodeElement.getAttribute("data-av-type") as TAVView; 732 for (let elementIndex = 0; elementIndex < cellElements.length; elementIndex++) { 733 let item = cellElements[elementIndex] as HTMLElement; 734 const rowID = getFieldIdByCellElement(item, viewType); 735 if (!rowID) { 736 break; 737 } 738 if (!nodeElement.contains(item)) { 739 if (viewType === "table") { 740 item = cellElements[elementIndex] = (nodeElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${item.dataset.colId}"]`) || 741 nodeElement.querySelector(`.fn__flex-1[data-col-id="${item.dataset.colId}"]`)) as HTMLElement; 742 } else { 743 item = cellElements[elementIndex] = (nodeElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${item.dataset.fieldId}"]`)) as HTMLElement; 744 } 745 } 746 747 if (!item) { 748 // 兼容新增行后台隐藏 749 break; 750 } 751 const type = getTypeByCellElement(item) || item.dataset.type as TAVCol; 752 if (["created", "updated", "template", "rollup"].includes(type)) { 753 break; 754 } 755 const cellId = item.dataset.id; // 刚创建时无 id,更新需和 oldValue 保持一致 756 const colId = getColId(item, viewType); 757 758 text += getCellText(item) + ((cellElements[elementIndex + 1] && item.nextElementSibling && item.nextElementSibling === cellElements[elementIndex + 1]) ? "\t" : "\n\n"); 759 const oldValue = genCellValueByElement(type, item); 760 if (elementIndex === 0 || cellElements[elementIndex - 1] !== item.previousElementSibling) { 761 json.push([]); 762 } 763 json[json.length - 1].push(oldValue); 764 let newValue = value; 765 // relation 为全部更新,以下类型为添加 766 if (type === "mAsset") { 767 if (Array.isArray(value)) { 768 newValue = oldValue.mAsset.concat(value); 769 } else if (typeof value !== "undefined" && typeof value !== "object") { // 不传入为删除,传入字符串不进行处理 770 let link = protyle.lute.GetLinkDest(value); 771 let name = ""; 772 let imgSrc = ""; 773 // https://github.com/siyuan-note/siyuan/issues/13892 774 if (!link && value.startsWith("assets/")) { 775 link = value; 776 name = getAssetName(value) + pathPosix().extname(value); 777 } 778 if (html) { 779 const tempElement = document.createElement("template"); 780 tempElement.innerHTML = html; 781 const aElement = tempElement.content.querySelector('[data-type~="a"]'); 782 if (aElement) { 783 link = aElement.getAttribute("data-href"); 784 name = aElement.textContent; 785 } else { 786 const imgElement = tempElement.content.querySelector(".img img"); 787 if (imgElement) { 788 imgSrc = imgElement.getAttribute("data-src"); 789 } 790 } 791 } 792 // https://github.com/siyuan-note/siyuan/issues/12308 793 if (!link) { 794 name = value; 795 } 796 if (!link && !name && !imgSrc) { 797 break; 798 } 799 if (imgSrc) { 800 // 支持解析 ![]() https://github.com/siyuan-note/siyuan/issues/11487 801 newValue = oldValue.mAsset.concat({ 802 type: "image", 803 content: imgSrc, 804 name: "" 805 }); 806 } else { 807 // 支持解析 https://github.com/siyuan-note/siyuan/issues/11463 808 newValue = oldValue.mAsset.concat({ 809 type: "file", 810 content: link, 811 name 812 }); 813 } 814 } 815 } else if (type === "mSelect" || type === "select") { 816 // 不传入为删除 817 if (typeof value === "string") { 818 const newMSelectValue: IAVCellSelectValue[] = []; 819 let colorIndex = oldValue.mSelect.length; 820 // 以逗号分隔,去重,去空,去换行后做为选项 821 [...new Set(value.split(",").map(v => v.trim().replace(/\n|\r\n|\r|\u2028|\u2029/g, "")))].forEach((item) => { 822 if (!item) { 823 return; 824 } 825 let hasSameContent = false; 826 oldValue.mSelect.find((mSelectItem) => { 827 if (mSelectItem.content === item) { 828 hasSameContent = true; 829 return true; 830 } 831 }); 832 if (hasSameContent) { 833 return; 834 } 835 colorIndex++; 836 newMSelectValue.push({ 837 content: item, 838 color: colorIndex.toString() 839 }); 840 }); 841 newValue = oldValue.mSelect.concat(newMSelectValue); 842 } 843 } else if (type === "block" && typeof value === "string" && oldValue.block.id) { 844 newValue = { 845 content: value, 846 id: oldValue.block.id, 847 }; 848 if (oldValue.block.icon) { 849 newValue.icon = oldValue.block.icon; 850 } 851 } 852 let cellValue: IAVCellValue; 853 if (typeof newValue === "object" && newValue.type) { 854 cellValue = transformCellValue(type, newValue); 855 } else { 856 cellValue = genCellValue(type, newValue); 857 } 858 cellValue.id = cellId; 859 if ((cellValue.type === "date" && typeof cellValue.date === "string") || 860 (cellValue.type === "relation" && typeof cellValue.relation === "string")) { 861 break; 862 } 863 if (columns && (type === "select" || type === "mSelect")) { 864 const operations = mergeAddOption(columns.find(e => e.id === colId), cellValue, avID); 865 doOperations.push(...operations.doOperations); 866 undoOperations.push(...operations.undoOperations); 867 } 868 // formattedContent 在单元格渲染时没有用到,需对比保持一致 869 if (type === "date") { 870 if (!(value && typeof value === "object" && typeof value.isNotTime === "boolean")) { 871 const response = await fetchSyncPost("/api/av/getAttributeViewKeysByID", {avID: avID, keyIDs: [colId]}); 872 if (response.data[0].date) { 873 cellValue.date.isNotTime = !response.data[0].date.fillSpecificTime; 874 } 875 } 876 cellValue.date.formattedContent = oldValue.date.formattedContent; 877 } 878 if (objEquals(cellValue, oldValue)) { 879 break; 880 } 881 882 doOperations.push({ 883 action: "updateAttrViewCell", 884 id: cellId, 885 avID, 886 keyID: colId, 887 rowID, 888 data: cellValue 889 }); 890 891 undoOperations.push({ 892 action: "updateAttrViewCell", 893 id: cellId, 894 avID, 895 keyID: colId, 896 rowID, 897 data: oldValue 898 }); 899 if (isCustomAttr) { 900 item.innerHTML = genAVValueHTML(cellValue); 901 } else { 902 updateAttrViewCellAnimation(item, cellValue); 903 } 904 } 905 if (getOperations) { 906 return {doOperations, undoOperations}; 907 } 908 if (doOperations.length > 0) { 909 doOperations.push({ 910 action: "doUpdateUpdated", 911 id, 912 data: dayjs().format("YYYYMMDDHHmmss"), 913 }); 914 undoOperations.push({ 915 action: "doUpdateUpdated", 916 id, 917 data: nodeElement.getAttribute("updated"), 918 }); 919 transaction(protyle, doOperations, undoOperations); 920 } 921 return {text: text.substring(0, text.length - 2), json}; 922}; 923 924export const renderCellAttr = (cellElement: Element, value: IAVCellValue) => { 925 if (value.type === "checkbox") { 926 if (value.checkbox.checked) { 927 cellElement.classList.add("av__cell-check"); 928 cellElement.classList.remove("av__cell-uncheck"); 929 } else { 930 cellElement.classList.remove("av__cell-check"); 931 cellElement.classList.add("av__cell-uncheck"); 932 } 933 } else if (value.type === "block") { 934 if (value.isDetached) { 935 cellElement.setAttribute("data-detached", "true"); 936 } else { 937 cellElement.querySelector(".av__celltext").setAttribute("data-id", value.block.id); 938 cellElement.removeAttribute("data-detached"); 939 } 940 } 941}; 942 943export const renderCell = (cellValue: IAVCellValue, rowIndex = 0, showIcon = true, type: TAVView = "table") => { 944 let text = ""; 945 if ("template" === cellValue.type) { 946 text = `<span class="av__celltext">${cellValue ? (cellValue.template.content || "") : ""}</span>`; 947 } else if ("text" === cellValue.type) { 948 text = `<span class="av__celltext">${cellValue ? Lute.EscapeHTMLStr(cellValue.text.content || "") : ""}</span>`; 949 } else if (["email", "phone"].includes(cellValue.type)) { 950 text = `<span class="av__celltext av__celltext--url" data-type="${cellValue.type}">${cellValue ? Lute.EscapeHTMLStr(cellValue[cellValue.type as "email"].content || "") : ""}</span>`; 951 } else if ("url" === cellValue.type) { 952 text = renderCellURL(cellValue?.url?.content || ""); 953 } else if (cellValue.type === "block") { 954 // 不可使用换行 https://github.com/siyuan-note/siyuan/issues/11365 955 if (cellValue?.isDetached) { 956 text = `<span class="av__celltext">${Lute.EscapeHTMLStr(cellValue.block.content || "")}</span><span class="b3-chip b3-chip--info b3-chip--small" data-type="block-more">${window.siyuan.languages.more}</span>`; 957 } else { 958 text = `<span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${cellValue.block.icon || ""}">${unicode2Emoji(cellValue.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${cellValue.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(cellValue.block.content)}</span><span class="b3-chip b3-chip--info b3-chip--small" data-type="block-more">${window.siyuan.languages.update}</span>`; 959 } 960 } else if (cellValue.type === "number") { 961 text = `<span class="av__celltext" data-content="${cellValue?.number.isNotEmpty ? cellValue?.number.content : ""}">${cellValue?.number.formattedContent || cellValue?.number.content || ""}</span>`; 962 } else if (cellValue.type === "mSelect" || cellValue.type === "select") { 963 cellValue?.mSelect?.forEach((item, index) => { 964 if (cellValue.type === "select" && index > 0) { 965 return; 966 } 967 text += `<span class="b3-chip" style="background-color:var(--b3-font-background${item.color});color:var(--b3-font-color${item.color})">${escapeHtml(item.content)}</span>`; 968 }); 969 } else if (cellValue.type === "date") { 970 const dataValue = cellValue ? cellValue.date : null; 971 text = `<span class="av__celltext" data-value='${JSON.stringify(dataValue)}'>`; 972 if (dataValue && dataValue.isNotEmpty) { 973 text += dayjs(dataValue.content).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"); 974 } 975 if (dataValue && dataValue.hasEndDate && dataValue.isNotEmpty && dataValue.isNotEmpty2) { 976 text += `<svg class="av__cellicon"><use xlink:href="#iconForward"></use></svg>${dayjs(dataValue.content2).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm")}`; 977 } 978 text += "</span>"; 979 } else if (["created", "updated"].includes(cellValue.type)) { 980 const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null; 981 text = `<span class="av__celltext" data-value='${JSON.stringify(dataValue)}'>`; 982 if (dataValue && dataValue.isNotEmpty) { 983 text += dayjs(dataValue.content).format("YYYY-MM-DD HH:mm"); 984 } 985 text += "</span>"; 986 } else if (["lineNumber"].includes(cellValue.type)) { 987 // 渲染行号 988 text = `<span class="av__celltext" data-value='${rowIndex + 1}'>${rowIndex + 1}</span>`; 989 } else if (cellValue.type === "mAsset") { 990 cellValue?.mAsset?.forEach((item) => { 991 if (item.type === "image") { 992 text += `<img loading="lazy" class="av__cellassetimg ariaLabel" aria-label="${item.content}" src="${getCompressURL(item.content)}">`; 993 } else { 994 text += `<span class="b3-chip av__celltext--url ariaLabel" aria-label="${escapeAttr(item.content)}" data-name="${escapeAttr(item.name)}" data-url="${escapeAttr(item.content)}">${item.name || item.content}</span>`; 995 } 996 }); 997 } else if (cellValue.type === "checkbox") { 998 text += `<div class="fn__flex"><svg class="av__checkbox"><use xlink:href="#icon${cellValue?.checkbox?.checked ? "Check" : "Uncheck"}"></use></svg>`; 999 if (type === "gallery" && cellValue?.checkbox?.content) { 1000 text += `<span class="fn__space"></span>${cellValue?.checkbox?.content}`; 1001 } 1002 text += "</div>"; 1003 } else if (cellValue.type === "rollup") { 1004 let rollupType; 1005 cellValue?.rollup?.contents?.forEach((item) => { 1006 const rollupText = ["template", "select", "mSelect", "mAsset", "relation"].includes(item.type) ? renderCell(item, rowIndex, showIcon, type) : renderRollup(item, showIcon); 1007 if (rollupText) { 1008 text += rollupText + (item.type === "checkbox" ? "" : ", "); 1009 } 1010 rollupType = item.type; 1011 }); 1012 if (text) { 1013 if (rollupType === "checkbox") { 1014 text = `<div class="fn__flex">${text}</div>`; 1015 } else if (text.endsWith(", ")) { 1016 text = text.substring(0, text.length - 2); 1017 } 1018 } 1019 } else if (cellValue.type === "relation") { 1020 cellValue?.relation?.contents?.forEach((item, index) => { 1021 if (item && item.block) { 1022 const rowID = cellValue.relation.blockIDs[index]; 1023 if (item?.isDetached) { 1024 text += `<span data-row-id="${rowID}" class="av__cell--relation"><span class="${showIcon ? "" : " fn__none"}">➖<span class="fn__space--5"></span></span><span class="av__celltext">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; 1025 } else { 1026 // data-block-id 用于更新 emoji 1027 text += `<span data-row-id="${rowID}" class="av__cell--relation" data-block-id="${item.block.id}"><span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${item.block.icon || ""}">${unicode2Emoji(item.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${item.block.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(item.block.content || window.siyuan.languages.untitled)}</span></span>`; 1028 } 1029 } 1030 }); 1031 if (text && text.endsWith(", ")) { 1032 text = text.substring(0, text.length - 2); 1033 } 1034 } 1035 1036 if ((["text", "template", "url", "email", "phone", "date", "created", "updated"].includes(cellValue.type) && cellValue[cellValue.type as "url"]?.content) || 1037 cellValue.type === "lineNumber" || 1038 (cellValue.type === "number" && cellValue.number?.isNotEmpty) || 1039 (cellValue.type === "block" && cellValue.block?.content)) { 1040 text += `<span ${cellValue.type !== "number" ? "" : 'style="right:auto;left:5px"'} data-type="copy" class="block__icon"><svg><use xlink:href="#iconCopy"></use></svg></span>`; 1041 } 1042 return text; 1043}; 1044 1045const renderRollup = (cellValue: IAVCellValue, showIcon: boolean) => { 1046 let text = ""; 1047 if (["text"].includes(cellValue.type)) { 1048 text = cellValue ? (cellValue[cellValue.type as "text"].content || "") : ""; 1049 } else if (["email", "phone"].includes(cellValue.type)) { 1050 const emailContent = cellValue ? cellValue[cellValue.type as "email"].content : ""; 1051 if (emailContent) { 1052 text = `<span class="av__celltext av__celltext--url" data-type="${cellValue.type}">${emailContent}</span>`; 1053 } 1054 } else if ("url" === cellValue.type) { 1055 const urlContent = cellValue?.url?.content || ""; 1056 if (urlContent) { 1057 text = renderCellURL(urlContent); 1058 } 1059 } else if (cellValue.type === "block") { 1060 if (cellValue?.isDetached) { 1061 text = `<span class="av__celltext">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`; 1062 } else { 1063 text = `<span class="b3-menu__avemoji${showIcon ? "" : " fn__none"}" data-unicode="${cellValue.block.icon || ""}">${unicode2Emoji(cellValue.block.icon || window.siyuan.storage[Constants.LOCAL_IMAGES].file)}</span><span data-type="block-ref" data-id="${cellValue.block?.id}" data-subtype="s" class="av__celltext av__celltext--ref">${Lute.EscapeHTMLStr(cellValue.block?.content || window.siyuan.languages.untitled)}</span>`; 1064 } 1065 } else if (cellValue.type === "number") { 1066 text = cellValue?.number.formattedContent || cellValue?.number.content.toString() || ""; 1067 } else if (cellValue.type === "checkbox") { 1068 text += `<svg class="av__checkbox"><use xlink:href="#icon${cellValue?.checkbox?.checked ? "Check" : "Uncheck"}"></use></svg><span class="fn__space"></span>`; 1069 } else if (["date", "updated", "created"].includes(cellValue.type)) { 1070 const dataValue = cellValue ? cellValue[cellValue.type as "date"] : null; 1071 if (dataValue.formattedContent) { 1072 text = dataValue.formattedContent; 1073 } else { 1074 if (dataValue && dataValue.isNotEmpty) { 1075 text = dayjs(dataValue.content).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm"); 1076 } 1077 if (dataValue && dataValue.hasEndDate && dataValue.isNotEmpty && dataValue.isNotEmpty2) { 1078 text = `<svg class="av__cellicon"><use xlink:href="#iconForward"></use></svg>${dayjs(dataValue.content2).format(dataValue.isNotTime ? "YYYY-MM-DD" : "YYYY-MM-DD HH:mm")}`; 1079 } 1080 } 1081 if (text) { 1082 text = `<span class="av__celltext">${text}</span>`; 1083 } 1084 } 1085 return text; 1086}; 1087 1088export const updateHeaderCell = (cellElement: HTMLElement, headerValue: { 1089 icon?: string, 1090 name?: string, 1091 pin?: boolean, 1092}) => { 1093 if (typeof headerValue.icon !== "undefined") { 1094 cellElement.dataset.icon = headerValue.icon; 1095 cellElement.querySelector(".av__cellheadericon").outerHTML = headerValue.icon ? unicode2Emoji(headerValue.icon, "av__cellheadericon", true) : `<svg class="av__cellheadericon"><use xlink:href="#${getColIconByType(cellElement.dataset.dtype as TAVCol)}"></use></svg>`; 1096 } 1097 if (typeof headerValue.name !== "undefined") { 1098 cellElement.querySelector(".av__celltext").textContent = headerValue.name; 1099 } 1100 if (typeof headerValue.pin !== "undefined") { 1101 const textElement = cellElement.querySelector(".av__celltext"); 1102 if (headerValue.pin) { 1103 if (!cellElement.querySelector(".av__cellheadericon--pin")) { 1104 textElement.insertAdjacentHTML("afterend", '<svg class="av__cellheadericon av__cellheadericon--pin"><use xlink:href="#iconPin"></use></svg>'); 1105 } 1106 } else { 1107 cellElement.querySelector(".av__cellheadericon--pin")?.remove(); 1108 } 1109 } 1110}; 1111 1112export const getPositionByCellElement = (cellElement: HTMLElement) => { 1113 let rowElement = hasClosestByClassName(cellElement, "av__row"); 1114 if (!rowElement) { 1115 return; 1116 } 1117 let rowIndex = -1; 1118 while (rowElement) { 1119 rowElement = rowElement.previousElementSibling as HTMLElement; 1120 rowIndex++; 1121 } 1122 let celIndex = -2; 1123 while (cellElement) { 1124 cellElement = cellElement.previousElementSibling as HTMLElement; 1125 if (cellElement && cellElement.classList.contains("av__colsticky")) { 1126 cellElement = cellElement.lastElementChild as HTMLElement; 1127 } 1128 celIndex++; 1129 } 1130 return {rowIndex, celIndex}; 1131}; 1132 1133export const dragFillCellsValue = (protyle: IProtyle, nodeElement: HTMLElement, originData: { 1134 [key: string]: IAVCellValue[] 1135}, originCellIds: string[], activeElement: Element) => { 1136 nodeElement.querySelector(".av__drag-fill")?.remove(); 1137 const newData: { [key: string]: Array<IAVCellValue & { colId?: string, element?: HTMLElement }> } = {}; 1138 nodeElement.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => { 1139 if (originCellIds.includes(item.dataset.id)) { 1140 return; 1141 } 1142 const rowElement = hasClosestByClassName(item, "av__row"); 1143 if (!rowElement) { 1144 return; 1145 } 1146 if (!newData[rowElement.dataset.id]) { 1147 newData[rowElement.dataset.id] = []; 1148 } 1149 const value: IAVCellValue & { 1150 colId?: string, 1151 element?: HTMLElement 1152 } = genCellValueByElement(getTypeByCellElement(item), item); 1153 value.colId = item.dataset.colId; 1154 value.element = item; 1155 newData[rowElement.dataset.id].push(value); 1156 }); 1157 const doOperations: IOperation[] = []; 1158 const undoOperations: IOperation[] = []; 1159 const avID = nodeElement.dataset.avId; 1160 const originKeys = Object.keys(originData); 1161 const showIcon = activeElement.querySelector(".b3-menu__avemoji") ? true : false; 1162 Object.keys(newData).forEach((rowID, index) => { 1163 newData[rowID].forEach((item, cellIndex) => { 1164 if (["rollup", "template", "created", "updated"].includes(item.type) || 1165 (item.type === "block" && item.element.getAttribute("data-detached") !== "true")) { 1166 return; 1167 } 1168 // https://ld246.com/article/1707975507571 数据库下拉填充数据后异常 1169 const data = JSON.parse(JSON.stringify(originData[originKeys[index % originKeys.length]][cellIndex])); 1170 data.id = item.id; 1171 const keyID = item.colId; 1172 if (data.type === "block") { 1173 data.isDetached = true; 1174 delete data.block.id; 1175 } 1176 doOperations.push({ 1177 action: "updateAttrViewCell", 1178 id: item.id, 1179 avID, 1180 keyID, 1181 rowID, 1182 data 1183 }); 1184 item.element.innerHTML = renderCell(data, 0, showIcon); 1185 renderCellAttr(item.element, data); 1186 delete item.colId; 1187 delete item.element; 1188 undoOperations.push({ 1189 action: "updateAttrViewCell", 1190 id: item.id, 1191 avID, 1192 keyID, 1193 rowID, 1194 data: item 1195 }); 1196 }); 1197 }); 1198 focusBlock(nodeElement); 1199 if (doOperations.length > 0) { 1200 transaction(protyle, doOperations, undoOperations); 1201 } 1202}; 1203 1204export const addDragFill = (cellElement: Element) => { 1205 if (!cellElement) { 1206 return; 1207 } 1208 cellElement.classList.add("av__cell--active"); 1209 if (!cellElement.querySelector(".av__drag-fill")) { 1210 const cellType = cellElement.getAttribute("data-dtype") as TAVCol; 1211 if (["template", "rollup", "lineNumber", "created", "updated"].includes(cellType)) { 1212 return; 1213 } 1214 cellElement.insertAdjacentHTML("beforeend", `<div aria-label="${window.siyuan.languages.dragFill}" class="av__drag-fill ariaLabel"></div>`); 1215 } 1216}; 1217 1218export const cellValueIsEmpty = (value: IAVCellValue) => { 1219 if (value.type === "checkbox") { 1220 return false; 1221 } 1222 if (["text", "block", "url", "phone", "email", "template"].includes(value.type)) { 1223 return !value[value.type as "text"]?.content; 1224 } 1225 if (value.type === "number") { 1226 return !value.number?.isNotEmpty; 1227 } 1228 if (["mSelect", "mAsset", "select"].includes(value.type)) { 1229 if (value[(value.type === "select" ? "mSelect" : value.type) as "mSelect"]?.length > 0) { 1230 return false; 1231 } 1232 return true; 1233 } 1234 if (["date", "created", "updated"].includes(value.type)) { 1235 return !value[value.type as "date"]?.isNotEmpty && 1236 !value[value.type as "date"]?.isNotEmpty2; 1237 } 1238 if (value.type === "relation") { 1239 if (value.relation?.blockIDs && value.relation.blockIDs.length > 0) { 1240 return false; 1241 } 1242 return true; 1243 } 1244 if (value.type === "rollup") { 1245 if (value.rollup?.contents && value.rollup.contents.length > 0) { 1246 return false; 1247 } 1248 return true; 1249 } 1250};