A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
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};