A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 816 lines 43 kB view raw
1import {fetchPost} from "../../../util/fetch"; 2import {getColIconByType} from "./col"; 3import {Constants} from "../../../constants"; 4import {addDragFill, cellScrollIntoView, popTextCell, renderCell} from "./cell"; 5import {unicode2Emoji} from "../../../emoji"; 6import {focusBlock} from "../../util/selection"; 7import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../../util/hasClosest"; 8import {stickyRow, updateHeader} from "./row"; 9import {getCalcValue} from "./calc"; 10import {renderAVAttribute} from "./blockAttr"; 11import {addClearButton} from "../../../util/addClearButton"; 12import {escapeAriaLabel, escapeAttr, escapeHtml} from "../../../util/escape"; 13import {electronUndo} from "../../undo"; 14import {isInAndroid, isInHarmony, isInIOS} from "../../util/compatibility"; 15import {isMobile} from "../../../util/functions"; 16import {renderGallery} from "./gallery/render"; 17import {getFieldsByData, getViewIcon} from "./view"; 18import {openMenuPanel} from "./openMenuPanel"; 19import {getPageSize} from "./groups"; 20import {clearSelect} from "../../util/clearSelect"; 21import {showMessage} from "../../../dialog/message"; 22 23interface IIds { 24 groupId: string, 25 rowId: string, 26 colId?: string 27} 28 29interface ITableOptions { 30 protyle: IProtyle, 31 blockElement: HTMLElement, 32 cb: (data: IAV) => void, 33 data: IAV, 34 renderAll: boolean, 35 resetData: { 36 left: number, 37 alignSelf: string, 38 headerTransform: string, 39 footerTransform: string, 40 isSearching: boolean, 41 selectCellId: IIds, 42 selectRowIds: IIds[], 43 dragFillId: IIds, 44 activeIds: IIds[], 45 query: string, 46 pageSizes: { [key: string]: string }, 47 } 48} 49 50export const genTabHeaderHTML = (data: IAV, showSearch: boolean, editable: boolean) => { 51 let tabHTML = ""; 52 let viewData: IAVView; 53 let hasFilter = false; 54 getFieldsByData(data).forEach((item) => { 55 if (!hasFilter) { 56 data.view.filters.find(filterItem => { 57 if (filterItem.value.type === item.type && item.id === filterItem.column) { 58 hasFilter = true; 59 return true; 60 } 61 }); 62 } 63 }); 64 data.views.forEach((item: IAVView) => { 65 tabHTML += `<div draggable="true" data-position="north" data-av-type="${item.type}" data-id="${item.id}" data-page="${item.pageSize}" data-desc="${escapeAriaLabel(item.desc || "")}" class="ariaLabel item${item.id === data.viewID ? " item--focus" : ""}"> 66 ${item.icon ? unicode2Emoji(item.icon, "item__graphic", true) : `<svg class="item__graphic"><use xlink:href="#${getViewIcon(item.type)}"></use></svg>`} 67 <span class="item__text">${escapeHtml(item.name)}</span> 68</div>`; 69 if (item.id === data.viewID) { 70 viewData = item; 71 } 72 }); 73 return `<div class="av__header"> 74 <div class="fn__flex av__views${showSearch ? " av__views--show" : ""}"> 75 <div class="layout-tab-bar fn__flex"> 76 ${tabHTML} 77 </div> 78 <div class="fn__space"></div> 79 <span data-type="av-add" class="block__icon ariaLabel" data-position="8south" aria-label="${window.siyuan.languages.newView}"> 80 <svg><use xlink:href="#iconAdd"></use></svg> 81 </span> 82 <div class="fn__flex-1"></div> 83 <div class="fn__space"></div> 84 <span data-type="av-switcher" aria-label="${window.siyuan.languages.allViews}" data-position="8south" class="ariaLabel block__icon${data.views.length > 0 ? "" : " fn__none"}"> 85 <svg><use xlink:href="#iconDown"></use></svg> 86 <span class="fn__space"></span> 87 <small>${data.views.length}</small> 88 </span> 89 <div class="fn__space"></div> 90 <span data-type="av-filter" aria-label="${window.siyuan.languages.filter}" data-position="8south" class="ariaLabel block__icon${hasFilter ? " block__icon--active" : ""}"> 91 <svg><use xlink:href="#iconFilter"></use></svg> 92 </span> 93 <div class="fn__space"></div> 94 <span data-type="av-sort" aria-label="${window.siyuan.languages.sort}" data-position="8south" class="ariaLabel block__icon${data.view.sorts.length > 0 ? " block__icon--active" : ""}"> 95 <svg><use xlink:href="#iconSort"></use></svg> 96 </span> 97 <div class="fn__space"></div> 98 <button data-type="av-search-icon" aria-label="${window.siyuan.languages.search}" data-position="8south" class="ariaLabel block__icon"> 99 <svg><use xlink:href="#iconSearch"></use></svg> 100 </button> 101 <div style="position: relative" class="fn__flex"> 102 <input style="${showSearch ? "width:128px" : "width:0;padding-left: 0;padding-right: 0;"}" data-type="av-search" class="b3-text-field b3-text-field--text" placeholder="${window.siyuan.languages.search}"> 103 </div> 104 <div class="fn__space"></div> 105 <span data-type="av-more" aria-label="${window.siyuan.languages.config}" data-position="8south" class="ariaLabel block__icon"> 106 <svg><use xlink:href="#iconSettings"></use></svg> 107 </span> 108 <div class="fn__space"></div> 109 <span data-type="av-add-more" class="block__icon ariaLabel" data-position="8south" aria-label="${window.siyuan.languages.newRow}"> 110 <svg><use xlink:href="#iconAdd"></use></svg> 111 </span> 112 <div class="fn__space"></div> 113 ${data.isMirror ? ` <span data-av-id="${data.id}" data-popover-url="/api/av/getMirrorDatabaseBlocks" class="popover__block block__icon block__icon--show ariaLabel" data-position="8south" aria-label="${window.siyuan.languages.mirrorTip}"> 114 <svg><use xlink:href="#iconSplitLR"></use></svg></span><div class="fn__space"></div>` : ""} 115 </div> 116 <div contenteditable="${editable}" spellcheck="${window.siyuan.config.editor.spellcheck.toString()}" class="av__title${viewData.hideAttrViewName ? " fn__none" : ""}" data-title="${data.name || ""}" data-tip="${window.siyuan.languages._kernel[267]}">${data.name || ""}</div> 117 <div class="av__counter fn__none"></div> 118 </div>`; 119}; 120 121const getTableHTMLs = (data: IAVTable, e: HTMLElement) => { 122 let calcHTML = ""; 123 let contentHTML = '<div class="av__row av__row--header"><div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div></div>'; 124 let pinIndex = -1; 125 let pinMaxIndex = -1; 126 let indexWidth = 0; 127 const eWidth = e.clientWidth; 128 data.columns.forEach((item, index) => { 129 if (!item.hidden) { 130 if (item.pin) { 131 pinIndex = index; 132 } 133 if (indexWidth < eWidth - 200) { 134 indexWidth += parseInt(item.width) || 200; 135 pinMaxIndex = index; 136 } 137 } 138 }); 139 if (eWidth === 0) { 140 pinMaxIndex = pinIndex; 141 } 142 pinIndex = Math.min(pinIndex, pinMaxIndex); 143 if (pinIndex > -1) { 144 contentHTML = '<div class="av__row av__row--header"><div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>'; 145 calcHTML = '<div class="av__colsticky">'; 146 } 147 let hasCalc = false; 148 data.columns.forEach((column: IAVColumn, index: number) => { 149 if (column.hidden) { 150 return; 151 } 152 contentHTML += `<div class="av__cell av__cell--header" data-col-id="${column.id}" draggable="true" 153data-icon="${column.icon}" data-dtype="${column.type}" data-wrap="${column.wrap}" data-pin="${column.pin}" 154data-desc="${escapeAttr(column.desc)}" data-position="north" 155style="width: ${column.width || "200px"};"> 156 ${column.icon ? unicode2Emoji(column.icon, "av__cellheadericon", true) : `<svg class="av__cellheadericon"><use xlink:href="#${getColIconByType(column.type)}"></use></svg>`} 157 <span class="av__celltext fn__flex-1">${escapeHtml(column.name)}</span> 158 ${column.pin ? '<svg class="av__cellheadericon av__cellheadericon--pin"><use xlink:href="#iconPin"></use></svg>' : ""} 159 <div class="av__widthdrag"></div> 160</div>`; 161 if (pinIndex === index) { 162 contentHTML += "</div>"; 163 } 164 if (column.type === "lineNumber") { 165 // lineNumber type 不参与计算操作 166 calcHTML += `<div data-col-id="${column.id}" data-dtype="${column.type}" class="av__calc" style="width: ${column.width || "200px"}">&nbsp;</div>`; 167 } else { 168 calcHTML += `<div class="av__calc${column.calc && column.calc.operator !== "" ? " av__calc--ashow" : ""}" data-col-id="${column.id}" data-dtype="${column.type}" data-operator="${column.calc?.operator || ""}" 169style="width: ${column.width || "200px"}">${getCalcValue(column) || `<svg><use xlink:href="#iconDown"></use></svg><small>${window.siyuan.languages.calc}</small>`}</div>`; 170 } 171 if (column.calc && column.calc.operator !== "") { 172 hasCalc = true; 173 } 174 175 if (pinIndex === index) { 176 calcHTML += "</div>"; 177 } 178 }); 179 contentHTML += `<div class="block__icons" style="min-height: auto"> 180 <div class="block__icon block__icon--show" data-type="av-header-more"><svg><use xlink:href="#iconMore"></use></svg></div> 181 <div class="fn__space"></div> 182 <div class="block__icon block__icon--show ariaLabel" aria-label="${window.siyuan.languages.newCol}" data-type="av-header-add" data-position="4south"><svg><use xlink:href="#iconAdd"></use></svg></div> 183</div> 184</div>`; 185 // body 186 data.rows.forEach((row: IAVRow, rowIndex: number) => { 187 contentHTML += `<div class="av__row" data-id="${row.id}">`; 188 if (pinIndex > -1) { 189 contentHTML += '<div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>'; 190 } else { 191 contentHTML += '<div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div></div>'; 192 } 193 194 row.cells.forEach((cell, index) => { 195 if (data.columns[index].hidden) { 196 return; 197 } 198 // https://github.com/siyuan-note/siyuan/issues/10262 199 let checkClass = ""; 200 if (cell.valueType === "checkbox") { 201 checkClass = cell.value?.checkbox?.checked ? " av__cell-check" : " av__cell-uncheck"; 202 } 203 contentHTML += `<div class="av__cell${checkClass}" data-id="${cell.id}" data-col-id="${data.columns[index].id}" 204data-wrap="${data.columns[index].wrap}" 205data-dtype="${data.columns[index].type}" 206${cell.value?.isDetached ? ' data-detached="true"' : ""} 207style="width: ${data.columns[index].width || "200px"}; 208${cell.valueType === "number" ? "text-align: right;" : ""} 209${cell.bgColor ? `background-color:${cell.bgColor};` : ""} 210${cell.color ? `color:${cell.color};` : ""}">${renderCell(cell.value, rowIndex, data.showIcon)}</div>`; 211 212 if (pinIndex === index) { 213 contentHTML += "</div>"; 214 } 215 }); 216 contentHTML += "<div></div></div>"; 217 }); 218 return `${contentHTML}<div class="av__row--util${data.rowCount > data.rows.length ? " av__readonly--show" : ""}"> 219 <div class="av__colsticky"> 220 <button class="b3-button av__button" data-type="av-add-bottom"> 221 <svg><use xlink:href="#iconAdd"></use></svg> 222 <span>${window.siyuan.languages.newRow}</span> 223 </button> 224 <span class="fn__space"></span> 225 <button class="b3-button av__button${data.rowCount > data.rows.length ? "" : " fn__none"}" data-type="av-load-more"> 226 <svg><use xlink:href="#iconArrowDown"></use></svg> 227 <span>${window.siyuan.languages.loadMore}</span> 228 <svg data-type="set-page-size" data-size="${data.pageSize}"><use xlink:href="#iconMore"></use></svg> 229 </button> 230 </div> 231</div> 232<div class="av__row--footer${hasCalc ? " av__readonly--show" : ""}">${calcHTML}</div>`; 233}; 234 235export const getGroupTitleHTML = (group: IAVView, counter: number) => { 236 let nameHTML = ""; 237 if (["mSelect", "select"].includes(group.groupValue.type)) { 238 group.groupValue.mSelect.forEach((item) => { 239 nameHTML += `<span class="b3-chip" style="background-color:var(--b3-font-background${item.color});color:var(--b3-font-color${item.color})">${escapeHtml(item.content)}</span>`; 240 }); 241 } else if (group.groupValue.type === "checkbox") { 242 nameHTML = `<svg style="width:calc(1.625em - 12px);height:calc(1.625em - 12px)"><use xlink:href="#icon${group.groupValue.checkbox.checked ? "Check" : "Uncheck"}"></use></svg>`; 243 } else { 244 nameHTML = group.name; 245 } 246 // av__group-name 为第三方需求,本应用内没有使用,但不能移除 https://github.com/siyuan-note/siyuan/issues/15736 247 return `<div class="av__group-title"> 248 <div class="av__group-icon" data-type="av-group-fold" data-id="${group.id}"> 249 <svg class="${group.groupFolded ? "" : "av__group-arrow--open"}"><use xlink:href="#iconRight"></use></svg> 250 </div> 251 <span class="fn__space"></span> 252 <span class="av__group-name">${nameHTML}</span> 253 ${counter === 0 ? '<span class="fn__space"></span>' : `<span class="av__group-counter">${counter}</span>`} 254 <span class="av__group-icon av__group-icon--hover ariaLabel" data-type="av-add-top" data-position="north" aria-label="${window.siyuan.languages.newRow}"><svg><use xlink:href="#iconAdd"></use></svg></span> 255</div>`; 256}; 257 258const renderGroupTable = (options: ITableOptions) => { 259 const searchInputElement = options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement; 260 const isSearching = searchInputElement && document.activeElement === searchInputElement; 261 const query = searchInputElement?.value || ""; 262 263 let avBodyHTML = ""; 264 options.data.view.groups.forEach((group: IAVTable) => { 265 if (group.groupHidden === 0) { 266 avBodyHTML += `${getGroupTitleHTML(group, group.rows.length)} 267<div data-group-id="${group.id}" data-page-size="${group.pageSize}" data-dtype="${group.groupKey.type}" data-content="${Lute.EscapeHTMLStr(group.groupValue.text?.content)}" style="float: left" class="av__body${group.groupFolded ? " fn__none" : ""}">${getTableHTMLs(group, options.blockElement)}</div>`; 268 } 269 }); 270 if (options.renderAll) { 271 options.blockElement.firstElementChild.outerHTML = `<div class="av__container"> 272 ${genTabHeaderHTML(options.data, isSearching || !!query, !options.protyle.disabled && !hasClosestByAttribute(options.blockElement, "data-type", "NodeBlockQueryEmbed"))} 273 <div class="av__scroll"> 274 ${avBodyHTML} 275 </div> 276 <div class="av__cursor" contenteditable="true">${Constants.ZWSP}</div> 277</div>`; 278 } else { 279 options.blockElement.firstElementChild.querySelector(".av__scroll").innerHTML = avBodyHTML; 280 } 281 afterRenderTable(options); 282}; 283 284const afterRenderTable = (options: ITableOptions) => { 285 if (options.blockElement.getAttribute("data-need-focus") === "true") { 286 focusBlock(options.blockElement); 287 options.blockElement.removeAttribute("data-need-focus"); 288 } 289 options.blockElement.setAttribute("data-render", "true"); 290 options.blockElement.querySelector(".av__scroll").scrollLeft = options.resetData.left; 291 options.blockElement.style.alignSelf = options.resetData.alignSelf; 292 const editRect = options.protyle.contentElement.getBoundingClientRect(); 293 if (options.resetData.headerTransform) { 294 const headerTransformElement = options.blockElement.querySelector('.av__row--header[style^="transform"]') as HTMLElement; 295 if (headerTransformElement) { 296 headerTransformElement.style.transform = options.resetData.headerTransform; 297 } 298 } else { 299 // 需等待渲染完,否则 getBoundingClientRect 错误 https://github.com/siyuan-note/siyuan/issues/13787 300 setTimeout(() => { 301 stickyRow(options.blockElement, editRect, "top"); 302 }, Constants.TIMEOUT_LOAD); 303 } 304 if (options.resetData.footerTransform) { 305 const footerTransformElement = options.blockElement.querySelector('.av__row--footer[style^="transform"]') as HTMLElement; 306 if (footerTransformElement) { 307 footerTransformElement.style.transform = options.resetData.footerTransform; 308 } 309 } else { 310 // 需等待渲染完,否则 getBoundingClientRect 错误 https://github.com/siyuan-note/siyuan/issues/13787 311 setTimeout(() => { 312 stickyRow(options.blockElement, editRect, "bottom"); 313 }, Constants.TIMEOUT_LOAD); 314 } 315 if (options.resetData.selectCellId) { 316 let newCellElement = options.blockElement.querySelector(`.av__body[data-group-id="${options.resetData.selectCellId.groupId}"] .av__row[data-id="${options.resetData.selectCellId.rowId}"] .av__cell[data-col-id="${options.resetData.selectCellId.colId}"]`); 317 if (!newCellElement) { 318 newCellElement = options.blockElement.querySelector(`.av__row[data-id="${options.resetData.selectCellId.rowId}"] .av__cell[data-col-id="${options.resetData.selectCellId.colId}"]`); 319 } 320 if (newCellElement) { 321 newCellElement.classList.add("av__cell--select"); 322 cellScrollIntoView(options.blockElement, newCellElement); 323 } 324 const avMaskElement = document.querySelector(".av__mask"); 325 const avPanelElement = document.querySelector(".av__panel"); 326 if (avMaskElement) { 327 (avMaskElement.querySelector("textarea, input") as HTMLTextAreaElement)?.focus(); 328 } else if (!avPanelElement && !options.resetData.isSearching && getSelection().rangeCount > 0) { 329 const range = getSelection().getRangeAt(0); 330 const blockElement = hasClosestBlock(range.startContainer); 331 if (blockElement && options.blockElement === blockElement) { 332 focusBlock(options.blockElement); 333 } 334 } else if (avPanelElement && !newCellElement) { 335 avPanelElement.remove(); 336 } 337 } 338 options.resetData.selectRowIds.forEach((selectRowId, index) => { 339 let rowElement = options.blockElement.querySelector(`.av__body[data-group-id="${selectRowId.groupId}"] .av__row[data-id="${selectRowId.rowId}"]`) as HTMLElement; 340 if (!rowElement) { 341 rowElement = options.blockElement.querySelector(`.av__row[data-id="${selectRowId.rowId}"]`) as HTMLElement; 342 } 343 if (rowElement) { 344 rowElement.classList.add("av__row--select"); 345 rowElement.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconCheck"); 346 } 347 if (index === options.resetData.selectRowIds.length - 1 && rowElement) { 348 updateHeader(rowElement); 349 } 350 }); 351 Object.keys(options.resetData.pageSizes).forEach((groupId) => { 352 const bodyElement = options.blockElement.querySelector(`.av__body[data-group-id="${groupId === "unGroup" ? "" : groupId}"]`) as HTMLElement; 353 if (bodyElement) { 354 bodyElement.dataset.pageSize = options.resetData.pageSizes[groupId]; 355 } 356 }); 357 if (options.resetData.dragFillId) { 358 let dragCellElement = options.blockElement.querySelector(`.av__body[data-group-id="${options.resetData.dragFillId.groupId}"] .av__row[data-id="${options.resetData.dragFillId.rowId}"] .av__cell[data-col-id="${options.resetData.dragFillId.colId}"]`); 359 if (!dragCellElement) { 360 dragCellElement = options.blockElement.querySelector(`.av__row[data-id="${options.resetData.dragFillId.rowId}"] .av__cell[data-col-id="${options.resetData.dragFillId.colId}"]`); 361 } 362 addDragFill(dragCellElement); 363 } 364 options.resetData.activeIds.forEach(activeId => { 365 let activeCellElement = options.blockElement.querySelector(`.av__body[data-group-id="${activeId.groupId}"] .av__row[data-id="${activeId.rowId}"] .av__cell[data-col-id="${activeId.colId}"]`); 366 if (!activeCellElement) { 367 activeCellElement = options.blockElement.querySelector(`.av__row[data-id="${activeId.rowId}"] .av__cell[data-col-id="${activeId.colId}"]`); 368 } 369 activeCellElement?.classList.add("av__cell--active"); 370 }); 371 if (getSelection().rangeCount > 0) { 372 // 修改表头后光标重新定位 373 const range = getSelection().getRangeAt(0); 374 if (!hasClosestByClassName(range.startContainer, "av__title")) { 375 const blockElement = hasClosestBlock(range.startContainer); 376 if (blockElement && options.blockElement === blockElement && !options.resetData.isSearching) { 377 focusBlock(options.blockElement); 378 } 379 } 380 } 381 options.blockElement.querySelector(".layout-tab-bar").scrollLeft = (options.blockElement.querySelector(".layout-tab-bar .item--focus") as HTMLElement).offsetLeft - 30; 382 if (options.cb) { 383 options.cb(options.data); 384 } 385 if (!options.renderAll) { 386 return; 387 } 388 const viewsElement = options.blockElement.querySelector(".av__views") as HTMLElement; 389 const searchInputElement = options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement; 390 searchInputElement.value = options.resetData.query || ""; 391 if (options.resetData.isSearching) { 392 searchInputElement.focus(); 393 } 394 searchInputElement.addEventListener("compositionstart", (event: KeyboardEvent) => { 395 event.stopPropagation(); 396 }); 397 searchInputElement.addEventListener("keydown", (event: KeyboardEvent) => { 398 if (event.isComposing) { 399 return; 400 } 401 electronUndo(event); 402 }); 403 searchInputElement.addEventListener("input", (event: KeyboardEvent) => { 404 event.stopPropagation(); 405 if (event.isComposing) { 406 return; 407 } 408 if (searchInputElement.value || document.activeElement === searchInputElement) { 409 viewsElement.classList.add("av__views--show"); 410 } else { 411 viewsElement.classList.remove("av__views--show"); 412 } 413 updateSearch(options.blockElement, options.protyle); 414 }); 415 searchInputElement.addEventListener("compositionend", () => { 416 updateSearch(options.blockElement, options.protyle); 417 }); 418 searchInputElement.addEventListener("blur", (event: KeyboardEvent) => { 419 if (event.isComposing) { 420 return; 421 } 422 if (!searchInputElement.value) { 423 viewsElement.classList.remove("av__views--show"); 424 searchInputElement.style.width = "0"; 425 searchInputElement.style.paddingLeft = "0"; 426 searchInputElement.style.paddingRight = "0"; 427 } 428 }); 429 addClearButton({ 430 inputElement: searchInputElement, 431 right: 0, 432 width: "1em", 433 height: searchInputElement.clientHeight, 434 clearCB() { 435 viewsElement.classList.remove("av__views--show"); 436 searchInputElement.style.width = "0"; 437 searchInputElement.style.paddingLeft = "0"; 438 searchInputElement.style.paddingRight = "0"; 439 focusBlock(options.blockElement); 440 updateSearch(options.blockElement, options.protyle); 441 } 442 }); 443}; 444 445export const avRender = (element: Element, protyle: IProtyle, cb?: (data: IAV) => void, renderAll = true) => { 446 let avElements: Element[] = []; 447 if (element.getAttribute("data-type") === "NodeAttributeView") { 448 // 编辑器内代码块编辑渲染 449 avElements = [element]; 450 } else { 451 avElements = Array.from(element.querySelectorAll('[data-type="NodeAttributeView"]')); 452 } 453 if (avElements.length === 0) { 454 return; 455 } 456 if (avElements.length > 0) { 457 avElements.forEach((e: HTMLElement) => { 458 e.removeAttribute("data-rendering"); 459 if (e.getAttribute("data-render") === "true" || hasClosestByClassName(e, "av__gallery-content")) { 460 return; 461 } 462 if (isMobile() || isInIOS() || isInAndroid() || isInHarmony()) { 463 e.classList.add("av--touch"); 464 } 465 466 if (e.getAttribute("data-av-type") === "gallery") { 467 renderGallery({blockElement: e, protyle, cb, renderAll}); 468 return; 469 } 470 471 let selectCellId; 472 const selectCellElement = e.querySelector(".av__cell--select") as HTMLElement; 473 if (selectCellElement) { 474 selectCellId = { 475 groupId: (hasClosestByClassName(selectCellElement, "av__body") as HTMLElement).dataset.groupId || "", 476 rowId: (hasClosestByClassName(selectCellElement, "av__row") as HTMLElement).dataset.id, 477 colId: selectCellElement.getAttribute("data-col-id"), 478 }; 479 } 480 const selectRowIds: IIds[] = []; 481 e.querySelectorAll(".av__row--select").forEach(rowItem => { 482 const rowId = rowItem.getAttribute("data-id"); 483 if (rowId) { 484 selectRowIds.push({ 485 groupId: (hasClosestByClassName(rowItem, "av__body") as HTMLElement).dataset.groupId || "", 486 rowId 487 }); 488 } 489 }); 490 let dragFillId; 491 const dragFillElement = e.querySelector(".av__drag-fill") as HTMLElement; 492 if (dragFillElement) { 493 dragFillId = { 494 groupId: (hasClosestByClassName(dragFillElement, "av__body") as HTMLElement).dataset.groupId || "", 495 rowId: (hasClosestByClassName(dragFillElement, "av__row") as HTMLElement).dataset.id, 496 colId: dragFillElement.parentElement.getAttribute("data-col-id"), 497 }; 498 } 499 const activeIds: IIds[] = []; 500 e.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => { 501 activeIds.push({ 502 groupId: (hasClosestByClassName(item, "av__body") as HTMLElement).dataset.groupId || "", 503 rowId: (hasClosestByClassName(item, "av__row") as HTMLElement).dataset.id, 504 colId: item.getAttribute("data-col-id"), 505 }); 506 }); 507 const searchInputElement = e.querySelector('[data-type="av-search"]') as HTMLInputElement; 508 const pageSizes: { [key: string]: string } = {}; 509 e.querySelectorAll(".av__body").forEach((item: HTMLElement) => { 510 pageSizes[item.dataset.groupId || "unGroup"] = item.dataset.pageSize; 511 }); 512 const resetData = { 513 selectCellId, 514 alignSelf: e.style.alignSelf, 515 left: e.querySelector(".av__scroll")?.scrollLeft || 0, 516 headerTransform: (e.querySelector('.av__row--header[style^="transform"]') as HTMLElement)?.style.transform, 517 footerTransform: (e.querySelector(".av__row--footer") as HTMLElement)?.style.transform, 518 isSearching: searchInputElement && document.activeElement === searchInputElement, 519 selectRowIds, 520 dragFillId, 521 activeIds, 522 query: searchInputElement?.value || "", 523 pageSizes 524 }; 525 if (e.firstElementChild.innerHTML === "") { 526 e.style.alignSelf = ""; 527 let html = ""; 528 [1, 2, 3].forEach(() => { 529 html += `<div class="av__row"> 530 <div style="width: 24px;flex-shrink: 0"></div> 531 <div class="av__cell" style="width: 200px"><span class="av__pulse"></span></div> 532 <div class="av__cell" style="width: 200px"><span class="av__pulse"></span></div> 533 <div class="av__cell" style="width: 200px"><span class="av__pulse"></span></div> 534 <div class="av__cell" style="width: 200px"><span class="av__pulse"></span></div> 535</div>`; 536 }); 537 e.firstElementChild.innerHTML = html; 538 } 539 const created = protyle.options.history?.created; 540 const snapshot = protyle.options.history?.snapshot; 541 const avPageSize = getPageSize(e); 542 fetchPost(created ? "/api/av/renderHistoryAttributeView" : (snapshot ? "/api/av/renderSnapshotAttributeView" : "/api/av/renderAttributeView"), { 543 id: e.getAttribute("data-av-id"), 544 created, 545 snapshot, 546 pageSize: avPageSize.unGroupPageSize, 547 groupPaging: avPageSize.groupPageSize, 548 viewID: e.getAttribute(Constants.CUSTOM_SY_AV_VIEW) || "", 549 query: resetData.query.trim(), 550 blockID: e.getAttribute("data-node-id"), 551 }, (response) => { 552 const data = response.data.view as IAVTable; 553 if (response.data.viewType === "gallery") { 554 e.setAttribute("data-av-type", "table"); 555 renderGallery({blockElement: e, protyle, cb, renderAll, data: response.data}); 556 return; 557 } 558 if (data.groups?.length > 0) { 559 renderGroupTable({blockElement: e, protyle, cb, renderAll, data: response.data, resetData}); 560 return; 561 } 562 const avBodyHTML = `<div class="av__body" data-group-id="" data-page-size="${data.pageSize}" style="float: left"> 563 ${getTableHTMLs(data, e)} 564</div>`; 565 if (renderAll) { 566 e.firstElementChild.outerHTML = `<div class="av__container"> 567 ${genTabHeaderHTML(response.data, resetData.isSearching || !!resetData.query, !protyle.disabled && !hasClosestByAttribute(e, "data-type", "NodeBlockQueryEmbed"))} 568 <div class="av__scroll"> 569 ${avBodyHTML} 570 </div> 571 <div class="av__cursor" contenteditable="true">${Constants.ZWSP}</div> 572</div>`; 573 } else { 574 e.firstElementChild.querySelector(".av__scroll").innerHTML = avBodyHTML; 575 } 576 afterRenderTable({ 577 renderAll, 578 data: response.data, 579 cb, 580 protyle, 581 blockElement: e, 582 resetData 583 }); 584 // 历史兼容 585 e.style.margin = ""; 586 }); 587 }); 588 } 589}; 590 591let searchTimeout: number; 592 593export const updateSearch = (e: HTMLElement, protyle: IProtyle) => { 594 clearTimeout(searchTimeout); 595 searchTimeout = window.setTimeout(() => { 596 e.removeAttribute("data-render"); 597 avRender(e, protyle, undefined, false); 598 }, Constants.TIMEOUT_INPUT); 599}; 600 601const refreshTimeouts: { 602 [key: string]: number; 603} = {}; 604 605export const refreshAV = (protyle: IProtyle, operation: IOperation) => { 606 if (operation.action === "setAttrViewName") { 607 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.id}"]`)).forEach((item: HTMLElement) => { 608 const titleElement = item.querySelector(".av__title") as HTMLElement; 609 if (!titleElement) { 610 return; 611 } 612 titleElement.textContent = operation.data; 613 titleElement.dataset.title = operation.data; 614 }); 615 } 616 if (operation.action === "setAttrViewColWidth") { 617 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 618 const cellElement = item.querySelector(`.av__cell[data-col-id="${operation.id}"]`) as HTMLElement; 619 if (!cellElement || cellElement.style.width === operation.data || item.getAttribute(Constants.CUSTOM_SY_AV_VIEW) !== operation.keyID) { 620 return; 621 } 622 item.querySelectorAll(".av__row").forEach(rowItem => { 623 (rowItem.querySelector(`[data-col-id="${operation.id}"]`) as HTMLElement).style.width = operation.data; 624 }); 625 }); 626 return; 627 } 628 if (operation.action === "setAttrViewCardSize") { 629 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 630 item.querySelectorAll(".av__gallery").forEach(galleryItem => { 631 galleryItem.classList.remove("av__gallery--small", "av__gallery--big"); 632 if (operation.data === 0) { 633 galleryItem.classList.add("av__gallery--small"); 634 } else if (operation.data === 2) { 635 galleryItem.classList.add("av__gallery--big"); 636 } 637 }); 638 }); 639 return; 640 } 641 if (operation.action === "setAttrViewCardAspectRatio") { 642 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 643 item.querySelectorAll(".av__gallery-cover").forEach(coverItem => { 644 coverItem.className = "av__gallery-cover av__gallery-cover--" + operation.data; 645 }); 646 }); 647 return; 648 } 649 if (operation.action === "hideAttrViewName") { 650 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 651 const titleElement = item.querySelector(".av__title"); 652 if (titleElement) { 653 if (!operation.data) { 654 titleElement.classList.remove("fn__none"); 655 } else { 656 // hide 657 titleElement.classList.add("fn__none"); 658 } 659 if (item.getAttribute("data-av-type") === "gallery" && !item.querySelector(".av__group-title")) { 660 const galleryElement = item.querySelector(".av__gallery"); 661 if (!operation.data) { 662 galleryElement.classList.remove("av__gallery--top"); 663 } else { 664 // hide 665 galleryElement.classList.add("av__gallery--top"); 666 } 667 } 668 } 669 }); 670 return; 671 } 672 if (operation.action === "setAttrViewWrapField") { 673 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 674 item.querySelectorAll(".av__cell").forEach(fieldItem => { 675 fieldItem.setAttribute("data-wrap", operation.data.toString()); 676 }); 677 }); 678 return; 679 } 680 if (operation.action === "setAttrViewFitImage") { 681 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"] .av__gallery-img`)).forEach((item: HTMLElement) => { 682 if (operation.data) { 683 item.classList.add("av__gallery-img--fit"); 684 } else { 685 item.classList.remove("av__gallery-img--fit"); 686 } 687 }); 688 return; 689 } 690 if (operation.action === "setAttrViewShowIcon") { 691 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 692 item.querySelectorAll('.av__cell[data-dtype="block"] .b3-menu__avemoji, .av__cell[data-dtype="relation"] .b3-menu__avemoji').forEach(cellItem => { 693 if (operation.data) { 694 cellItem.classList.remove("fn__none"); 695 } else { 696 cellItem.classList.add("fn__none"); 697 } 698 }); 699 }); 700 return; 701 } 702 if (operation.action === "setAttrViewColWrap") { 703 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 704 item.querySelectorAll(`.av__cell[data-col-id="${operation.id}"],.av__cell[data-field-id="${operation.id}"]`).forEach(cellItem => { 705 cellItem.setAttribute("data-wrap", operation.data.toString()); 706 }); 707 }); 708 return; 709 } 710 if (operation.action === "foldAttrViewGroup") { 711 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${operation.avID}"]`)).forEach((item: HTMLElement) => { 712 const foldElement = item.querySelector(`[data-type="av-group-fold"][data-id="${operation.id}"]`); 713 if (foldElement) { 714 if (operation.data) { 715 foldElement.firstElementChild.classList.remove("av__group-arrow--open"); 716 foldElement.parentElement.nextElementSibling.classList.add("fn__none"); 717 } else { 718 foldElement.firstElementChild.classList.add("av__group-arrow--open"); 719 foldElement.parentElement.nextElementSibling.classList.remove("fn__none"); 720 } 721 foldElement.removeAttribute("data-folding"); 722 } 723 }); 724 return; 725 } 726 // 只能 setTimeout,以前方案快速输入后最后一次修改会被忽略;必须为每一个 protyle 单独设置,否则有多个 protyle 时,其余无法被执行 727 clearTimeout(refreshTimeouts[protyle.id]); 728 refreshTimeouts[protyle.id] = window.setTimeout(() => { 729 // 修改表格名 avID 传入到 id 上了 https://github.com/siyuan-note/siyuan/issues/12724 730 const avID = operation.action === "setAttrViewName" ? operation.id : operation.avID; 731 const attrElement = document.querySelector(`.b3-dialog--open[data-key="${Constants.DIALOG_ATTR}"] .custom-attr > [data-av-id="${avID}"]`) as HTMLElement; 732 if (attrElement) { 733 // 更新属性面板 734 attrElement.removeAttribute("data-rendering"); 735 renderAVAttribute(attrElement.parentElement, attrElement.dataset.nodeId, protyle); 736 } 737 Array.from(protyle.wysiwyg.element.querySelectorAll(`.av[data-av-id="${avID}"]`)).forEach((item: HTMLElement) => { 738 item.removeAttribute("data-render"); 739 if (operation.action === "sortAttrViewRow") { 740 clearSelect(["cell"], item); 741 } else if (operation.action === "sortAttrViewCol") { 742 item.querySelectorAll(".av__cell--active").forEach((item: HTMLElement) => { 743 item.classList.remove("av__cell--active"); 744 item.querySelector(".av__drag-fill")?.remove(); 745 }); 746 addDragFill(item.querySelector(".av__cell--select")); 747 } else if (operation.action === "setAttrViewBlockView") { 748 const viewTabElement = item.querySelector(`.av__views > .layout-tab-bar > .item[data-id="${operation.id}"]`) as HTMLElement; 749 if (viewTabElement) { 750 item.querySelectorAll(".av__body").forEach((bodyItem: HTMLElement) => { 751 bodyItem.dataset.pageSize = viewTabElement.dataset.page; 752 }); 753 } 754 } else if (operation.action === "addAttrViewView") { 755 item.querySelectorAll(".av__body").forEach((bodyItem: HTMLElement) => { 756 bodyItem.dataset.pageSize = "50"; 757 }); 758 } else if (operation.action === "removeAttrViewView") { 759 item.querySelectorAll(".av__body").forEach((bodyItem: HTMLElement) => { 760 bodyItem.dataset.pageSize = item.querySelector(`.av__views > .layout-tab-bar .item[data-id="${item.getAttribute(Constants.CUSTOM_SY_AV_VIEW)}"]`)?.getAttribute("data-page"); 761 }); 762 } else if (operation.action === "sortAttrViewView" && operation.data === "unRefresh") { 763 const viewTabElement = item.querySelector(`.av__views > .layout-tab-bar > .item[data-id="${operation.id}"]`) as HTMLElement; 764 if (viewTabElement && !operation.previousID && !viewTabElement.previousElementSibling) { 765 return; 766 } else if (viewTabElement && operation.previousID && viewTabElement.previousElementSibling?.getAttribute("data-id") === operation.previousID) { 767 return; 768 } 769 } 770 const hasGhost = item.querySelector('[data-type="ghost"]'); 771 avRender(item, protyle, () => { 772 if (operation.action === "insertAttrViewBlock" && operation.context?.ignoreTip !== "true") { 773 if (operation.context?.message) { 774 showMessage(operation.context.message); 775 } else { 776 const groupQuery = operation.groupID ? `[data-group-id="${operation.groupID}"]` : ""; 777 if (item.getAttribute("data-av-type") === "gallery") { 778 operation.srcs.forEach(srcItem => { 779 const filesElement = item.querySelector(`.av__body${groupQuery} .av__gallery-item[data-id="${srcItem.itemID}"]`)?.querySelector(".av__gallery-fields"); 780 if (filesElement && filesElement.querySelector('[data-dtype="block"]')?.parentElement.getAttribute("data-empty") === "true") { 781 filesElement.classList.add("av__gallery-fields--edit"); 782 } 783 }); 784 } 785 if (operation.srcs.length === 1) { 786 let popCellElement = item.querySelector(`.av__body${groupQuery} [data-id="${operation.srcs[0].itemID}"] .av__cell[data-dtype="block"]`) as HTMLElement; 787 if (!popCellElement) { 788 const popCellElements = item.querySelectorAll(`.av__body [data-id="${operation.srcs[0].itemID}"] .av__cell[data-dtype="block"]`); 789 if (popCellElements.length === 1) { 790 popCellElement = popCellElements[0] as HTMLElement; 791 } 792 } 793 if (popCellElement && popCellElement.getAttribute("data-detached") === "true" && 794 popCellElement.querySelector(".av__celltext").textContent === "" && 795 popCellElement.getBoundingClientRect().height !== 0 && hasGhost) { 796 popTextCell(protyle, [popCellElement], "block"); 797 } 798 } 799 operation.srcs.find((srcItem) => { 800 if (!item.querySelector(`.av__body [data-id="${srcItem.itemID}"]`) && 801 !item.querySelector(`.av__body [data-dtype="block"] .av__celltext--ref[data-id="${srcItem.id}"]`)) { 802 showMessage(window.siyuan.languages.insertRowTip); 803 return true; 804 } 805 }); 806 } 807 } else if (operation.action === "addAttrViewView") { 808 if (item.getAttribute("data-node-id") === operation.blockID) { 809 openMenuPanel({protyle, blockElement: item, type: "config"}); 810 } 811 } 812 item.removeAttribute("data-loading"); 813 }); 814 }); 815 }, ["insertAttrViewBlock"].includes(operation.action) ? 2 : 100); 816};