A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {Menu} from "../../../plugin/Menu";
2import {transaction} from "../../wysiwyg/transaction";
3import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest";
4import {confirmDialog} from "../../../dialog/confirmDialog";
5import {upDownHint} from "../../../util/upDownHint";
6import {bindEditEvent, getColId, getEditHTML} from "./col";
7import {updateAttrViewCellAnimation} from "./action";
8import {genAVValueHTML, isCustomAttr} from "./blockAttr";
9import {escapeAriaLabel, escapeAttr, escapeHtml} from "../../../util/escape";
10import {genCellValueByElement, getTypeByCellElement} from "./cell";
11import * as dayjs from "dayjs";
12import {getFieldsByData} from "./view";
13import {getFieldIdByCellElement} from "./row";
14import {Constants} from "../../../constants";
15
16let cellValues: IAVCellValue[];
17
18const filterSelectHTML = (key: string, options: {
19 name: string,
20 color: string,
21 desc?: string
22}[], selected: string[] = []) => {
23 let html = "";
24 let hasMatch = false;
25 if (selected.length === 0) {
26 document.querySelectorAll(".av__panel .b3-chips .b3-chip").forEach((item: HTMLElement) => {
27 selected.push(item.dataset.content);
28 });
29 }
30 if (options) {
31 const currentName = document.querySelector(".av__panel .b3-menu__item--current")?.getAttribute("data-name") || "";
32 options.forEach(item => {
33 if (!key ||
34 (key.toLowerCase().indexOf(item.name.toLowerCase()) > -1 ||
35 item.name.toLowerCase().indexOf(key.toLowerCase()) > -1)) {
36 const airaLabel = item.desc ? `${escapeAriaLabel(item.name)}<div class='ft__on-surface'>${escapeAriaLabel(item.desc || "")}</div>` : "";
37 html += `<button data-type="addColOptionOrCell" class="b3-menu__item${currentName === item.name ? " b3-menu__item--current" : ""}" data-name="${escapeAttr(item.name)}" data-desc="${escapeAttr(item.desc || "")}" draggable="true" data-color="${item.color}">
38 <svg class="b3-menu__icon fn__grab"><use xlink:href="#iconDrag"></use></svg>
39 <div class="fn__flex-1 ariaLabel" data-position="parentW" aria-label="${airaLabel}">
40 <span class="b3-chip" style="background-color:var(--b3-font-background${item.color});color:var(--b3-font-color${item.color})">
41 <span class="fn__ellipsis">${escapeHtml(item.name)}</span>
42 </span>
43 </div>
44 <svg class="b3-menu__action" data-type="setColOption"><use xlink:href="#iconEdit"></use></svg>
45 ${selected.includes(item.name) ? '<svg class="b3-menu__checked"><use xlink:href="#iconSelect"></use></svg></span>' : ""}
46</button>`;
47 }
48 if (key === item.name) {
49 hasMatch = true;
50 }
51 });
52 }
53 if (!hasMatch && key) {
54 html = html.replace('class="b3-menu__item b3-menu__item--current"', 'class="b3-menu__item"');
55 const colorIndex = (options?.length || 0) % 14 + 1;
56 html = `<button data-type="addColOptionOrCell" class="b3-menu__item b3-menu__item--current" data-name="${key}" data-color="${colorIndex}">
57<svg class="b3-menu__icon"><use xlink:href="#iconAdd"></use></svg>
58<div class="fn__flex-1">
59 <span class="b3-chip" style="background-color:var(--b3-font-background${colorIndex});color:var(--b3-font-color${colorIndex})">
60 <span class="fn__ellipsis">${escapeHtml(key)}</span>
61 </span>
62</div>
63<span class="b3-menu__accelerator b3-menu__accelerator--hotkey">${window.siyuan.languages.enterKey}</span>
64</button>${html}`;
65 } else if (html.indexOf("b3-menu__item--current") === -1) {
66 html = html.replace('class="b3-menu__item"', 'class="b3-menu__item b3-menu__item--current"');
67 }
68 return html;
69};
70
71export const removeCellOption = (protyle: IProtyle, cellElements: HTMLElement[], target: HTMLElement, blockElement: Element) => {
72 if (!target) {
73 return;
74 }
75 const viewType = blockElement.getAttribute("data-av-type") as TAVView;
76 const colId = getColId(cellElements[0], viewType);
77 const doOperations: IOperation[] = [];
78 const undoOperations: IOperation[] = [];
79 let mSelectValue: IAVCellSelectValue[];
80 const avID = blockElement.getAttribute("data-av-id");
81 cellElements.forEach((item, elementIndex) => {
82 const rowID = getFieldIdByCellElement(item, viewType);
83 if (!rowID) {
84 return;
85 }
86 if (!blockElement.contains(item)) {
87 if (viewType === "table") {
88 item = cellElements[elementIndex] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${item.dataset.colId}"]`) ||
89 blockElement.querySelector(`.fn__flex-1[data-col-id="${item.dataset.colId}"]`)) as HTMLElement;
90 } else {
91 item = cellElements[elementIndex] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${item.dataset.fieldId}"]`)) as HTMLElement;
92 }
93 }
94 const cellValue: IAVCellValue = cellValues[elementIndex];
95 const oldValue = JSON.parse(JSON.stringify(cellValue));
96 if (elementIndex === 0) {
97 cellValue.mSelect?.find((item, index) => {
98 if (item.content === target.dataset.content) {
99 cellValue.mSelect.splice(index, 1);
100 return true;
101 }
102 });
103 mSelectValue = cellValue.mSelect;
104 } else {
105 cellValue.mSelect = mSelectValue;
106 }
107 doOperations.push({
108 action: "updateAttrViewCell",
109 id: cellValue.id,
110 keyID: colId,
111 rowID,
112 avID,
113 data: cellValue
114 });
115 undoOperations.push({
116 action: "updateAttrViewCell",
117 id: cellValue.id,
118 keyID: colId,
119 rowID,
120 avID,
121 data: oldValue
122 });
123 if (item.classList.contains("custom-attr__avvalue")) {
124 item.innerHTML = genAVValueHTML(cellValue);
125 } else {
126 updateAttrViewCellAnimation(item, cellValue);
127 }
128 });
129 doOperations.push({
130 action: "doUpdateUpdated",
131 id: blockElement.getAttribute("data-node-id"),
132 data: dayjs().format("YYYYMMDDHHmmss"),
133 });
134 transaction(protyle, doOperations, undoOperations);
135 Array.from(document.querySelectorAll(".av__panel .b3-menu__item")).find((item: HTMLElement) => {
136 if (item.dataset.name === target.dataset.content) {
137 item.querySelector(".b3-menu__checked")?.remove();
138 return true;
139 }
140 });
141 target.remove();
142};
143
144export const setColOption = (protyle: IProtyle, data: IAV, target: HTMLElement, blockElement: Element, isCustomAttr: boolean, cellElements?: HTMLElement[]) => {
145 const menuElement = hasClosestByClassName(target, "b3-menu");
146 if (!menuElement) {
147 return;
148 }
149 const blockID = blockElement.getAttribute("data-node-id");
150 const viewType = blockElement.getAttribute("data-av-type") as TAVView;
151 const colId = (cellElements && cellElements[0]) ? getColId(cellElements[0], viewType) : menuElement.querySelector(".b3-menu__item").getAttribute("data-col-id");
152 let name = target.parentElement.dataset.name;
153 let desc = target.parentElement.dataset.desc;
154 let color = target.parentElement.dataset.color;
155 const fields = getFieldsByData(data);
156 const menu = new Menu(Constants.MENU_AV_COL_OPTION, () => {
157 if ((name === inputElement.value && desc === descElement.value) || !inputElement.value) {
158 return;
159 }
160 // cell 不判断重名 https://github.com/siyuan-note/siyuan/issues/11484
161 transaction(protyle, [{
162 action: "updateAttrViewColOption",
163 id: colId,
164 avID: data.id,
165 data: {
166 newColor: color,
167 oldName: name,
168 newName: inputElement.value,
169 newDesc: descElement.value
170 },
171 }, {
172 action: "doUpdateUpdated",
173 id: blockID,
174 data: dayjs().format("YYYYMMDDHHmmss"),
175 }], [{
176 action: "updateAttrViewColOption",
177 id: colId,
178 avID: data.id,
179 data: {
180 newColor: color,
181 oldName: inputElement.value,
182 newName: name,
183 newDesc: desc
184 },
185 }]);
186 fields.find(column => {
187 if (column.id === colId) {
188 // 重名不进行更新 https://github.com/siyuan-note/siyuan/issues/13554
189 const sameItem = column.options.find((item) => {
190 if (item.name === inputElement.value && item.desc === descElement.value) {
191 return true;
192 }
193 });
194 if (!sameItem) {
195 column.options.find((item) => {
196 if (item.name === name) {
197 item.name = inputElement.value;
198 item.desc = descElement.value;
199 return true;
200 }
201 });
202 }
203 return true;
204 }
205 });
206 const oldScroll = menuElement.querySelector(".b3-menu__items").scrollTop;
207 const selectedElement = menuElement.querySelector(".b3-chips");
208 const oldChipsHeight = selectedElement ? selectedElement.clientHeight : 0;
209 if (!cellElements) {
210 menuElement.innerHTML = getEditHTML({protyle, data, colId, isCustomAttr});
211 bindEditEvent({protyle, data, menuElement, isCustomAttr, blockID});
212 } else {
213 cellElements.forEach((cellElement: HTMLElement, index) => {
214 const rowID = getFieldIdByCellElement(cellElement, viewType);
215 if (viewType === "table" || isCustomAttr) {
216 cellElement = cellElements[index] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${cellElement.dataset.colId}"]`) ||
217 blockElement.querySelector(`.fn__flex-1[data-col-id="${cellElement.dataset.colId}"]`)) as HTMLElement;
218 } else {
219 cellElement = cellElements[index] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${cellElement.dataset.fieldId}"]`)) as HTMLElement;
220 }
221
222 cellValues[index].mSelect.find((item) => {
223 if (item.content === name) {
224 item.content = inputElement.value;
225 return true;
226 }
227 });
228 if (cellElement.classList.contains("custom-attr__avvalue")) {
229 cellElement.innerHTML = genAVValueHTML(cellValues[index]);
230 } else {
231 updateAttrViewCellAnimation(cellElement, cellValues[index]);
232 }
233 });
234 menuElement.innerHTML = getSelectHTML(fields, cellElements, false, blockElement);
235 bindSelectEvent(protyle, data, menuElement, cellElements, blockElement);
236 }
237 if (selectedElement) {
238 menuElement.querySelector(".b3-menu__items").scrollTop = oldScroll + (menuElement.querySelector(".b3-chips").clientHeight - oldChipsHeight);
239 }
240 });
241 if (menu.isOpen) {
242 return;
243 }
244 menu.addItem({
245 iconHTML: "",
246 type: "empty",
247 label: `<div class="fn__hr"></div>
248<div class="b3-form__icona fn__block">
249 <input class="b3-text-field b3-form__icona-input" type="text" size="16">
250 <svg data-position="north" class="b3-form__icona-icon ariaLabel" aria-label="${desc ? escapeAriaLabel(desc) : window.siyuan.languages.addDesc}"><use xlink:href="#iconInfo"></use></svg>
251</div>
252<div class="fn__none">
253 <div class="fn__hr"></div>
254 <textarea rows="1" placeholder="${window.siyuan.languages.addDesc}" class="b3-text-field fn__block" type="text" data-value="${escapeAttr(desc)}">${desc}</textarea>
255</div>
256<div class="fn__hr--small"></div>`,
257 bind(element) {
258 const inputElement = element.querySelector("input");
259 inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
260 if (event.isComposing) {
261 return;
262 }
263 if (event.key === "Enter") {
264 menu.close();
265 }
266 });
267 inputElement.value = name;
268 const descElement = element.querySelector("textarea");
269 inputElement.nextElementSibling.addEventListener("click", () => {
270 const descPanelElement = descElement.parentElement;
271 descPanelElement.classList.toggle("fn__none");
272 if (!descPanelElement.classList.contains("fn__none")) {
273 descElement.focus();
274 }
275 });
276 descElement.addEventListener("keydown", (event: KeyboardEvent) => {
277 if (event.isComposing) {
278 return;
279 }
280 if (event.key === "Enter") {
281 menu.close();
282 }
283 });
284 descElement.addEventListener("input", () => {
285 inputElement.nextElementSibling.setAttribute("aria-label", descElement.value ? escapeHtml(descElement.value) : window.siyuan.languages.addDesc);
286 });
287 }
288 });
289 menu.addItem({
290 id: "delete",
291 label: window.siyuan.languages.delete,
292 icon: "iconTrashcan",
293 click() {
294 confirmDialog(window.siyuan.languages.deleteOpConfirm, window.siyuan.languages.confirmDelete, () => {
295 let colOptions: { name: string, color: string }[] = [];
296 fields.find(column => {
297 if (column.id === colId) {
298 colOptions = column.options;
299 return true;
300 }
301 });
302 const newName = target.parentElement.dataset.name;
303 transaction(protyle, [{
304 action: "removeAttrViewColOption",
305 id: colId,
306 avID: data.id,
307 data: newName,
308 }, {
309 action: "doUpdateUpdated",
310 id: blockID,
311 data: dayjs().format("YYYYMMDDHHmmss"),
312 }], [{
313 action: "updateAttrViewColOptions",
314 id: colId,
315 avID: data.id,
316 data: colOptions
317 }]);
318 colOptions.find((item, index) => {
319 if (item.name === newName) {
320 colOptions.splice(index, 1);
321 return true;
322 }
323 });
324 const oldScroll = menuElement.querySelector(".b3-menu__items").scrollTop;
325 const selectedElement = menuElement.querySelector(".b3-chips");
326 const oldChipsHeight = selectedElement ? selectedElement.clientHeight : 0;
327 if (!cellElements) {
328 menuElement.innerHTML = getEditHTML({protyle, data, colId, isCustomAttr});
329 bindEditEvent({protyle, data, menuElement, isCustomAttr, blockID});
330 } else {
331 cellElements.forEach((cellElement: HTMLElement, index) => {
332 const rowID = getFieldIdByCellElement(cellElement, viewType);
333 if (viewType === "table" || isCustomAttr) {
334 cellElement = cellElements[index] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${cellElement.dataset.colId}"]`) ||
335 blockElement.querySelector(`.fn__flex-1[data-col-id="${cellElement.dataset.colId}"]`)) as HTMLElement;
336 } else {
337 cellElement = cellElements[index] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${cellElement.dataset.fieldId}"]`)) as HTMLElement;
338 }
339 cellValues[index].mSelect.find((item, selectIndex) => {
340 if (item.content === newName) {
341 cellValues[index].mSelect.splice(selectIndex, 1);
342 return true;
343 }
344 });
345 if (cellElement.classList.contains("custom-attr__avvalue")) {
346 cellElement.innerHTML = genAVValueHTML(cellValues[index]);
347 } else {
348 updateAttrViewCellAnimation(cellElement, cellValues[index]);
349 }
350 });
351 menuElement.innerHTML = getSelectHTML(fields, cellElements, false, blockElement);
352 bindSelectEvent(protyle, data, menuElement, cellElements, blockElement);
353 }
354 if (selectedElement) {
355 menuElement.querySelector(".b3-menu__items").scrollTop = oldScroll + (menuElement.querySelector(".b3-chips").clientHeight - oldChipsHeight);
356 }
357 }, undefined, true);
358 }
359 });
360 menu.addSeparator();
361 let html = "<div class=\"fn__flex fn__flex-wrap\" style=\"width: 238px\">";
362 Array.from(Array(14).keys()).forEach(index => {
363 html += `<button data-color="${index + 1}" class="color__square${parseInt(color) === index + 1 ? " color__square--current" : ""}" style="color: var(--b3-font-color${index + 1});background-color: var(--b3-font-background${index + 1});">A</button>`;
364 });
365 menu.addItem({
366 type: "empty",
367 iconHTML: "",
368 label: html + "</div>",
369 bind(element) {
370 element.addEventListener("click", (event) => {
371 const colorTarget = event.target as HTMLElement;
372 if (colorTarget.classList.contains("color__square") && !colorTarget.classList.contains("color__square--current")) {
373 element.querySelector(".color__square--current")?.classList.remove("color__square--current");
374 colorTarget.classList.add("color__square--current");
375 const newColor = colorTarget.getAttribute("data-color");
376 transaction(protyle, [{
377 action: "updateAttrViewColOption",
378 id: colId,
379 avID: data.id,
380 data: {
381 oldName: name,
382 newName: inputElement.value,
383 oldColor: color,
384 newColor,
385 newDesc: descElement.value
386 },
387 }, {
388 action: "doUpdateUpdated",
389 id: blockID,
390 data: dayjs().format("YYYYMMDDHHmmss"),
391 }], [{
392 action: "updateAttrViewColOption",
393 id: colId,
394 avID: data.id,
395 data: {
396 oldName: inputElement.value,
397 newName: name,
398 oldColor: newColor,
399 newColor: color,
400 newDesc: descElement.value
401 },
402 }]);
403
404 fields.find(column => {
405 if (column.id === colId) {
406 column.options.find((item) => {
407 if (item.name === name) {
408 item.name = inputElement.value;
409 item.color = newColor;
410 return true;
411 }
412 });
413 return true;
414 }
415 });
416 const oldScroll = menuElement.querySelector(".b3-menu__items").scrollTop;
417 if (!cellElements) {
418 menuElement.innerHTML = getEditHTML({protyle, data, colId, isCustomAttr});
419 bindEditEvent({protyle, data, menuElement, isCustomAttr, blockID});
420 } else {
421 cellElements.forEach((cellElement: HTMLElement, cellIndex) => {
422 const rowID = getFieldIdByCellElement(cellElement, viewType);
423 if (viewType === "table") {
424 cellElement = cellElements[cellIndex] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${cellElement.dataset.colId}"]`) ||
425 blockElement.querySelector(`.fn__flex-1[data-col-id="${cellElement.dataset.colId}"]`)) as HTMLElement;
426 } else {
427 cellElement = cellElements[cellIndex] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${cellElement.dataset.fieldId}"]`)) as HTMLElement;
428 }
429 cellValues[cellIndex].mSelect.find((item) => {
430 if (item.content === name) {
431 item.content = inputElement.value;
432 item.color = newColor;
433 return true;
434 }
435 });
436 if (cellElement.classList.contains("custom-attr__avvalue")) {
437 cellElement.innerHTML = genAVValueHTML(cellValues[cellIndex]);
438 } else {
439 updateAttrViewCellAnimation(cellElement, cellValues[cellIndex]);
440 }
441 });
442 menuElement.innerHTML = getSelectHTML(fields, cellElements, false, blockElement);
443 bindSelectEvent(protyle, data, menuElement, cellElements, blockElement);
444 }
445 menuElement.querySelector(".b3-menu__items").scrollTop = oldScroll;
446 name = inputElement.value;
447 desc = descElement.value;
448 color = newColor;
449 }
450 });
451 }
452 });
453 const rect = target.getBoundingClientRect();
454 menu.open({
455 x: rect.right,
456 y: rect.bottom,
457 w: rect.width,
458 h: rect.height,
459 });
460 const inputElement = window.siyuan.menus.menu.element.querySelector("input");
461 inputElement.select();
462 const descElement = window.siyuan.menus.menu.element.querySelector("textarea");
463};
464
465export const bindSelectEvent = (protyle: IProtyle, data: IAV, menuElement: HTMLElement, cellElements: HTMLElement[], blockElement: Element) => {
466 const inputElement = menuElement.querySelector("input");
467 const colId = getColId(cellElements[0], blockElement.getAttribute("data-av-type") as TAVView);
468 let colData: IAVColumn;
469 getFieldsByData(data).find((item: IAVColumn) => {
470 if (item.id === colId) {
471 colData = item;
472 return;
473 }
474 });
475 if (!colData.options) {
476 colData.options = [];
477 }
478 const listElement = menuElement.lastElementChild.lastElementChild as HTMLElement;
479 inputElement.addEventListener("input", (event: InputEvent) => {
480 if (event.isComposing) {
481 return;
482 }
483 listElement.innerHTML = filterSelectHTML(inputElement.value, colData.options);
484 });
485 inputElement.addEventListener("compositionend", () => {
486 listElement.innerHTML = filterSelectHTML(inputElement.value, colData.options);
487 });
488 inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
489 if (event.isComposing) {
490 return;
491 }
492 let currentElement = upDownHint(listElement, event, "b3-menu__item--current", listElement.firstElementChild);
493 if (event.key === "Enter") {
494 if (!currentElement) {
495 currentElement = menuElement.querySelector(".b3-menu__item--current");
496 }
497 if (currentElement.querySelector(".b3-menu__checked")) {
498 removeCellOption(protyle, cellElements, menuElement.querySelector(`.b3-chips .b3-chip[data-content="${escapeAttr(currentElement.dataset.name)}"]`), blockElement);
499 } else {
500 addColOptionOrCell(protyle, data, cellElements, currentElement, menuElement, blockElement);
501 }
502 } else if (event.key === "Backspace" && inputElement.value === "") {
503 removeCellOption(protyle, cellElements, inputElement.previousElementSibling as HTMLElement, blockElement);
504 }
505 });
506};
507
508export const addColOptionOrCell = (protyle: IProtyle, data: IAV, cellElements: HTMLElement[], currentElement: HTMLElement, menuElement: HTMLElement, blockElement: Element) => {
509 let hasSelected = false;
510 Array.from(menuElement.querySelectorAll(".b3-chips .b3-chip")).find((item: HTMLElement) => {
511 if (item.dataset.content === currentElement.dataset.name) {
512 hasSelected = true;
513 return true;
514 }
515 });
516 if (hasSelected) {
517 menuElement.querySelector("input").focus();
518 return;
519 }
520
521 const nodeElement = hasClosestBlock(cellElements[0]);
522 if (!nodeElement) {
523 cellElements.forEach((item, index) => {
524 const rowID = getFieldIdByCellElement(item, data.viewType);
525 if (data.viewType === "table" || isCustomAttr(item)) {
526 cellElements[index] = (blockElement.querySelector(`.av__row[data-id="${rowID}"] .av__cell[data-col-id="${item.dataset.colId}"]`) ||
527 blockElement.querySelector(`.fn__flex-1[data-col-id="${item.dataset.colId}"]`)) as HTMLElement;
528 } else {
529 cellElements[index] = (blockElement.querySelector(`.av__gallery-item[data-id="${rowID}"] .av__cell[data-field-id="${item.dataset.fieldId}"]`)) as HTMLElement;
530 }
531 });
532 }
533 const colId = getColId(cellElements[0], blockElement.getAttribute("data-av-type") as TAVView);
534 let colData: IAVColumn;
535 const fields = getFieldsByData(data);
536 fields.find((item: IAVColumn) => {
537 if (item.id === colId) {
538 colData = item;
539 if (!colData.options) {
540 colData.options = [];
541 }
542 return;
543 }
544 });
545
546 const cellDoOperations: IOperation[] = [];
547 const cellUndoOperations: IOperation[] = [];
548 let mSelectValue: IAVCellSelectValue[];
549 cellElements.forEach((item, index) => {
550 const rowID = getFieldIdByCellElement(item, data.viewType);
551 if (!rowID) {
552 return;
553 }
554 const cellValue: IAVCellValue = cellValues[index];
555 const oldValue = JSON.parse(JSON.stringify(cellValue));
556 if (index === 0) {
557 if (colData.type === "mSelect") {
558 let hasOption = false;
559 cellValue.mSelect.find((item) => {
560 if (item.content === currentElement.dataset.name) {
561 hasOption = true;
562 return true;
563 }
564 });
565 if (!hasOption) {
566 cellValue.mSelect.push({
567 color: currentElement.dataset.color,
568 content: currentElement.dataset.name
569 });
570 }
571 } else {
572 cellValue.mSelect = [{
573 color: currentElement.dataset.color,
574 content: currentElement.dataset.name
575 }];
576 }
577 mSelectValue = cellValue.mSelect;
578 } else {
579 cellValue.mSelect = mSelectValue;
580 }
581 cellDoOperations.push({
582 action: "updateAttrViewCell",
583 id: cellValue.id,
584 keyID: colId,
585 rowID,
586 avID: data.id,
587 data: cellValue
588 });
589 cellUndoOperations.push({
590 action: "updateAttrViewCell",
591 id: cellValue.id,
592 keyID: colId,
593 rowID,
594 avID: data.id,
595 data: oldValue
596 });
597 if (item.classList.contains("custom-attr__avvalue")) {
598 item.innerHTML = genAVValueHTML(cellValue);
599 } else {
600 updateAttrViewCellAnimation(item, cellValue);
601 }
602 });
603
604 if (currentElement.querySelector(".b3-menu__accelerator")) {
605 colData.options.push({
606 color: currentElement.dataset.color,
607 name: currentElement.dataset.name
608 });
609 cellDoOperations.splice(0, 0, {
610 action: "updateAttrViewColOptions",
611 id: colId,
612 avID: data.id,
613 data: colData.options
614 });
615 cellDoOperations.push({
616 action: "doUpdateUpdated",
617 id: blockElement.getAttribute("data-node-id"),
618 data: dayjs().format("YYYYMMDDHHmmss"),
619 });
620 transaction(protyle, cellDoOperations, [{
621 action: "removeAttrViewColOption",
622 id: colId,
623 avID: data.id,
624 data: currentElement.dataset.name,
625 }]);
626 } else {
627 cellDoOperations.push({
628 action: "doUpdateUpdated",
629 id: blockElement.getAttribute("data-node-id"),
630 data: dayjs().format("YYYYMMDDHHmmss"),
631 });
632 transaction(protyle, cellDoOperations, cellUndoOperations);
633 }
634 if (colData.type === "select") {
635 blockElement.setAttribute("data-rendering", "true");
636 menuElement.parentElement.dispatchEvent(new CustomEvent("click", {detail: "close"}));
637 } else {
638 const oldScroll = menuElement.querySelector(".b3-menu__items").scrollTop;
639 const oldChipsHeight = menuElement.querySelector(".b3-chips").clientHeight;
640 menuElement.innerHTML = getSelectHTML(fields, cellElements, false, blockElement);
641 bindSelectEvent(protyle, data, menuElement, cellElements, blockElement);
642 menuElement.querySelector("input").focus();
643 menuElement.querySelector(".b3-menu__items").scrollTop = oldScroll + (menuElement.querySelector(".b3-chips").clientHeight - oldChipsHeight);
644 }
645};
646
647export const getSelectHTML = (fields: IAVColumn[], cellElements: HTMLElement[], init = false, blockElement: Element) => {
648 if (init) {
649 // 快速选中后如果 render 了再使用 genCellValueByElement 获取的元素和当前选中的不一致, https://github.com/siyuan-note/siyuan/issues/11268
650 cellValues = [];
651 const isCustomAttr = cellElements[0].classList.contains("custom-attr__avvalue");
652 cellElements.forEach(item => {
653 cellValues.push(genCellValueByElement(isCustomAttr ? item.dataset.type as TAVCol : getTypeByCellElement(item), item));
654 });
655 }
656 const colId = getColId(cellElements[0], blockElement.getAttribute("data-av-type") as TAVView);
657 const colData = fields.find(item => {
658 if (item.id === colId) {
659 return item;
660 }
661 });
662 let selectedHTML = "";
663 const selected: string[] = [];
664 cellValues[0].mSelect?.forEach((item) => {
665 selected.push(item.content);
666 selectedHTML += `<div class="b3-chip b3-chip--middle" data-content="${escapeAttr(item.content)}" style="white-space: nowrap;max-width:100%;background-color:var(--b3-font-background${item.color});color:var(--b3-font-color${item.color})"><span class="fn__ellipsis">${escapeHtml(item.content)}</span><svg class="b3-chip__close" data-type="removeCellOption"><use xlink:href="#iconCloseRound"></use></svg></div>`;
667 });
668
669 return `<div class="b3-menu__items">
670<div class="b3-chips" style="max-width: 50vw">
671 ${selectedHTML}
672 <input>
673</div>
674<div>${filterSelectHTML("", colData.options, selected)}</div>
675</div>`;
676};
677
678export const mergeAddOption = (column: IAVColumn, cellValue: IAVCellValue, avID: string) => {
679 const doOperations: IOperation[] = [];
680 const undoOperations: IOperation[] = [];
681 cellValue.mSelect.forEach((item: IAVCellSelectValue) => {
682 if (!column.options) {
683 column.options = [];
684 }
685 const needAdd = column.options.find((option: {
686 name: string,
687 color: string,
688 }) => {
689 if (option.name === item.content) {
690 item.color = option.color;
691 return true;
692 }
693 });
694 if (!needAdd) {
695 const newColor = ((column.options?.length || 0) % 14 + 1).toString();
696 column.options.push({
697 name: item.content,
698 color: newColor
699 });
700 item.color = newColor;
701 doOperations.push({
702 action: "updateAttrViewColOptions",
703 id: column.id,
704 avID,
705 data: column.options
706 });
707 undoOperations.push({
708 action: "removeAttrViewColOption",
709 id: column.id,
710 avID,
711 data: item.content,
712 });
713 }
714 });
715 return {
716 doOperations,
717 undoOperations
718 };
719};