Jelajahi Sumber

:art: fix https://github.com/siyuan-note/siyuan/pull/9617

Vanessa 1 tahun lalu
induk
melakukan
70e6a3182d

+ 6 - 2
app/appearance/themes/daylight/theme.css

@@ -55,6 +55,10 @@
     --b3-tooltips-color: var(--b3-theme-background-light);
     --b3-tooltips-shadow: 0 2px 8px rgba(0, 0, 0, .1);
 
+    /* av */
+    --b3-av-hover: #e8e8e9;
+    --b3-av-background-hl: #e8eefc;
+
     /* 为空提示 */
     --b3-empty-color: var(--b3-theme-on-surface-light);
 
@@ -92,11 +96,11 @@
     --b3-font-background5: #e2e3e4;
     --b3-font-background6: #acd0fc;
     --b3-font-background7: #fdeed6;
-    --b3-font-background8: rgba(255, 193, 153, 0.5);
+    --b3-font-background8: #fae1cf;
     --b3-font-background9: #fdd5e7;
     --b3-font-background10: #e6c7e6;
     --b3-font-background11: #def0d9;
-    --b3-font-background12: rgba(253, 198, 200, 0.5);
+    --b3-font-background12: #fae3e4;
     --b3-font-background13: var(--b3-theme-on-background);
 
     /* 动画效果 */

+ 4 - 0
app/appearance/themes/midnight/theme.css

@@ -55,6 +55,10 @@
     --b3-tooltips-color: var(--b3-theme-on-surface-light);
     --b3-tooltips-shadow: 0 2px 8px rgba(0, 0, 0, .3);
 
+    /* av */
+    --b3-av-hover: #2a2a2a;
+    --b3-av-background-hl: #28324e;
+
     /* 为空提示 */
     --b3-empty-color: var(--b3-theme-on-surface);
 

+ 69 - 26
app/src/assets/scss/business/_av.scss

@@ -11,6 +11,7 @@
   &__pulse {
     width: 70%;
     height: 23px;
+    display: block;
     position: relative;
     overflow: hidden;
     background: var(--b3-border-color);
@@ -29,10 +30,6 @@
     }
   }
 
-  &:hover .av__row--footer > .av__calc--show {
-    opacity: 1;
-  }
-
   &__header {
     top: -43px;
     z-index: 2;
@@ -61,7 +58,7 @@
     bottom: 0;
     height: 30px;
     padding: 0 5px;
-    background-color: var(--b3-theme-background);
+    background-color: var(--av-background);
   }
 
   &__gutters {
@@ -116,7 +113,9 @@
     }
 
     &--select {
-      background-color: var(--b3-theme-primary-lightest);
+      .av__cell {
+        background-color: var(--b3-av-hover);
+      }
 
       .av__firstcol svg {
         opacity: 1;
@@ -124,39 +123,45 @@
     }
 
     &--header {
-      z-index: 1;
+      z-index: 3;
 
       .av__cell {
         padding: 0;
         transition: background 20ms ease-in 0s;
         display: flex;
+        overflow: inherit; // 保证列宽和顺序调整的拖拽点样式
 
         &:hover {
-          background-color: var(--b3-list-icon-hover);
+          background-color: var(--b3-av-hover);
         }
       }
     }
 
     &--header,
     &--footer {
-      background-color: var(--b3-theme-background);
+      background-color: var(--av-background);
     }
 
     &--footer {
       display: flex;
       border-top: 1px solid var(--b3-theme-surface-lighter);
       color: var(--b3-theme-on-surface);
+      position: relative;
+      z-index: 2;
 
-      &:hover > .av__calc,
-      &.av__row--show > .av__calc {
+      &:hover .av__calc,
+      &.av__row--show .av__calc {
         opacity: 1;
       }
 
-      & > .av__calc {
-        transition: opacity 150ms linear, background 20ms ease-in 0s;
+      .av__colsticky {
+        background-color: var(--av-background); // 保证盯住时无计算结果的列不被覆盖
+      }
+
+      .av__calc {
         display: flex;
         align-items: center;
-        padding: 5px 5px 5px 7px;
+        padding: 5px;
         border-right: 1px;
         flex-direction: row-reverse;
         box-sizing: border-box;
@@ -181,29 +186,32 @@
         }
 
         &:hover {
-          background-color: var(--b3-list-icon-hover);
+          background-color: var(--b3-av-hover);
         }
       }
     }
 
     &--add {
       color: var(--b3-theme-on-surface);
-      padding: 5px 5px 5px 7px;
-      display: flex;
-      align-items: center;
       transition: background 20ms ease-in 0s;
       font-size: 87.5%;
+      align-items: center;
+      display: flex;
+
+      .av__colsticky {
+        align-items: center;
 
-      svg {
-        height: 12px;
-        width: 12px;
-        color: var(--b3-theme-on-surface);
-        margin-right: 5px;
-        flex-shrink: 0;
+        svg {
+          height: 14px;
+          width: 14px;
+          color: var(--b3-theme-on-surface);
+          padding: 10px 10px 10px 5px;
+          flex-shrink: 0;
+        }
       }
 
       &:hover {
-        background-color: var(--b3-list-icon-hover);
+        background-color: var(--b3-av-hover);
       }
     }
   }
@@ -300,6 +308,18 @@
     }
   }
 
