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 = `
+
+
+ ${galleryHTML}
+
+
${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;