A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
at lambda-fork/main 719 lines 33 kB view raw
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};