diff --git a/app/src/assets/scss/business/_av.scss b/app/src/assets/scss/business/_av.scss index 7fac56d8d..4423bfc38 100644 --- a/app/src/assets/scss/business/_av.scss +++ b/app/src/assets/scss/business/_av.scss @@ -72,10 +72,6 @@ opacity: 0; display: flex; - &[draggable=true] { - cursor: grab; - } - svg { height: 25px; } @@ -94,10 +90,6 @@ cursor: pointer; } - &__body { - float: left; - } - &__row { display: flex; border-bottom: 1px solid var(--b3-theme-surface-lighter); @@ -121,10 +113,6 @@ .av__gutters { opacity: 1; } - - .av__firstcol svg { - opacity: 1; - } } &--select { @@ -148,23 +136,11 @@ } } - &--add { - .av__firstcol svg { - opacity: 1; - } - - &:hover { - background-color: var(--b3-list-icon-hover); - } - } - &--header, &--footer { background-color: var(--b3-theme-background); - position: relative; } - &--add, &--footer { display: flex; border-top: 1px solid var(--b3-theme-surface-lighter); @@ -208,6 +184,27 @@ } } } + + &--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%; + + svg { + height: 12px; + width: 12px; + color: var(--b3-theme-on-surface); + margin-right: 5px; + flex-shrink: 0; + } + + &:hover { + background-color: var(--b3-list-icon-hover); + } + } } &__cell { @@ -237,13 +234,6 @@ border-radius: var(--b3-border-radius); } - &--add { - position: sticky; - left: 25px; - font-size: 87.5%; - padding: 0 0.5em; - } - .block__icon { position: absolute; right: 5px; @@ -275,7 +265,6 @@ &__celltext { overflow: hidden; - line-height: normal; .b3-chip { margin: 1px 2px; @@ -294,14 +283,6 @@ } &__firstcol { - background-color: var(--b3-theme-background); - border-right: 1px solid var(--b3-theme-surface-lighter); - - position: sticky; - left: 0; - width: 24px; - z-index: 1; - svg { color: var(--b3-theme-on-surface); height: 33px; @@ -311,6 +292,10 @@ box-sizing: border-box; float: left; } + + &:hover svg { + opacity: 1; + } } &__widthdrag { @@ -395,7 +380,6 @@ .protyle-wysiwyg--select, .protyle-wysiwyg--hl { .av__row--header, - .av__firstcol, .av__row--footer { background-color: transparent; } diff --git a/app/src/protyle/render/av/action.ts b/app/src/protyle/render/av/action.ts index 7538fc8dd..a8daeba91 100644 --- a/app/src/protyle/render/av/action.ts +++ b/app/src/protyle/render/av/action.ts @@ -32,7 +32,7 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle if (event.shiftKey) { const rowElement = hasClosestByClassName(event.target, "av__row"); if (rowElement && !rowElement.classList.contains("av__row--header")) { - selectRow(rowElement.querySelector(".av__check"), "toggle"); + selectRow(rowElement.querySelector(".av__firstcol"), "toggle"); return true; } } @@ -63,8 +63,8 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle return true; } - const gutterElement = hasClosestByClassName(event.target, "av__gutter"); - if (gutterElement) { + const gutterElement = hasClosestByClassName(event.target, "ariaLabel"); + if (gutterElement && gutterElement.parentElement.classList.contains("av__gutters")) { const rowElement = gutterElement.parentElement.parentElement; if (gutterElement.dataset.action === "add") { const avID = blockElement.getAttribute("data-av-id"); @@ -97,7 +97,7 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle return true; } - const checkElement = hasClosestByClassName(event.target, "av__check"); + const checkElement = hasClosestByClassName(event.target, "av__firstcol"); if (checkElement) { window.siyuan.menus.menu.remove(); selectRow(checkElement, "toggle"); @@ -199,10 +199,10 @@ export const avClick = (protyle: IProtyle, event: MouseEvent & { target: HTMLEle 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 (type === "updated" || type === "created" || (type === "block" && !cellElement.getAttribute("data-detached"))) { - selectRow(cellElement.parentElement.querySelector(".av__check"), "toggle"); + selectRow(cellElement.parentElement.querySelector(".av__firstcol"), "toggle"); } else { cellElement.parentElement.parentElement.querySelectorAll(".av__row--select").forEach(item => { - item.querySelector(".av__check use").setAttribute("xlink:href", "#iconUncheck"); + item.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconUncheck"); item.classList.remove("av__row--select"); }); updateHeader(cellElement.parentElement); @@ -259,7 +259,7 @@ export const avContextmenu = (protyle: IProtyle, rowElement: HTMLElement, positi blockElement.querySelectorAll(".av__row--select").forEach(item => { item.classList.remove("av__row--select"); }); - blockElement.querySelectorAll(".av__check use").forEach(item => { + blockElement.querySelectorAll(".av__firstcol use").forEach(item => { item.setAttribute("xlink:href", "#iconUncheck"); }); } @@ -269,7 +269,7 @@ export const avContextmenu = (protyle: IProtyle, rowElement: HTMLElement, positi return true; } rowElement.classList.add("av__row--select"); - rowElement.querySelector(".av__check use").setAttribute("xlink:href", "#iconCheck"); + rowElement.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconCheck"); const rowIds: string[] = []; const blockIds: string[] = []; const rowElements = blockElement.querySelectorAll(".av__row--select:not(.av__row--header)"); diff --git a/app/src/protyle/render/av/keydown.ts b/app/src/protyle/render/av/keydown.ts index 9a87a06e1..2557737ff 100644 --- a/app/src/protyle/render/av/keydown.ts +++ b/app/src/protyle/render/av/keydown.ts @@ -19,7 +19,7 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl if (selectCellElement) { if (event.key === "Escape") { selectCellElement.classList.remove("av__cell--select"); - selectRow(selectCellElement.parentElement.querySelector(".av__check"), "select"); + selectRow(selectCellElement.parentElement.querySelector(".av__firstcol"), "select"); event.preventDefault(); return true; } @@ -100,11 +100,11 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl } if (event.key === "Escape") { event.preventDefault(); - selectRow(selectRowElements[0].querySelector(".av__check"), "unselectAll"); + selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll"); return true; } if (event.key === "Enter") { - selectRow(selectRowElements[0].querySelector(".av__check"), "unselectAll"); + selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll"); popTextCell(protyle, [selectRowElements[0].querySelector(".av__cell")]); event.preventDefault(); return true; @@ -112,9 +112,9 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl // TODO event.shiftKey if (event.key === "ArrowUp") { const previousRowElement = selectRowElements[0].previousElementSibling; - selectRow(selectRowElements[0].querySelector(".av__check"), "unselectAll"); + selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll"); if (previousRowElement && !previousRowElement.classList.contains("av__row--header")) { - selectRow(previousRowElement.querySelector(".av__check"), "select"); + selectRow(previousRowElement.querySelector(".av__firstcol"), "select"); cellScrollIntoView(nodeElement, previousRowElement.getBoundingClientRect(), true); } else { nodeElement.classList.add("protyle-wysiwyg--select"); @@ -124,9 +124,9 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl } if (event.key === "ArrowDown") { const nextRowElement = selectRowElements[selectRowElements.length - 1].nextElementSibling; - selectRow(selectRowElements[0].querySelector(".av__check"), "unselectAll"); + selectRow(selectRowElements[0].querySelector(".av__firstcol"), "unselectAll"); if (nextRowElement && !nextRowElement.classList.contains("av__row--add")) { - selectRow(nextRowElement.querySelector(".av__check"), "select"); + selectRow(nextRowElement.querySelector(".av__firstcol"), "select"); cellScrollIntoView(nodeElement, nextRowElement.getBoundingClientRect(), true); } else { nodeElement.classList.add("protyle-wysiwyg--select"); @@ -137,3 +137,4 @@ export const avKeydown = (event: KeyboardEvent, nodeElement: HTMLElement, protyl } return false; }; + diff --git a/app/src/protyle/render/av/render.ts b/app/src/protyle/render/av/render.ts index 903baac11..4e3732464 100644 --- a/app/src/protyle/render/av/render.ts +++ b/app/src/protyle/render/av/render.ts @@ -6,7 +6,6 @@ import * as dayjs from "dayjs"; import {unicode2Emoji} from "../../../emoji"; import {focusBlock} from "../../util/selection"; import {isMac} from "../../util/compatibility"; -import {stickyScrollY} from "../../scroll/stickyScroll"; export const avRender = (element: Element, protyle: IProtyle, cb?: () => void) => { let avElements: Element[] = []; @@ -41,6 +40,8 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: () => void) = e.firstElementChild.innerHTML = html; } const left = e.querySelector(".av__scroll")?.scrollLeft || 0; + const headerTransform = (e.querySelector(".av__row--header") as HTMLElement)?.style.transform; + const footerTransform = (e.querySelector(".av__row--footer") as HTMLElement)?.style.transform; let selectCellId = ""; const selectCellElement = e.querySelector(".av__cell--select") as HTMLElement; if (selectCellElement) { @@ -51,7 +52,7 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: () => void) = }, (response) => { const data = response.data.view as IAVTable; // header - let tableHTML = '
'; + let tableHTML = '
'; let calcHTML = ""; data.columns.forEach((column: IAVColumn) => { if (column.hidden) { @@ -79,10 +80,10 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || ' { tableHTML += `
- - + +
-
`; +
`; row.cells.forEach((cell, index) => { if (data.columns[index].hidden) { return; @@ -149,7 +150,7 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || '`; + text += ``; } } tableHTML += `
${text}
`;
-
+
${tableHTML}
-
- -
-
- ${window.siyuan.languages.addAttr} -
+ + ${window.siyuan.languages.addAttr}
- +
`; @@ -215,16 +212,11 @@ ${cell.color ? `color:${cell.color};` : ""}">${text}
`; if (left) { e.querySelector(".av__scroll").scrollLeft = left; } - const bodyElement = e.querySelector(".av__body"); - if (bodyElement) { - const headerElement = bodyElement.querySelector(".av__row--header"); - const footerElement = bodyElement.querySelector(".av__row--footer"); - stickyScrollY( - protyle.contentElement, - bodyElement as HTMLElement, - headerElement ? [{element: headerElement}] : [], - footerElement ? [{element: footerElement}] : [], - ); + if (headerTransform) { + (e.querySelector(".av__row--header") as HTMLElement).style.transform = headerTransform; + } + if (footerTransform) { + (e.querySelector(".av__row--footer") as HTMLElement).style.transform = footerTransform; } if (selectCellId) { const newCellElement = e.querySelector(`.av__row[data-id="${selectCellId.split(Constants.ZWSP)[0]}"] .av__cell[data-col-id="${selectCellId.split(Constants.ZWSP)[1]}"]`); diff --git a/app/src/protyle/render/av/row.ts b/app/src/protyle/render/av/row.ts index 3d9f79794..5a2646ba9 100644 --- a/app/src/protyle/render/av/row.ts +++ b/app/src/protyle/render/av/row.ts @@ -6,12 +6,12 @@ export const selectRow = (checkElement: Element, type: "toggle" | "select" | "un const useElement = checkElement.querySelector("use"); if (rowElement.classList.contains("av__row--header") || type === "unselectAll") { if ("#iconCheck" === useElement.getAttribute("xlink:href")) { - rowElement.parentElement.querySelectorAll(".av__check").forEach(item => { + rowElement.parentElement.querySelectorAll(".av__firstcol").forEach(item => { item.querySelector("use").setAttribute("xlink:href", "#iconUncheck"); item.parentElement.classList.remove("av__row--select"); }); } else { - rowElement.parentElement.querySelectorAll(".av__check").forEach(item => { + rowElement.parentElement.querySelectorAll(".av__firstcol").forEach(item => { item.querySelector("use").setAttribute("xlink:href", "#iconCheck"); item.parentElement.classList.add("av__row--select"); }); diff --git a/app/src/protyle/scroll/event.ts b/app/src/protyle/scroll/event.ts index f149de553..99fb14f50 100644 --- a/app/src/protyle/scroll/event.ts +++ b/app/src/protyle/scroll/event.ts @@ -4,7 +4,6 @@ import {fetchPost} from "../../util/fetch"; import {onGet} from "../util/onGet"; import {isMobile} from "../../util/functions"; import {hasClosestBlock, hasClosestByClassName} from "../util/hasClosest"; -import {stickyScrollY} from "./stickyScroll"; let getIndexTimeout: number; export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => { @@ -26,18 +25,26 @@ export const scrollEvent = (protyle: IProtyle, element: HTMLElement) => { } protyle.wysiwyg.element.querySelectorAll(".av").forEach((item: HTMLElement) => { - const bodyElement = item.querySelector(".av__body") as HTMLElement; - - if (bodyElement) { - const headerElement = bodyElement.querySelector(".av__row--header") as HTMLElement; - const footerElement = bodyElement.querySelector(".av__row--footer") as HTMLElement; - - stickyScrollY( - element, - bodyElement, - headerElement ? [{element: headerElement}] : [], - footerElement ? [{element: footerElement}] : [], - ); + if (item.parentElement.classList.contains("protyle-wysiwyg")) { + const headerTop = item.offsetTop + 43; + const headerElement = item.querySelector(".av__row--header") as HTMLElement; + if (headerElement) { + if (headerTop < element.scrollTop && headerTop + headerElement.parentElement.clientHeight > element.scrollTop) { + headerElement.style.transform = `translateY(${element.scrollTop - headerTop}px)`; + } else { + headerElement.style.transform = ""; + } + } + const footerElement = item.querySelector(".av__row--footer") as HTMLElement; + if (footerElement) { + const footerBottom = headerTop + footerElement.parentElement.clientHeight; + const scrollBottom = element.scrollTop + element.clientHeight + 5; + if (headerTop + 42 + 36 * 2 < scrollBottom && footerBottom > scrollBottom) { + footerElement.style.transform = `translateY(${scrollBottom - footerBottom}px)`; + } else { + footerElement.style.transform = ""; + } + } } }); diff --git a/app/src/protyle/scroll/stickyScroll.ts b/app/src/protyle/scroll/stickyScroll.ts deleted file mode 100644 index 42b88c3c7..000000000 --- a/app/src/protyle/scroll/stickyScroll.ts +++ /dev/null @@ -1,276 +0,0 @@ -export interface IStickyPositionY { - top: number; - bottom: number; -} - -export interface IStickyElementY { - element: HTMLElement; - offset?: number; -} - -export interface IStickyContextY extends IStickyElementY { - rect: DOMRect; - base: number; - origin: IStickyPositionY; - current: IStickyPositionY; - target: IStickyPositionY; - style: IStickyPositionY; -} - -export const stickyScrollY = ( - view: HTMLElement, // 视口元素 - container: HTMLElement, // 容器元素 - topElements: IStickyElementY[] = [], // 顶部粘性元素 - bottomElements: IStickyElementY[] = [], // 底部粘性元素 -) => { - if (topElements.length === 0 && bottomElements.length === 0) { - return; - } - - const viewRect = view.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - - /** - * ┏---------------┓ - * | view | - * ┗---------------┛ → viewRect.bottom - * ┌-----------┐ --→ containerRect.top - * | container | - * └-----------┘ - * ====== OR ====== - * ┌-----------┐ - * | container | - * └-----------┘ --→ containerRect.bottom - * ┏---------------┓ → viewRect.top - * | view | - * ┗---------------┛ - */ - if (viewRect.bottom <= containerRect.top || containerRect.bottom <= viewRect.top) { - return; - } - - const topContext: IStickyContextY[] = topElements.map(item => { - const rect = item.element.getBoundingClientRect(); - const base = Number.parseFloat(item.element.style.top) || 0; - item.offset ??= 0; - - return { - ...item, - rect, - base, - origin: { - top: rect.top - base, - bottom: rect.bottom - base, - }, - current: { - top: rect.top, - bottom: rect.bottom, - }, - target: { - top: null, - bottom: null, - }, - style: { - top: null, - bottom: null, - } - }; - }); - const bottomContext: IStickyContextY[] = bottomElements.map(item => { - const rect = item.element.getBoundingClientRect(); - const base = Number.parseFloat(item.element.style.bottom) || 0; - item.offset ??= 0; - - return { - ...item, - rect, - base, - origin: { - top: rect.top + base, - bottom: rect.bottom + base, - }, - current: { - top: rect.top, - bottom: rect.bottom, - }, - target: { - top: null, - bottom: null, - }, - style: { - top: null, - bottom: null, - } - }; - }); - - let handleTop = false; - let handleBottom = false; - - switch (true) { - /** - * ┏---------------┓ → viewRect.top - * | | - * | view | - * | ┌-----------┐ | → containerRect.top - * ┗-╊-----------╊-┛ → viewRect.bottom - * | container | - * └-----------┘ --→ containerRect.bottom - */ - case viewRect.top <= containerRect.top - && containerRect.top <= viewRect.bottom - && viewRect.top <= viewRect.bottom: - handleBottom = true; - break; - - /** - * ┏---------------┓ → viewRect.top - * | view | - * | ┌-----------┐ | → containerRect.top - * | | container | | - * | └-----------┘ | → containerRect.bottom - * ┗---------------┛ → viewRect.bottom - */ - case viewRect.top <= containerRect.top - && containerRect.bottom <= viewRect.bottom: - break; - - - /** - * ┌-----------┐ --→ containerRect.top - * ┏-╊-----------╊-┓ → viewRect.top - * | | | |}→ view - * ┗-╊-----------╊-┛ → viewRect.bottom - * | container | - * └-----------┘ --→ containerRect.bottom - */ - case containerRect.top <= viewRect.top - && viewRect.bottom <= containerRect.bottom: - handleTop = true; - handleBottom = true; - break; - - /** - * ┌-----------┐ --→ containerRect.top - * | container | - * ┏-╊-----------╊-┓ → viewRect.top - * | └-----------┘ | → containerRect.bottom - * | view | - * ┗-|-----------|-┛ → viewRect.bottom - */ - case containerRect.top <= viewRect.top - && viewRect.top <= containerRect.bottom - && containerRect.bottom <= viewRect.bottom: - handleTop = true; - break; - - default: - break; - } - - if (handleTop) { - if (topContext.length > 0) { - topContext.reduceRight((next, current) => { - switch (true) { - /** - * ┌-----------┐ --→ containerRect.top - * ┏-╊-----------╊-┓ → viewRect.top - * | | ┌┄┄┄┄┄┄┄┐ | | → current.target.top - current.offset - * | | ├╌╌╌╌╌╌╌┤ | | → current.origin.top - * | | └╌╌╌╌╌╌╌┘ | | → current.origin.bottom - * | | ┌╌╌╌╌╌╌╌┐ | | → next.origin.top - * | | └╌╌╌╌╌╌╌┘ | | → next.origin.bottom - * ┗-╊-----------╊-┛ → viewRect.bottom - * └-----------┘ --→ containerRect.bottom - */ - case viewRect.top <= (current.origin.top - current.offset): - current.target.top = current.origin.top; - current.target.bottom = current.origin.bottom; - current.style.top = null; - break; - - /** - * ┌-----------┐ --→ containerRect.top - * | ┌╌╌╌╌╌╌╌┐ | --→ current.origin.top - * | └╌╌╌╌╌╌╌┘ | --→ current.origin.bottom - * ┏-╊-----------╊-┓ → viewRect.top - * | | ┌-------┐ | | → current.target.top - * | | └-------┘ | | → current.target.bottom - * ┗-╊-----------╊-┛ → viewRect.bottom - * | container | - * └-----------┘ --→ containerRect.bottom - */ - default: - current.target.top = viewRect.top + current.offset; - current.target.bottom = current.target.top + current.rect.height; - if (next) { - const nextTop = Math.min(next.target.top, next.origin.top); - if (nextTop < current.target.bottom) { - const diff = nextTop - current.target.bottom; - current.target.top += diff; - current.target.bottom += diff; - } - } - current.style.top = current.base + (current.target.top - current.current.top); - break; - } - return current; - }, null); - } - } - if (handleBottom) { - if (bottomContext.length > 0) { - bottomContext.reduce((last, current) => { - switch (true) { - /** - * ┌-----------┐ --→ containerRect.top - * ┏-╊-----------╊-┓ → viewRect.top - * | | ┌╌╌╌╌╌╌╌┐ | | → last.origin.top - * | | └╌╌╌╌╌╌╌┘ | | → last.origin.bottom - * | | ┌╌╌╌╌╌╌╌┐ | | → current.origin.top - * | | ├╌╌╌╌╌╌╌┤ | | → current.origin.bottom - * | | └┄┄┄┄┄┄┄┘ | | → current.target.bottom + current.offset - * ┗-╊-----------╊-┛ → viewRect.bottom - * └-----------┘ --→ containerRect.bottom - */ - case (current.origin.bottom + current.offset) <= viewRect.bottom: - current.target.top = current.origin.top; - current.target.bottom = current.origin.bottom; - current.style.bottom = null; - break; - - /** - * ┌-----------┐ --→ containerRect.top - * ┏-╊-----------╊-┓ → viewRect.top - * | | ┌-------┐ | | → current.target.top - * | | └-------┘ | | → current.target.bottom - * ┗-╊-----------╊-┛ → viewRect.bottom - * | ┌╌╌╌╌╌╌╌┐ | --→ current.origin.top - * | └╌╌╌╌╌╌╌┘ | --→ current.origin.bottom - * | container | - * └-----------┘ --→ containerRect.bottom - */ - default: - current.target.bottom = viewRect.bottom - current.offset; - current.target.top = current.target.bottom - current.rect.height; - if (last) { - const lastBottom = Math.max(last.target.bottom, last.origin.bottom); - if (current.target.top < lastBottom) { - const diff = lastBottom - current.target.top; - current.target.top += diff; - current.target.bottom += diff; - } - } - current.style.bottom = current.base - (current.target.bottom - current.current.bottom); - break; - } - return current; - }, null); - } - } - - [...topContext, ...bottomContext].forEach(item => { - item.element.style.top = item.style.top ? `${item.style.top}px` : null; - item.element.style.bottom = item.style.bottom ? `${item.style.bottom}px` : null; - }); -} diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index 90fa8b42b..bb1f823a4 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -189,7 +189,7 @@ export class WYSIWYG { protyle.wysiwyg.element.querySelectorAll(".img--select, .av__cell--select, .av__row--select").forEach((item: HTMLElement) => { if (item.classList.contains("av__row--select") && !hasClosestByClassName(element, "av")) { item.classList.remove("av__row--select"); - item.querySelector(".av__check use").setAttribute("xlink:href", "#iconUncheck"); + item.querySelector(".av__firstcol use").setAttribute("xlink:href", "#iconUncheck"); updateHeader(item); } else { item.classList.remove("img--select", "av__cell--select");