diff --git a/app/src/assets/scss/business/_av.scss b/app/src/assets/scss/business/_av.scss index 1874f249e..cca52d200 100644 --- a/app/src/assets/scss/business/_av.scss +++ b/app/src/assets/scss/business/_av.scss @@ -271,6 +271,22 @@ } } + &__gallery { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 16px; + width: 100%; + + &-item { + border: 1px solid var(--b3-border-color); + border-radius: var(--b3-border-radius); + + &--select { + + } + } + } + &__cell { box-sizing: border-box; position: relative; diff --git a/app/src/protyle/render/av/gallery/render.ts b/app/src/protyle/render/av/gallery/render.ts index e69de29bb..f1bc0febb 100644 --- a/app/src/protyle/render/av/gallery/render.ts +++ b/app/src/protyle/render/av/gallery/render.ts @@ -0,0 +1,251 @@ +import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../../../util/hasClosest"; +import {Constants} from "../../../../constants"; +import {fetchPost} from "../../../../util/fetch"; +import {escapeAriaLabel, escapeHtml} from "../../../../util/escape"; +import {unicode2Emoji} from "../../../../emoji"; +import {renderCell} from "../cell"; +import {focusBlock} from "../../../util/selection"; +import {electronUndo} from "../../../undo"; +import {addClearButton} from "../../../../util/addClearButton"; +import {updateSearch} from "../render"; + +export const renderGallery = (options: { + blockElement: HTMLElement, + protyle: IProtyle, + cb?: () => void, + viewID?: string, + renderAll: boolean +}) => { + const alignSelf = options.blockElement.style.alignSelf; + if (options.blockElement.firstElementChild.innerHTML === "") { + options.blockElement.style.alignSelf = ""; + options.blockElement.firstElementChild.outerHTML = ``; + } + + const selectItemIds: string[] = []; + options.blockElement.querySelectorAll(".av__gallery-item--select").forEach(rowItem => { + const rowId = rowItem.getAttribute("data-id"); + if (rowId) { + selectItemIds.push(rowId); + } + }); + const created = options.protyle.options.history?.created; + const snapshot = options.protyle.options.history?.snapshot; + let newViewID = options.blockElement.getAttribute(Constants.CUSTOM_SY_AV_VIEW) || ""; + if (typeof options.viewID === "string") { + const viewTabElement = options.blockElement.querySelector(`.av__views > .layout-tab-bar > .item[data-id="${options.viewID}"]`) as HTMLElement; + if (viewTabElement) { + options.blockElement.dataset.pageSize = viewTabElement.dataset.page; + } + newViewID = options.viewID; + fetchPost("/api/av/setDatabaseBlockView", { + id: options.blockElement.dataset.nodeId, + avID: options.blockElement.dataset.avId, + viewID: options.viewID + }); + options.blockElement.setAttribute(Constants.CUSTOM_SY_AV_VIEW, newViewID); + } + let searchInputElement = options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement; + const isSearching = searchInputElement && document.activeElement.isSameNode(searchInputElement); + const query = searchInputElement?.value || ""; + fetchPost(created ? "/api/av/renderHistoryAttributeView" : (snapshot ? "/api/av/renderSnapshotAttributeView" : "/api/av/renderAttributeView"), { + id: options.blockElement.getAttribute("data-av-id"), + created, + snapshot, + pageSize: parseInt(options.blockElement.dataset.pageSize) || undefined, + viewID: newViewID, + query: query.trim() + }, (response) => { + const data = response.data.view as IAVTable; + if (!options.blockElement.dataset.pageSize) { + options.blockElement.dataset.pageSize = data.pageSize.toString(); + } + let galleryHTML = ""; + // body + debugger + data.rows.forEach((row: IAVRow, rowIndex: number) => { + row.cells.forEach((cell, index) => { + if (data.columns[index].hidden) { + return; + } + // https://github.com/siyuan-note/siyuan/issues/10262 + let checkClass = ""; + if (cell.valueType === "checkbox") { + checkClass = cell.value?.checkbox?.checked ? " av__cell-check" : " av__cell-uncheck"; + } + galleryHTML += `
${renderCell(cell.value, rowIndex)}
`; + }); + }); + let tabHTML = ""; + let viewData: IAVView; + response.data.views.forEach((item: IAVView) => { + tabHTML += `
+ ${item.icon ? unicode2Emoji(item.icon, "item__graphic", true) : ''} + ${escapeHtml(item.name)} +
`; + if (item.id === response.data.viewID) { + viewData = item; + } + }); + if (options.renderAll) { + let hasFilter = false; + data.columns.forEach((item) => { + if (!hasFilter) { + data.filters.find(filterItem => { + if (filterItem.value.type === item.type && item.id === filterItem.column) { + hasFilter = true; + return true; + } + }); + } + }); + options.blockElement.firstElementChild.outerHTML = `
+
+
+
+ ${tabHTML} +
+
+ + + +
+
+ + + + ${response.data.views.length} + +
+ + + +
+ + + +
+ +
+ +
+
+ + + +
+ + + +
+ ${response.data.isMirror ? ` +
` : ""} +
+
${response.data.name || ""}
+
+
+ +
${Constants.ZWSP}
+
`; + } else { + options.blockElement.firstElementChild.querySelector(".av__gallery").innerHTML = galleryHTML; + } + options.blockElement.setAttribute("data-render", "true"); + if (alignSelf) { + options.blockElement.style.alignSelf = alignSelf; + } + selectItemIds.forEach((selectRowId) => { + const rowElement = options.blockElement.querySelector(`.av__gallery-cell[data-id="${selectRowId}"]`) as HTMLElement; + if (rowElement) { + rowElement.classList.add("av__gallery-cell--select"); + } + }); + if (getSelection().rangeCount > 0) { + // 修改表头后光标重新定位 + const range = getSelection().getRangeAt(0); + if (!hasClosestByClassName(range.startContainer, "av__title")) { + const blockElement = hasClosestBlock(range.startContainer); + if (blockElement && options.blockElement.isSameNode(blockElement) && !isSearching) { + focusBlock(options.blockElement); + } + } + } + options.blockElement.querySelector(".layout-tab-bar").scrollLeft = (options.blockElement.querySelector(".layout-tab-bar .item--focus") as HTMLElement).offsetLeft; + if (options.cb) { + options.cb(); + } + if (!options.renderAll) { + return; + } + const viewsElement = options.blockElement.querySelector(".av__views") as HTMLElement; + searchInputElement = options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement; + searchInputElement.value = query || ""; + if (isSearching) { + searchInputElement.focus(); + } + searchInputElement.addEventListener("compositionstart", (event: KeyboardEvent) => { + event.stopPropagation(); + }); + searchInputElement.addEventListener("keydown", (event: KeyboardEvent) => { + if (event.isComposing) { + return; + } + electronUndo(event); + }); + searchInputElement.addEventListener("input", (event: KeyboardEvent) => { + event.stopPropagation(); + if (event.isComposing) { + return; + } + if (searchInputElement.value || document.activeElement.isSameNode(searchInputElement)) { + viewsElement.classList.add("av__views--show"); + } else { + viewsElement.classList.remove("av__views--show"); + } + updateSearch(options.blockElement, options.protyle); + }); + searchInputElement.addEventListener("compositionend", () => { + updateSearch(options.blockElement, options.protyle); + }); + searchInputElement.addEventListener("blur", (event: KeyboardEvent) => { + if (event.isComposing) { + return; + } + if (!searchInputElement.value) { + viewsElement.classList.remove("av__views--show"); + searchInputElement.style.width = "0"; + searchInputElement.style.paddingLeft = "0"; + searchInputElement.style.paddingRight = "0"; + } + }); + addClearButton({ + inputElement: searchInputElement, + right: 0, + width: "1em", + height: searchInputElement.clientHeight, + clearCB() { + viewsElement.classList.remove("av__views--show"); + searchInputElement.style.width = "0"; + searchInputElement.style.paddingLeft = "0"; + searchInputElement.style.paddingRight = "0"; + focusBlock(options.blockElement); + updateSearch(options.blockElement, options.protyle); + } + }); + }); +}; diff --git a/app/src/protyle/render/av/render.ts b/app/src/protyle/render/av/render.ts index 72440faaa..91f4c057f 100644 --- a/app/src/protyle/render/av/render.ts +++ b/app/src/protyle/render/av/render.ts @@ -14,6 +14,7 @@ import {escapeAriaLabel, escapeAttr, escapeHtml} from "../../../util/escape"; import {electronUndo} from "../../undo"; import {isInAndroid, isInHarmony, isInIOS} from "../../util/compatibility"; import {isMobile} from "../../../util/functions"; +import {renderGallery} from "./gallery/render"; export const avRender = (element: Element, protyle: IProtyle, cb?: () => void, viewID?: string, renderAll = true) => { let avElements: Element[] = []; @@ -35,7 +36,10 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: () => void, v e.classList.add("av--touch"); } - // TODO + if (e.getAttribute("data-av-type") === "gallery") { + renderGallery({blockElement: e, protyle, cb, viewID, renderAll}); + return; + } const alignSelf = e.style.alignSelf; if (e.firstElementChild.innerHTML === "") { @@ -85,7 +89,7 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: () => void, v e.dataset.pageSize = viewTabElement.dataset.page; } newViewID = viewID; - fetchPost("/api/av/setDatabaseBlockView", {id: e.dataset.nodeId, viewID}); + fetchPost("/api/av/setDatabaseBlockView", {id: e.dataset.nodeId, viewID, avID: e.dataset.avId,}); e.setAttribute(Constants.CUSTOM_SY_AV_VIEW, newViewID); } let searchInputElement = e.querySelector('[data-type="av-search"]') as HTMLInputElement; @@ -432,7 +436,7 @@ ${cell.color ? `color:${cell.color};` : ""}">${renderCell(cell.value, rowIndex)} let searchTimeout: number; -const updateSearch = (e: HTMLElement, protyle: IProtyle) => { +export const updateSearch = (e: HTMLElement, protyle: IProtyle) => { clearTimeout(searchTimeout); searchTimeout = window.setTimeout(() => { e.removeAttribute("data-render"); diff --git a/app/src/protyle/render/av/row.ts b/app/src/protyle/render/av/row.ts index bad400d14..5ab01a4b0 100644 --- a/app/src/protyle/render/av/row.ts +++ b/app/src/protyle/render/av/row.ts @@ -252,6 +252,9 @@ ${getTypeByCellElement(item) === "block" ? ' data-detached="true"' : ""}> { + if (blockElement.dataset.avType !== "table") { + return; + } // 只读模式下也需固定 https://github.com/siyuan-note/siyuan/issues/11338 const scrollRect = blockElement.querySelector(".av__scroll").getBoundingClientRect(); const headerElement = blockElement.querySelector(".av__row--header") as HTMLElement;