+  &__colsticky {
+    position: sticky;
+    left: 0;
+    z-index: 1;
+    display: flex;
+
+    &.av__firstcol,
+    & > div {
+      background-color: var(--av-background);
+    }
+  }
+
   &__widthdrag {
     position: absolute;
     cursor: col-resize;
@@ -382,7 +402,30 @@
 .protyle-wysiwyg--select,
 .protyle-wysiwyg--hl {
   .av__row--header,
-  .av__row--footer {
+  .av__row--footer,
+  .av__row--footer .av__colsticky,
+  .av__row--select .av__cell,
+  .av__colsticky.av__firstcol,
+  .av__colsticky > div,
+  .av__counter {
+    background-color: var(--b3-av-background-hl);
+  }
+}
+
+.dragover__top,
+.dragover__bottom {
+  .av__colsticky {
+    z-index: 0;
+
+    & > div {
+      background-color: transparent;
+    }
+  }
+}
+
+.dragover__bottom + .av__row,
+.av__row:has(+ .dragover__top) {
+  .av__colsticky > div {
     background-color: transparent;
   }
 }

+ 2 - 0
app/src/assets/scss/business/_custom.scss

@@ -38,6 +38,8 @@
   .block__icons {
     min-height: auto;
     padding: 4px 8px;
+    font-size: 100%;
+    border-bottom: 0;
   }
 
   .b3-text-field--text {

+ 1 - 1
app/src/block/popover.ts

@@ -22,7 +22,7 @@ export const initBlockPopover = (app: App) => {
         if (aElement) {
             let tip = aElement.getAttribute("aria-label") || aElement.getAttribute("data-inline-memo-content");
             if (aElement.classList.contains("av__celltext")) {
-                if (aElement.scrollWidth > aElement.parentElement.clientWidth - 11) {
+                if (aElement.offsetWidth > aElement.parentElement.clientWidth - 11) {
                     if (aElement.querySelector(".av__cellicon")) {
                         tip = `${aElement.firstChild.textContent} ➡️ ${aElement.lastChild.textContent}`;
                     } else {

+ 10 - 5
app/src/boot/globalEvent/keydown.ts

@@ -1270,18 +1270,23 @@ export const windowKeyDown = (app: App, event: KeyboardEvent) => {
             }
         }
 
-        if (window.siyuan.dialogs.length > 0) {
-            window.siyuan.dialogs[window.siyuan.dialogs.length - 1].destroy();
-            return;
-        }
-
         // 需放在 menus 后,否则资源列中添加资源会先关闭菜单
+        // 需放在 dialog 前,否则属性面板中修改日期会先关闭 dialog,只剩修改界面
         const avElement = document.querySelector(".av__panel");
         if (avElement) {
+            const selectCellElement = document.querySelector(".av__cell--select")
+            if (selectCellElement) {
+                focusBlock(hasClosestBlock(selectCellElement) as HTMLElement);
+            }
             avElement.remove();
             return;
         }
 
+        if (window.siyuan.dialogs.length > 0) {
+            window.siyuan.dialogs[window.siyuan.dialogs.length - 1].destroy();
+            return;
+        }
+
         // remove blockpopover
         const maxEditLevels: { [key: string]: number } = {oid: 0};
         window.siyuan.blockPanels.forEach((item) => {

+ 1 - 1
app/src/menus/Menu.ts

@@ -1,4 +1,4 @@
-import {getEventName, isCtrl, updateHotkeyTip} from "../protyle/util/compatibility";
+import {getEventName, updateHotkeyTip} from "../protyle/util/compatibility";
 import {setPosition} from "../util/setPosition";
 import {hasClosestByClassName} from "../protyle/util/hasClosest";
 import {isMobile} from "../util/functions";

+ 20 - 10
app/src/protyle/render/av/action.ts

@@ -3,7 +3,7 @@ import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName} from "../
 import {transaction} from "../../wysiwyg/transaction";
 import {openEditorTab} from "../../../menus/util";
 import {copySubMenu} from "../../../menus/commonMenuItem";
-import {openCalcMenu, popTextCell} from "./cell";
+import {getTypeByCellElement, openCalcMenu, popTextCell} from "./cell";
 import {getColIconByType, showColMenu} from "./col";
 import {insertAttrViewBlockAnimation, updateHeader} from "./row";
 import {emitOpenMenu} from "../../../plugin/EventBus";
@@ -70,7 +70,10 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle
 
     const gutterElement = hasClosestByClassName(event.target, "ariaLabel");
     if (gutterElement && gutterElement.parentElement.classList.contains("av__gutters")) {
-        const rowElement = gutterElement.parentElement.parentElement;
+        const rowElement = hasClosestByClassName(gutterElement, "av__row");
+        if (!rowElement) {
+            return
+        }
         if (gutterElement.dataset.action === "add") {
             const avID = blockElement.getAttribute("data-av-id");
             const srcIDs = [Lute.NewNodeID()];
@@ -200,16 +203,24 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle
     }
 
     const cellElement = hasClosestByClassName(event.target, "av__cell");
-    if (cellElement && !cellElement.parentElement.classList.contains("av__row--header")) {
-        const type = cellElement.parentElement.parentElement.firstElementChild.querySelector(`[data-col-id="${cellElement.getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol;
+    if (cellElement && !hasClosestByClassName(cellElement, "av__row--header")) {
+        const scrollElement = hasClosestByClassName(cellElement, "av__scroll")
+        if (!scrollElement) {
+            return
+        }
+        const rowElement = hasClosestByClassName(cellElement, "av__row");
+        if (!rowElement) {
+            return;
+        }
+        const type = getTypeByCellElement(cellElement);
         if (type === "updated" || type === "created" || (type === "block" && !cellElement.getAttribute("data-detached"))) {
-            selectRow(cellElement.parentElement.querySelector(".av__firstcol"), "toggle");
+            selectRow(rowElement.querySelector(".av__firstcol"), "toggle");
         } else {
-            cellElement.parentElement.parentElement.querySelectorAll(".av__row--select").forEach(item => {
+            scrollElement.querySelectorAll(".av__row--select").forEach(item => {
                 item.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconUncheck");
                 item.classList.remove("av__row--select");
             });
-            updateHeader(cellElement.parentElement);
+            updateHeader(rowElement);
             popTextCell(protyle, [cellElement]);
         }
         event.preventDefault();
@@ -307,7 +318,7 @@ export const avContextmenu = (protyle: IProtyle, rowElement: HTMLElement, positi
             updateHeader(blockElement.querySelector(".av__row"));
         }
     });
-    if (rowIds.length === 1) {
+    if (rowIds.length === 1 && !rowElements[0].querySelector('[data-detached="true"]')) {
         menu.addSeparator();
         openEditorTab(protyle.app, rowIds[0]);
         menu.addItem({
@@ -410,8 +421,7 @@ export const updateAVName = (protyle: IProtyle, blockElement: Element) => {
 };
 
 export const updateAttrViewCellAnimation = (cellElement: HTMLElement) => {
-    cellElement.style.opacity = "0.38";
-    cellElement.style.backgroundColor = "var(--b3-theme-surface-light)";
+    cellElement.style.backgroundColor = "var(--b3-av-hover)";
 };
 
 export const removeAttrViewColAnimation = (blockElement: Element, id: string) => {

+ 4 - 3
app/src/protyle/render/av/asset.ts

@@ -13,6 +13,7 @@ import {previewImage} from "../../preview/image";
 import {genAVValueHTML} from "./blockAttr";
 import {hideMessage, showMessage} from "../../../dialog/message";
 import {fetchPost} from "../../../util/fetch";
+import {hasClosestByClassName} from "../../util/hasClosest";
 
 export const bindAssetEvent = (options: {
     protyle: IProtyle,
@@ -49,7 +50,7 @@ export const bindAssetEvent = (options: {
 
 export const getAssetHTML = (data: IAVTable, cellElements: HTMLElement[]) => {
     const cellId = cellElements[0].dataset.id;
-    const rowId = cellElements[0].parentElement.dataset.id;
+    const rowId = (hasClosestByClassName(cellElements[0], "av__row") as HTMLElement).dataset.id;
     let cellData: IAVCell;
     data.rows.find(row => {
         if (row.id === rowId) {
@@ -112,7 +113,7 @@ export const updateAssetCell = (options: {
     removeContent?: string
 }) => {
     let cellIndex: number;
-    Array.from(options.cellElements[0].parentElement.querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
+    Array.from((hasClosestByClassName(options.cellElements[0], "av__row") as HTMLElement).querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
         if (item.dataset.id === options.cellElements[0].dataset.id) {
             cellIndex = index;
             return true;
@@ -124,7 +125,7 @@ export const updateAssetCell = (options: {
     let newValue: IAVCellAssetValue[] = [];
     options.cellElements.forEach((item, elementIndex) => {
         let cellData: IAVCell;
-        const rowID = item.parentElement.dataset.id;
+        const rowID = (hasClosestByClassName(item, "av__row") as HTMLElement).dataset.id;
         options.data.view.rows.find(row => {
             if (row.id === rowID) {
                 if (typeof cellIndex === "number") {

+ 1 - 1
app/src/protyle/render/av/blockAttr.ts

@@ -90,7 +90,7 @@ export const renderAVAttribute = (element: HTMLElement, id: string, protyle?: IP
     <span>${table.avName || window.siyuan.languages.database}</span>
 </div>`;
             table.keyValues?.forEach(item => {
-                html += `<div class="block__icons" data-id="${id}">
+                html += `<div class="block__icons av__row" data-id="${id}">
     <div class="block__logo">
         <svg><use xlink:href="#${getColIconByType(item.key.type)}"></use></svg>
         <span>${item.key.name}</span>

+ 62 - 23
app/src/protyle/render/av/cell.ts

@@ -175,9 +175,13 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => {
     if (!blockElement) {
         return;
     }
-    calcElement.parentElement.classList.add("av__row--show");
+    const rowElement = hasClosestByClassName(calcElement, "av__row--footer");
+    if (!rowElement) {
+        return;
+    }
+    rowElement.classList.add("av__row--show");
     const menu = new Menu("av-calc", () => {
-        calcElement.parentElement.classList.remove("av__row--show");
+        rowElement.classList.remove("av__row--show");
     });
     if (menu.isOpen) {
         return;
@@ -346,14 +350,28 @@ export const openCalcMenu = (protyle: IProtyle, calcElement: HTMLElement) => {
     menu.open({x: calcRect.left, y: calcRect.bottom, h: calcRect.height});
 };
 
-export const cellScrollIntoView = (blockElement: HTMLElement, cellRect: DOMRect, onlyHeight = true) => {
+export const cellScrollIntoView = (blockElement: HTMLElement, cellElement: Element, onlyHeight = true) => {
+    const cellRect = cellElement.getBoundingClientRect();
     if (!onlyHeight) {
         const avScrollElement = blockElement.querySelector(".av__scroll");
-        const avScrollRect = avScrollElement.getBoundingClientRect();
-        if (avScrollRect.left > cellRect.left) {
-            avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - avScrollRect.left;
-        } else if (avScrollRect.right < cellRect.right) {
-            avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.right - avScrollRect.right;
+        if (avScrollElement) {
+            const avScrollRect = avScrollElement.getBoundingClientRect();
+            if (avScrollRect.right < cellRect.right) {
+                avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.right - avScrollRect.right;
+            } else {
+                const rowElement = hasClosestByClassName(cellElement, "av__row");
+                if (rowElement) {
+                    const stickyElement = rowElement.querySelector(".av__colsticky");
+                    if (stickyElement) {
+                        const stickyRight = stickyElement.getBoundingClientRect().right
+                        if (stickyRight > cellRect.left) {
+                            avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - stickyRight;
+                        }
+                    } else if (avScrollRect.left > cellRect.left) {
+                        avScrollElement.scrollLeft = avScrollElement.scrollLeft + cellRect.left - avScrollRect.left;
+                    }
+                }
+            }
         }
     }
     if (!blockElement.querySelector(".av__header")) {
@@ -377,9 +395,17 @@ export const cellScrollIntoView = (blockElement: HTMLElement, cellRect: DOMRect,
     }
 };
 
+export const getTypeByCellElement = (cellElement: Element) => {
+    const scrollElement = hasClosestByClassName(cellElement, "av__scroll")
+    if (!scrollElement) {
+        return;
+    }
+    return scrollElement.querySelector(".av__row--header").querySelector(`[data-col-id="${cellElement.getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol;
+}
+
 export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type?: TAVCol) => {
     if (!type) {
-        type = cellElements[0].parentElement.parentElement.firstElementChild.querySelector(`[data-col-id="${cellElements[0].getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol;
+        type = getTypeByCellElement(cellElements[0])
     }
     if (type === "updated" || type === "created") {
         return;
@@ -396,7 +422,7 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type
             contentElement.scrollTop = contentElement.scrollTop + cellRect.top - 110;
         }
         /// #else
-        cellScrollIntoView(blockElement, cellRect);
+        cellScrollIntoView(blockElement, cellElements[0], false);
         /// #endif
     }
     cellRect = cellElements[0].getBoundingClientRect();
@@ -406,14 +432,17 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type
         html = `<textarea ${style} class="b3-text-field">${cellElements[0].firstElementChild.textContent}</textarea>`;
     } else if (type === "number") {
         html = `<input type="number" value="${cellElements[0].firstElementChild.getAttribute("data-content")}" ${style} class="b3-text-field">`;
-    } else if (["select", "mSelect"].includes(type) && blockElement) {
-        openMenuPanel({protyle, blockElement, type: "select", cellElements});
-        return;
-    } else if (type === "mAsset" && blockElement) {
-        openMenuPanel({protyle, blockElement, type: "asset", cellElements});
-        return;
-    } else if (type === "date" && blockElement) {
-        openMenuPanel({protyle, blockElement, type: "date", cellElements});
+    } else if (blockElement) {
+        if (["select", "mSelect"].includes(type)) {
+            openMenuPanel({protyle, blockElement, type: "select", cellElements});
+        } else if (type === "mAsset") {
+            openMenuPanel({protyle, blockElement, type: "asset", cellElements});
+        } else if (type === "date") {
+            openMenuPanel({protyle, blockElement, type: "date", cellElements});
+        }
+        if (!hasClosestByClassName(cellElements[0], "custom-attr")) {
+            cellElements[0].classList.add("av__cell--select");
+        }
         return;
     }
     window.siyuan.menus.menu.remove();
@@ -459,12 +488,16 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[], type
 };
 
 const updateCellValue = (protyle: IProtyle, type: TAVCol, cellElements: HTMLElement[]) => {
+    const rowElement = hasClosestByClassName(cellElements[0], "av__row");
+    if (!rowElement) {
+        return;
+    }
     if (!document.contains(cellElements[0]) && cellElements.length === 1) {
         // 原始 cell 已被更新
-        const avid = cellElements[0].parentElement.dataset.avid;
+        const avid = rowElement.dataset.avid;
         if (avid) {
             // 新增行后弹出的输入框
-            const previousId = cellElements[0].parentElement.dataset.previousId;
+            const previousId = rowElement.dataset.previousId;
             cellElements[0] = protyle.wysiwyg.element.querySelector(previousId ? `[data-av-id="${avid}"] .av__row[data-id="${previousId}"]` : `[data-av-id="${avid}"] .av__row--header`).nextElementSibling.querySelector('[data-detached="true"]');
         } else {
             // 修改单元格后立即修改其他单元格
@@ -474,7 +507,7 @@ const updateCellValue = (protyle: IProtyle, type: TAVCol, cellElements: HTMLElem
             }
         }
     }
-    if (cellElements.length === 1 && cellElements[0].dataset.detached === "true" && !cellElements[0].parentElement.dataset.id) {
+    if (cellElements.length === 1 && cellElements[0].dataset.detached === "true" && !rowElement.dataset.id) {
         return;
     }
     const blockElement = hasClosestBlock(cellElements[0]);
@@ -514,10 +547,16 @@ const updateCellValue = (protyle: IProtyle, type: TAVCol, cellElements: HTMLElem
             const rowID = rowElement.getAttribute("data-id");
             const cellId = item.getAttribute("data-id");
             const colId = item.getAttribute("data-col-id");
-            const inputValue: { content: string | number, isNotEmpty?: boolean } = {
+            const inputValue: {
+                content: string | number,
+                isNotEmpty?: boolean
+            } = {
                 content: (avMaskElement.querySelector(".b3-text-field") as HTMLInputElement).value
             };
-            const oldValue: { content: string | number, isNotEmpty?: boolean } = {
+            const oldValue: {
+                content: string | number,
+                isNotEmpty?: boolean
+            } = {
                 content: type === "block" ? item.firstElementChild.textContent.trim() : item.textContent.trim()
             };
             if (type === "number") {

+ 20 - 0
app/src/protyle/render/av/col.ts

@@ -577,6 +577,26 @@ export const showColMenu = (protyle: IProtyle, blockElement: Element, cellElemen
                 }]);
             }
         });
+    }
+    const isPin = cellElement.dataset.pin === "true"
+    menu.addItem({
+        icon: "iconPin",
+        label: isPin ? window.siyuan.languages.unfreezeCol : window.siyuan.languages.freezeCol,
+        click() {
+            transaction(protyle, [{
+                action: "setAttrViewColPin",
+                id: colId,
+                avID,
+                data: !isPin
+            }], [{
+                action: "setAttrViewColPin",
+                id: colId,
+                avID,
+                data: isPin
+            }]);
+        }
+    });
+    if (type !== "block") {
         menu.addItem({
             icon: "iconCopy",
             label: window.siyuan.languages.duplicate,

+ 4 - 3
app/src/protyle/render/av/date.ts

@@ -2,13 +2,14 @@ import {transaction} from "../../wysiwyg/transaction";
 import * as dayjs from "dayjs";
 import {updateAttrViewCellAnimation} from "./action";
 import {genAVValueHTML} from "./blockAttr";
+import {hasClosestByClassName} from "../../util/hasClosest";
 
 export const getDateHTML = (data: IAVTable, cellElements: HTMLElement[]) => {
     let hasEndDate = true;
     let cellValue: IAVCell;
     cellElements.forEach((cellElement) => {
         data.rows.find(row => {
-            if (cellElement.parentElement.dataset.id === row.id) {
+            if ((hasClosestByClassName(cellElement, "av__row") as HTMLElement).dataset.id === row.id) {
                 row.cells.find(cell => {
                     if (cell.id === cellElement.dataset.id) {
                         if (!cell.value || !cell.value.date || !cell.value.date.hasEndDate) {
@@ -141,7 +142,7 @@ export const setDateValue = (options: {
     value: IAVCellDateValue
 }) => {
     let cellIndex: number;
-    Array.from(options.cellElements[0].parentElement.querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
+    Array.from((hasClosestByClassName(options.cellElements[0], "av__row") as HTMLElement).querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
         if (item.dataset.id === options.cellElements[0].dataset.id) {
             cellIndex = index;
             return true;
@@ -153,7 +154,7 @@ export const setDateValue = (options: {
     options.cellElements.forEach(item => {
         let cellData: IAVCell;
         let oldValue;
-        const rowID = item.parentElement.dataset.id;
+        const rowID = (hasClosestByClassName(item, "av__row") as HTMLElement).dataset.id;
         options.data.view.rows.find(row => {
             if (row.id === rowID) {
                 if (typeof cellIndex === "number") {

+ 26 - 14
app/src/protyle/render/av/keydown.ts

@@ -2,6 +2,7 @@ import {matchHotKey} from "../../util/hotKey";
 import {selectRow} from "./row";
 import {cellScrollIntoView, popTextCell} from "./cell";
 import {avContextmenu} from "./action";
+import {hasClosestByClassName} from "../../util/hasClosest";
 
 export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyle: IProtyle) => {
     if (!nodeElement.classList.contains("av") || !window.siyuan.menus.menu.element.classList.contains("fn__none")) {
@@ -17,9 +18,13 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl
     }
     const selectCellElement = nodeElement.querySelector(".av__cell--select") as HTMLElement;
     if (selectCellElement) {
+        const rowElement = hasClosestByClassName(selectCellElement, "av__row");
+        if (!rowElement) {
+            return false;
+        }
         if (event.key === "Escape") {
             selectCellElement.classList.remove("av__cell--select");
-            selectRow(selectCellElement.parentElement.querySelector(".av__firstcol"), "select");
+            selectRow(rowElement.querySelector(".av__firstcol"), "select");
             event.preventDefault();
             return true;
         }
@@ -30,57 +35,64 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl
         }
         let newCellElement;
         if (event.key === "ArrowLeft") {
-            const previousRowElement = selectCellElement.parentElement.previousElementSibling;
-            if (selectCellElement.previousElementSibling && selectCellElement.previousElementSibling.classList.contains("av__cell")) {
-                newCellElement = selectCellElement.previousElementSibling;
+            const previousRowElement = rowElement.previousElementSibling;
+            if (selectCellElement.previousElementSibling && !selectCellElement.previousElementSibling.classList.contains("av__firstcol")) {
+                if (selectCellElement.previousElementSibling.classList.contains("av__cell")) {
+                    newCellElement = selectCellElement.previousElementSibling;
+                } else {
+                    newCellElement = selectCellElement.previousElementSibling.lastElementChild;
+                }
             } else if (previousRowElement && !previousRowElement.classList.contains("av__row--header")) {
-                newCellElement = previousRowElement.lastElementChild.previousElementSibling;
+                const previousCellElements = previousRowElement.querySelectorAll(".av__cell")
+                newCellElement = previousCellElements[previousCellElements.length - 1];
             }
             if (newCellElement) {
                 selectCellElement.classList.remove("av__cell--select");
                 newCellElement.classList.add("av__cell--select");
-                cellScrollIntoView(nodeElement, newCellElement.getBoundingClientRect());
+                cellScrollIntoView(nodeElement, newCellElement, false);
             }
             event.preventDefault();
             return true;
         }
         if (event.key === "ArrowRight") {
-            const nextRowElement = selectCellElement.parentElement.nextElementSibling;
+            const nextRowElement = rowElement.nextElementSibling;
             if (selectCellElement.nextElementSibling && selectCellElement.nextElementSibling.classList.contains("av__cell")) {
                 newCellElement = selectCellElement.nextElementSibling;
+            } else if (!selectCellElement.nextElementSibling && selectCellElement.parentElement.nextElementSibling) {
+                newCellElement = selectCellElement.parentElement.nextElementSibling;
             } else if (nextRowElement && !nextRowElement.classList.contains("av__row--footer")) {
                 newCellElement = nextRowElement.querySelector(".av__cell");
             }
             if (newCellElement) {
                 selectCellElement.classList.remove("av__cell--select");
                 newCellElement.classList.add("av__cell--select");
-                cellScrollIntoView(nodeElement, newCellElement.getBoundingClientRect());
+                cellScrollIntoView(nodeElement, newCellElement, false);
             }
             event.preventDefault();
             return true;
         }
         if (event.key === "ArrowUp") {
-            const previousRowElement = selectCellElement.parentElement.previousElementSibling;
+            const previousRowElement = rowElement.previousElementSibling;
             if (previousRowElement && !previousRowElement.classList.contains("av__row--header")) {
                 newCellElement = previousRowElement.querySelector(`.av__cell[data-col-id="${selectCellElement.dataset.colId}"]`);
             }
             if (newCellElement) {
                 selectCellElement.classList.remove("av__cell--select");
                 newCellElement.classList.add("av__cell--select");
-                cellScrollIntoView(nodeElement, newCellElement.getBoundingClientRect());
+                cellScrollIntoView(nodeElement, newCellElement);
             }
             event.preventDefault();
             return true;
         }
         if (event.key === "ArrowDown") {
-            const nextRowElement = selectCellElement.parentElement.nextElementSibling;
+            const nextRowElement = rowElement.nextElementSibling;
             if (nextRowElement && !nextRowElement.classList.contains("av__row--footer")) {
                 newCellElement = nextRowElement.querySelector(`.av__cell[data-col-id="${selectCellElement.dataset.colId}"]`);
             }
             if (newCellElement) {
                 selectCellElement.classList.remove("av__cell--select");
                 newCellElement.classList.add("av__cell--select");
-                cellScrollIntoView(nodeElement, newCellElement.getBoundingClientRect());
+                cellScrollIntoView(nodeElement, newCellElement);
             }
             event.preventDefault();
             return true;
@@ -115,7 +127,7 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl
             selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll");
             if (previousRowElement && !previousRowElement.classList.contains("av__row--header")) {
                 selectRow(previousRowElement.querySelector(".av__firstcol"), "select");
-                cellScrollIntoView(nodeElement, previousRowElement.getBoundingClientRect(), true);
+                cellScrollIntoView(nodeElement, previousRowElement);
             } else {
                 nodeElement.classList.add("protyle-wysiwyg--select");
             }
@@ -127,7 +139,7 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl
             selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll");
             if (nextRowElement && !nextRowElement.classList.contains("av__row--add")) {
                 selectRow(nextRowElement.querySelector(".av__firstcol"), "select");
-                cellScrollIntoView(nodeElement, nextRowElement.getBoundingClientRect(), true);
+                cellScrollIntoView(nodeElement, nextRowElement);
             } else {
                 nodeElement.classList.add("protyle-wysiwyg--select");
             }

+ 55 - 16
app/src/protyle/render/av/render.ts

@@ -6,6 +6,7 @@ import * as dayjs from "dayjs";
 import {unicode2Emoji} from "../../../emoji";
 import {focusBlock} from "../../util/selection";
 import {isMac} from "../../util/compatibility";
+import {hasClosestByClassName} from "../../util/hasClosest";
 
 export const avRender = (element: Element, protyle: IProtyle, cb?: () => void) => {
     let avElements: Element[] = [];
@@ -45,20 +46,41 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: () => void) =
             let selectCellId = "";
             const selectCellElement = e.querySelector(".av__cell--select") as HTMLElement;
             if (selectCellElement) {
-                selectCellId = selectCellElement.parentElement.dataset.id + Constants.ZWSP + selectCellElement.getAttribute("data-col-id");
+                selectCellId = (hasClosestByClassName(selectCellElement, "av__row") as HTMLElement).dataset.id + Constants.ZWSP + selectCellElement.getAttribute("data-col-id");
             }
             fetchPost("/api/av/renderAttributeView", {
                 id: e.getAttribute("data-av-id"),
             }, (response) => {
                 const data = response.data.view as IAVTable;
                 // header
-                let tableHTML = '<div class="av__row av__row--header"><div class="av__firstcol"><svg style="height: 32px"><use xlink:href="#iconUncheck"></use></svg></div>';
-                let calcHTML = "";
-                data.columns.forEach((column: IAVColumn) => {
+                let tableHTML = '<div class="av__row av__row--header"><div class="av__firstcol av__colsticky"><svg><use xlink:href="#iconUncheck"></use></svg></div>';
+                let calcHTML = '<div style="width: 24px"></div>';
+                let pinIndex = -1;
+                let pinMaxIndex = -1;
+                let indexWidth = 0;
+                const eWidth = e.clientWidth;
+                data.columns.forEach((item, index) => {
+                    if (!item.hidden) {
+                        if (item.pin) {
+                            pinIndex = index;
+                        }
+                        if (indexWidth < eWidth - 200) {
+                            indexWidth += parseInt(item.width) || 200;
+                            pinMaxIndex = index;
+                        }
+                    }
+                })
+                pinIndex = Math.min(pinIndex, pinMaxIndex);
+                if (pinIndex > -1) {
+                    tableHTML = '<div class="av__row av__row--header"><div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>'
+                    calcHTML = '<div class="av__colsticky"><div style="width: 24px"></div>'
+                }
+                data.columns.forEach((column: IAVColumn, index: number) => {
                     if (column.hidden) {
                         return;
                     }
-                    tableHTML += `<div class="av__cell" data-col-id="${column.id}" data-icon="${column.icon}" data-dtype="${column.type}"  
+                    tableHTML += `<div class="av__cell" data-col-id="${column.id}" 
+data-icon="${column.icon}" data-dtype="${column.type}"  data-pin="${column.pin}" 
 style="width: ${column.width || "200px"};
 ${column.wrap ? "" : "white-space: nowrap;"}">
     <div draggable="true" class="av__cellheader">
@@ -67,8 +89,14 @@ ${column.wrap ? "" : "white-space: nowrap;"}">
     </div>
     <div class="av__widthdrag"></div>
 </div>`;
+                    if (pinIndex === index) {
+                        tableHTML += '</div>'
+                    }
                     calcHTML += `<div class="av__calc${calcHTML ? "" : " av__calc--show"}${column.calc && column.calc.operator !== "" ? " av__calc--ashow" : ""}" data-col-id="${column.id}" data-dtype="${column.type}" data-operator="${column.calc?.operator || ""}"  
 style="width: ${column.width || "200px"}">${getCalcValue(column) || '<svg><use xlink:href="#iconDown"></use></svg>' + window.siyuan.languages.calc}</div>`;
+                    if (pinIndex === index) {
+                        calcHTML += '</div>'
+                    }
                 });
                 tableHTML += `<div class="block__icons" style="min-height: auto">
     <div class="block__icon block__icon--show" data-type="av-header-add"><svg><use xlink:href="#iconAdd"></use></svg></div>
@@ -82,8 +110,13 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || '<svg><use x
 <div class="av__gutters">
     <button class="ariaLabel" data-action="add" data-position="right" aria-label="${isMac() ? window.siyuan.languages.addBelowAbove : window.siyuan.languages.addBelowAbove.replace("⌥", "Alt+")}"><svg><use xlink:href="#iconAdd"></use></svg></button>
     <button class="ariaLabel" draggable="true" data-position="right" aria-label="${window.siyuan.languages.rowTip}"><svg><use xlink:href="#iconDrag"></use></svg></button>
-</div>
-<div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>`;
+</div>`;
+                    if (pinIndex > -1) {
+                        tableHTML += '<div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>'
+                    } else {
+                        tableHTML += `<div class="av__firstcol av__colsticky"><svg><use xlink:href="#iconUncheck"></use></svg></div>`
+                    }
+
                     row.cells.forEach((cell, index) => {
                         if (data.columns[index].hidden) {
                             return;
@@ -144,13 +177,11 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || '<svg><use x
                             });
                             if (!text) {
                                 text = '<span class="av__celltext"></span>';
-                            } else {
-                                text = `<span class="av__celltext">${text}</span>`;
                             }
                         }
                         if (["text", "template", "url", "email", "phone", "number", "date", "created", "updated"].includes(cell.valueType)) {
                             if (cell.value && cell.value[cell.valueType as "url"].content) {
-                                text += `<span ${cell.valueType !== "number" ? "" : 'style="right:auto;left:5px"'} data-type="copy" class="b3-tooltips b3-tooltips__n block__icon" aria-label="${window.siyuan.languages.copy}"><svg><use xlink:href="#iconCopy"></use></svg></span>`;
+                                text += `<span ${cell.valueType !== "number" ? "" : 'style="right:auto;left:5px"'} data-type="copy" class="block__icon"><svg><use xlink:href="#iconCopy"></use></svg></span>`;
                             }
                         }
                         tableHTML += `<div class="av__cell" data-id="${cell.id}" data-col-id="${data.columns[index].id}"
@@ -161,6 +192,10 @@ ${cell.bgColor ? `background-color:${cell.bgColor};` : ""}
 white-space: ${data.columns[index].wrap ? "pre-wrap" : "nowrap"};
 ${cell.valueType !== "number" ? "" : "flex-direction: row-reverse;"}
 ${cell.color ? `color:${cell.color};` : ""}">${text}</div>`;
+
+                        if (pinIndex === index) {
+                            tableHTML += '</div>'
+                        }
                     });
                     tableHTML += "<div></div></div>";
                 });
@@ -172,7 +207,7 @@ ${cell.color ? `color:${cell.color};` : ""}">${text}</div>`;
 </div>`;
                 });
                 setTimeout(() => {
-                    e.firstElementChild.outerHTML = `<div class="av__container">
+                    e.firstElementChild.outerHTML = `<div class="av__container" style="--av-background:${e.style.backgroundColor || "var(--b3-theme-background)"}">
     <div class="av__header">
         <div class="layout-tab-bar fn__flex">
             ${tabHTML}
@@ -199,10 +234,12 @@ ${cell.color ? `color:${cell.color};` : ""}">${text}</div>`;
         <div style="float: left;">
             ${tableHTML}
             <div class="av__row--add">
-                <svg><use xlink:href="#iconAdd"></use></svg>
-                ${window.siyuan.languages.addAttr}
+                <div class="av__colsticky">
+                    <svg><use xlink:href="#iconAdd"></use></svg>
+                    ${window.siyuan.languages.addAttr}
+                </div>
             </div>
-            <div class="av__row--footer"><div style="width: 24px"></div>${calcHTML}</div>
+            <div class="av__row--footer">${calcHTML}</div>
         </div>
     </div>
 </div>`;
@@ -223,7 +260,9 @@ ${cell.color ? `color:${cell.color};` : ""}">${text}</div>`;
                         if (newCellElement) {
                             newCellElement.classList.add("av__cell--select");
                         }
-                        focusBlock(e);
+                        if (!document.querySelector(".av__panel")) {
+                            focusBlock(e);
+                        }
                     }
                     if (cb) {
                         cb();
@@ -270,7 +309,7 @@ export const refreshAV = (protyle: IProtyle, operation: IOperation, isUndo: bool
             avRender(item, protyle, () => {
                 // https://github.com/siyuan-note/siyuan/issues/9599
                 if (!isUndo && operation.action === "insertAttrViewBlock" && operation.isDetached) {
-                    popTextCell(protyle, [item.querySelector(`.av__row[data-id="${operation.srcIDs[0]}"] > .av__cell[data-detached="true"]`)], "block");
+                    popTextCell(protyle, [item.querySelector(`.av__row[data-id="${operation.srcIDs[0]}"] .av__cell[data-detached="true"]`)], "block");
                 }
             });
         });

+ 24 - 9
app/src/protyle/render/av/row.ts

@@ -1,27 +1,36 @@
-import {hasClosestBlock} from "../../util/hasClosest";
+import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest";
 import {focusBlock} from "../../util/selection";
 
 export const selectRow = (checkElement: Element, type: "toggle" | "select" | "unselect" | "unselectAll") => {
-    const rowElement = checkElement.parentElement;
+    const rowElement = hasClosestByClassName(checkElement, "av__row");
+    if (!rowElement) {
+        return
+    }
     const useElement = checkElement.querySelector("use");
     if (rowElement.classList.contains("av__row--header") || type === "unselectAll") {
         if ("#iconCheck" === useElement.getAttribute("xlink:href")) {
             rowElement.parentElement.querySelectorAll(".av__firstcol").forEach(item => {
                 item.querySelector("use").setAttribute("xlink:href", "#iconUncheck");
-                item.parentElement.classList.remove("av__row--select");
+                const rowItemElement = hasClosestByClassName(item, "av__row");
+                if (rowItemElement) {
+                    rowItemElement.classList.remove("av__row--select");
+                }
             });
         } else {
             rowElement.parentElement.querySelectorAll(".av__firstcol").forEach(item => {
                 item.querySelector("use").setAttribute("xlink:href", "#iconCheck");
-                item.parentElement.classList.add("av__row--select");
+                const rowItemElement = hasClosestByClassName(item, "av__row");
+                if (rowItemElement) {
+                    rowItemElement.classList.add("av__row--select");
+                }
             });
         }
     } else {
         if (type === "select" || (useElement.getAttribute("xlink:href") === "#iconUncheck" && type === "toggle")) {
-            checkElement.parentElement.classList.add("av__row--select");
+            rowElement.classList.add("av__row--select");
             useElement.setAttribute("xlink:href", "#iconCheck");
         } else if (type === "unselect" || (useElement.getAttribute("xlink:href") === "#iconCheck" && type === "toggle")) {
-            checkElement.parentElement.classList.remove("av__row--select");
+            rowElement.classList.remove("av__row--select");
             useElement.setAttribute("xlink:href", "#iconUncheck");
         }
     }
@@ -60,15 +69,21 @@ export const updateHeader = (rowElement: HTMLElement) => {
 
 export const insertAttrViewBlockAnimation = (blockElement: Element, size: number, previousId: string, avId?: string) => {
     const previousElement = blockElement.querySelector(`.av__row[data-id="${previousId}"]`) || blockElement.querySelector(".av__row--header");
-    let colHTML = "";
-    previousElement.querySelectorAll(".av__cell").forEach((item: HTMLElement) => {
+    let colHTML = '<div style="width: 24px"></div>';
+    const pinIndex = previousElement.querySelectorAll(".av__colsticky .av__cell").length - 1;
+    if (pinIndex > -1) {
+        colHTML = `<div class="av__colsticky"><div style="width: 24px"></div>`;
+    }
+    previousElement.querySelectorAll(".av__cell").forEach((item: HTMLElement, index) => {
         colHTML += `<div class="av__cell" style="width: ${item.style.width}" ${(item.getAttribute("data-block-id") || item.dataset.dtype === "block") ? ' data-detached="true"' : ""}><span class="av__pulse"></span></div>`;
+        if (pinIndex === index) {
+            colHTML += `</div>`;
+        }
     });
 
     let html = "";
     new Array(size).fill(1).forEach(() => {
         html += `<div class="av__row" data-avid="${avId}" data-previous-id="${previousId}">
-    <div style="width: 24px"></div>
     ${colHTML}
 </div>`;
     });

+ 17 - 10
app/src/protyle/render/av/select.ts

@@ -54,7 +54,7 @@ export const removeCellOption = (protyle: IProtyle, data: IAV, cellElements: HTM
     const undoOperations: IOperation[] = [];
     let newData: IAVCellSelectValue[];
     cellElements.forEach((item, elementIndex) => {
-        const rowID = item.parentElement.dataset.id;
+        const rowID = (hasClosestByClassName(item, "av__row") as HTMLElement).dataset.id;
         const cellId = item.dataset.id;
         let cellData: IAVCell;
         data.view.rows.find(row => {
@@ -157,7 +157,7 @@ export const setColOption = (protyle: IProtyle, data: IAV, target: HTMLElement,
         } else {
             cellElements.forEach((cellElement: HTMLMediaElement) => {
                 data.view.rows.find(row => {
-                    if (row.id === cellElement.parentElement.dataset.id) {
+                    if (row.id === (hasClosestByClassName(cellElement, "av__row") as HTMLElement).dataset.id) {
                         row.cells.find(cell => {
                             if (cell.id === cellElement.dataset.id) {
                                 cell.value.mSelect.find((item) => {
@@ -235,7 +235,7 @@ export const setColOption = (protyle: IProtyle, data: IAV, target: HTMLElement,
                 } else {
                     cellElements.forEach((cellElement: HTMLElement) => {
                         data.view.rows.find(row => {
-                            if (row.id === cellElement.parentElement.dataset.id) {
+                            if (row.id === (hasClosestByClassName(cellElement, "av__row") as HTMLElement).dataset.id) {
                                 row.cells.find(cell => {
                                     if (cell.id === cellElement.dataset.id) {
                                         cell.value.mSelect.find((item, index) => {
@@ -314,7 +314,7 @@ export const setColOption = (protyle: IProtyle, data: IAV, target: HTMLElement,
                 } else {
                     cellElements.forEach((cellElement: HTMLElement) => {
                         data.view.rows.find(row => {
-                            if (row.id === cellElement.parentElement.dataset.id) {
+                            if (row.id === (hasClosestByClassName(cellElement, "av__row") as HTMLElement).dataset.id) {
                                 row.cells.find(cell => {
                                     if (cell.id === cellElement.dataset.id) {
                                         cell.value.mSelect.find((item) => {
@@ -394,8 +394,6 @@ export const bindSelectEvent = (protyle: IProtyle, data: IAV, menuElement: HTMLE
             addColOptionOrCell(protyle, data, cellElements, currentElement, menuElement);
         } else if (event.key === "Backspace" && inputElement.value === "") {
             removeCellOption(protyle, data, cellElements, inputElement.previousElementSibling as HTMLElement);
-        } else if (event.key === "Escape") {
-            menuElement.parentElement.remove();
         }
     });
 };
@@ -413,14 +411,18 @@ export const addColOptionOrCell = (protyle: IProtyle, data: IAV, cellElements: H
         return;
     }
 
-    const colId = cellElements[0].dataset.colId;
+    const rowElement = hasClosestByClassName(cellElements[0], "av__row");
+    if (!rowElement) {
+        return;
+    }
     let cellIndex: number;
-    Array.from(cellElements[0].parentElement.querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
+    Array.from(rowElement.querySelectorAll(".av__cell")).find((item: HTMLElement, index) => {
         if (item.dataset.id === cellElements[0].dataset.id) {
             cellIndex = index;
             return true;
         }
     });
+    const colId = cellElements[0].dataset.colId;
     let colData: IAVColumn;
     data.view.columns.find((item: IAVColumn) => {
         if (item.id === colId) {
@@ -436,8 +438,12 @@ export const addColOptionOrCell = (protyle: IProtyle, data: IAV, cellElements: H
     const cellUndoOperations: IOperation[] = [];
     let newValue: IAVCellSelectValue[];
     cellElements.forEach((item, index) => {
+        const itemRowElement = hasClosestByClassName(item, "av__row");
+        if (!itemRowElement) {
+            return;
+        }
         let cellData: IAVCell;
-        const rowID = item.parentElement.dataset.id;
+        const rowID = itemRowElement.dataset.id;
         data.view.rows.find(row => {
             if (row.id === rowID) {
                 if (typeof cellIndex === "number") {
@@ -553,7 +559,8 @@ export const getSelectHTML = (data: IAVTable, cellElements: HTMLElement[]) => {
 
     let allUniqueOptions: IAVCellSelectValue[] = [];
     data.rows.find(row => {
-        if (cellElements[0].parentElement.dataset.id === row.id) {
+        const rowElement = hasClosestByClassName(cellElements[0], "av__row");
+        if (rowElement && rowElement.dataset.id === row.id) {
             row.cells.find(cell => {
                 if (cell.id === cellElements[0].dataset.id) {
                     if (cell.value && cell.value.mSelect) {

+ 2 - 2
app/src/protyle/scroll/event.ts

@@ -27,7 +27,7 @@ export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => {
             const scrollRect = item.querySelector(".av__scroll").getBoundingClientRect()
             const headerElement = item.querySelector(".av__row--header") as HTMLElement;
             if (headerElement) {
-                const distance = elementRect.top - scrollRect.top;
+                const distance = Math.floor(elementRect.top - scrollRect.top);
                 if (distance > 0 && distance < scrollRect.height) {
                     headerElement.style.transform = `translateY(${distance}px)`;
                 } else {
@@ -37,7 +37,7 @@ export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => {
             const footerElement = item.querySelector(".av__row--footer") as HTMLElement;
             if (footerElement) {
                 if (footerElement.querySelector(".av__calc--ashow")) {
-                    const distance = elementRect.bottom - scrollRect.bottom;
+                    const distance = Math.floor(elementRect.bottom - footerElement.parentElement.getBoundingClientRect().bottom);
                     if (distance < 0 && -distance < scrollRect.height) {
                         footerElement.style.transform = `translateY(${distance}px)`;
                     } else {

+ 9 - 0
app/src/protyle/toolbar/Font.ts

@@ -216,10 +216,16 @@ export const fontEvent = (protyle: IProtyle, nodeElements: Element[], type?: str
                 e.style.textShadow = "";
                 e.style.backgroundColor = "";
                 e.style.fontSize = "";
+                if (e.classList.contains("av")) {
+                    e.querySelector(".av__container").setAttribute("style", "--av-background:--b3-theme-background");
+                }
             } else if (type === "style1") {
                 const colorList = color.split(Constants.ZWSP);
                 e.style.backgroundColor = colorList[0];
                 e.style.color = colorList[1];
+                if (e.classList.contains("av")) {
+                    e.querySelector(".av__container").setAttribute("style", `--av-background:${colorList[0]}`);
+                }
             } else if (type === "style2") {
                 e.style.webkitTextStroke = "0.2px var(--b3-theme-on-background)";
                 e.style.webkitTextFillColor = "transparent";
@@ -229,6 +235,9 @@ export const fontEvent = (protyle: IProtyle, nodeElements: Element[], type?: str
                 e.style.color = color;
             } else if (type === "backgroundColor") {
                 e.style.backgroundColor = color;
+                if (e.classList.contains("av")) {
+                    e.querySelector(".av__container").setAttribute("style", `--av-background:${color}`);
+                }
             } else if (type === "fontSize") {
                 e.style.fontSize = color;
             }

+ 34 - 4
app/src/protyle/util/editorCommonEvent.ts

@@ -852,15 +852,39 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => {
                     const blockElement = hasClosestBlock(targetElement);
                     if (blockElement) {
                         const avID = blockElement.getAttribute("data-av-id");
+                        let previousID = "";
+                        if (targetClass.includes("dragover__left")) {
+                            if (targetElement.previousElementSibling) {
+                                if (targetElement.previousElementSibling.classList.contains("av__colsticky")) {
+                                    previousID = targetElement.previousElementSibling.lastElementChild.getAttribute("data-col-id")
+                                } else {
+                                    previousID = targetElement.previousElementSibling.getAttribute("data-col-id")
+                                }
+                            }
+                        } else {
+                            previousID = targetElement.getAttribute("data-col-id")
+                        }
+                        let oldPreviousID = "";
+                        const rowElement = hasClosestByClassName(targetElement, "av__row");
+                        if (rowElement) {
+                            const oldPreviousElement = rowElement.querySelector(`[data-col-id="${gutterTypes[2]}"`)?.previousElementSibling
+                            if (oldPreviousElement) {
+                                if (oldPreviousElement.classList.contains("av__colsticky")) {
+                                    oldPreviousID = oldPreviousElement.lastElementChild.getAttribute("data-col-id")
+                                } else {
+                                    oldPreviousID = oldPreviousElement.getAttribute("data-col-id")
+                                }
+                            }
+                        }
                         transaction(protyle, [{
                             action: "sortAttrViewCol",
                             avID,
-                            previousID: (targetClass.includes("dragover__left") ? targetElement.previousElementSibling?.getAttribute("data-col-id") : targetElement.getAttribute("data-col-id")) || "",
+                            previousID,
                             id: gutterTypes[2],
                         }], [{
                             action: "sortAttrViewCol",
                             avID,
-                            previousID: targetElement.parentElement.querySelector(`[data-col-id="${gutterTypes[2]}"`).previousElementSibling?.getAttribute("data-col-id") || "",
+                            previousID: oldPreviousID,
                             id: gutterTypes[2],
                         }]);
                     }
@@ -1111,8 +1135,14 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => {
         if (gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}Col${Constants.ZWSP}`.toLowerCase())) {
             // 表头只能拖拽到当前 av 的表头中
             targetElement = hasClosestByClassName(event.target, "av__cell");
-            if (targetElement && !targetElement.parentElement.isSameNode(window.siyuan.dragElement.parentElement)) {
-                targetElement = false;
+            if (targetElement) {
+                const targetRowElement = hasClosestByClassName(targetElement, "av__row--header")
+                const dragRowElement = hasClosestByClassName(window.siyuan.dragElement, "av__row--header")
+                if (!targetRowElement || !dragRowElement ||
+                    (targetRowElement && dragRowElement && !targetRowElement.isSameNode(dragRowElement))
+                ) {
+                    targetElement = false;
+                }
             }
         } else if (targetElement && gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeView${Constants.ZWSP}Row${Constants.ZWSP}`.toLowerCase())) {
             // 行只能拖拽当前 av 中

+ 6 - 3
app/src/protyle/wysiwyg/index.ts

@@ -384,9 +384,12 @@ export class WYSIWYG {
                 let newWidth: string;
                 documentSelf.onmousemove = (moveEvent: MouseEvent) => {
                     newWidth = Math.max(oldWidth + (moveEvent.clientX - event.clientX), 58) + "px";
-                    dragElement.parentElement.parentElement.querySelectorAll(".av__row, .av__row--footer").forEach(item => {
-                        (item.querySelector(`[data-col-id="${dragColId}"]`) as HTMLElement).style.width = newWidth;
-                    });
+                    const scrollElement = hasClosestByClassName(dragElement, "av__scroll")
+                    if (scrollElement) {
+                        scrollElement.querySelectorAll(".av__row, .av__row--footer").forEach(item => {
+                            (item.querySelector(`[data-col-id="${dragColId}"]`) as HTMLElement).style.width = newWidth;
+                        });
+                    }
                 };
 
                 documentSelf.onmouseup = () => {

+ 1 - 1
app/src/protyle/wysiwyg/transaction.ts

@@ -711,7 +711,7 @@ export const onTransaction = (protyle: IProtyle, operation: IOperation, isUndo:
         "updateAttrViewColOption", "updateAttrViewCell", "sortAttrViewRow", "sortAttrViewCol", "setAttrViewColHidden",
         "setAttrViewColWrap", "setAttrViewColWidth", "removeAttrViewColOption", "setAttrViewName", "setAttrViewFilters",
         "setAttrViewSorts", "setAttrViewColCalc", "removeAttrViewCol", "updateAttrViewColNumberFormat", "removeAttrViewBlock",
-        "replaceAttrViewBlock", "updateAttrViewColTemplate", "setAttrViewColIcon"].includes(operation.action)) {
+        "replaceAttrViewBlock", "updateAttrViewColTemplate", "setAttrViewColIcon", "setAttrViewColPin"].includes(operation.action)) {
         refreshAV(protyle, operation, isUndo);
     } else if (operation.action === "doUpdateUpdated") {
         updateElements.forEach(item => {

+ 3 - 1
app/src/types/index.d.ts

@@ -27,6 +27,7 @@ type TOperation =
     | "updateAttrViewColTemplate"
     | "sortAttrViewRow"
     | "sortAttrViewCol"
+    | "setAttrViewColPin"
     | "setAttrViewColHidden"
     | "setAttrViewColWrap"
     | "setAttrViewColWidth"
@@ -1029,11 +1030,12 @@ interface IAVSort {
 }
 
 interface IAVColumn {
-    width: number,
+    width: string,
     icon: string,
     id: string,
     name: string,
     wrap: boolean,
+    pin: boolean,
     hidden: boolean,
     type: TAVCol,
     numberFormat: string,