siyuan/app/src/protyle/render/av/row.ts

534 lines
22 KiB
TypeScript

import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest";
import {focusBlock} from "../../util/selection";
import {Menu} from "../../../plugin/Menu";
import {transaction} from "../../wysiwyg/transaction";
import {
addDragFill,
genCellValueByElement,
getTypeByCellElement,
popTextCell,
renderCell,
renderCellAttr
} from "./cell";
import {fetchPost} from "../../../util/fetch";
import {showMessage} from "../../../dialog/message";
import * as dayjs from "dayjs";
import {Constants} from "../../../constants";
import {insertGalleryItemAnimation} from "./gallery/item";
import {clearSelect} from "../../util/clearSelect";
export const getFieldIdByCellElement = (cellElement: Element, viewType: TAVView): string => {
if (hasClosestByClassName(cellElement, "custom-attr")) {
return (hasClosestByClassName(cellElement, "av__row") as HTMLElement).dataset.id;
}
return (hasClosestByClassName(cellElement, viewType === "table" ? "av__row" : "av__gallery-item") as HTMLElement).dataset.id;
};
export const selectRow = (checkElement: Element, type: "toggle" | "select" | "unselect" | "unselectAll") => {
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") || type === "unselectAll") {
rowElement.parentElement.querySelectorAll(".av__firstcol").forEach(item => {
item.querySelector("use").setAttribute("xlink:href", "#iconUncheck");
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");
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")) {
rowElement.classList.add("av__row--select");
useElement.setAttribute("xlink:href", "#iconCheck");
} else if (type === "unselect" || (useElement.getAttribute("xlink:href") === "#iconCheck" && type === "toggle")) {
rowElement.classList.remove("av__row--select");
useElement.setAttribute("xlink:href", "#iconUncheck");
}
}
focusBlock(hasClosestBlock(rowElement) as HTMLElement);
updateHeader(rowElement);
};
export const updateHeader = (rowElement: HTMLElement) => {
const blockElement = hasClosestBlock(rowElement);
if (!blockElement) {
return;
}
const selectCount = rowElement.parentElement.querySelectorAll(".av__row--select:not(.av__row--header)").length;
const count = rowElement.parentElement.querySelectorAll(".av__row:not(.av__row--header)").length;
const headElement = rowElement.parentElement.firstElementChild;
const headUseElement = headElement.querySelector("use");
if (count === selectCount && count !== 0) {
headElement.classList.add("av__row--select");
headUseElement.setAttribute("xlink:href", "#iconCheck");
} else if (selectCount === 0) {
headElement.classList.remove("av__row--select");
headUseElement.setAttribute("xlink:href", "#iconUncheck");
} else if (selectCount > 0) {
headElement.classList.add("av__row--select");
headUseElement.setAttribute("xlink:href", "#iconIndeterminateCheck");
}
const counterElement = blockElement.querySelector(".av__counter");
const allCount = blockElement.querySelectorAll(".av__row--select:not(.av__row--header)").length;
if (allCount === 0) {
counterElement.classList.add("fn__none");
return;
}
counterElement.classList.remove("fn__none");
counterElement.innerHTML = `${allCount} ${window.siyuan.languages.selected}`;
};
export const setPage = (blockElement: Element) => {
const avType = blockElement.getAttribute("data-av-type") as TAVView;
blockElement.querySelectorAll(".av__body").forEach((item: HTMLElement) => {
const pageSize = item.dataset.pageSize;
if (pageSize) {
const currentCount = item.querySelectorAll(avType === "table" ? ".av__row:not(.av__row--header)" : ".av__gallery-item").length;
if (parseInt(pageSize) < currentCount) {
item.dataset.pageSize = currentCount.toString();
}
}
});
};
/**
* 前端插入一假行
* @param options.protyle
* @param options.blockElement
* @param options.srcIDs
* @param options.previousId
* @param options.avId 存在为新增否则为拖拽插入
*/
export const insertAttrViewBlockAnimation = (options: {
protyle: IProtyle,
blockElement: Element,
srcIDs: string[],
previousId: string,
groupID?: string
}) => {
if ((options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement).value !== "") {
showMessage(window.siyuan.languages.insertRowTip);
return;
}
const avId = options.blockElement.getAttribute("data-av-id");
const groupQuery = options.groupID ? `.av__body[data-group-id="${options.groupID}"] ` : "";
let previousElement = options.blockElement.querySelector(`.av__row[data-id="${options.previousId}"]`) || options.blockElement.querySelector(groupQuery + ".av__row--header");
// 有排序需要加入最后一行
if (options.blockElement.querySelector('.av__views [data-type="av-sort"]').classList.contains("block__icon--active")) {
previousElement = options.blockElement.querySelector(groupQuery + ".av__row--util").previousElementSibling;
}
let colHTML = '<div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div></div>';
const pinIndex = previousElement.querySelectorAll(".av__colsticky .av__cell").length - 1;
if (pinIndex > -1) {
colHTML = '<div class="av__colsticky"><div class="av__firstcol"><svg><use xlink:href="#iconUncheck"></use></svg></div>';
}
previousElement.querySelectorAll(".av__cell").forEach((item: HTMLElement, index) => {
let lineNumber = "";
if (getTypeByCellElement(item) === "lineNumber") {
const lineNumberValue = item.querySelector(".av__celltext")?.getAttribute("data-value");
if (lineNumberValue) {
lineNumber = (parseInt(lineNumberValue) + 1).toString();
}
}
colHTML += `<div class="av__cell" data-col-id="${item.dataset.colId}"
style="width: ${item.style.width};${item.dataset.dtype === "number" ? "text-align: right;" : ""}"
${getTypeByCellElement(item) === "block" ? ' data-detached="true"' : ""}><span class="${avId ? "av__celltext" : "av__pulse"}">${lineNumber}</span></div>`;
if (pinIndex === index) {
colHTML += "</div>";
}
});
let html = "";
options.srcIDs.forEach((id) => {
const blockCellElement = options.blockElement.querySelector(`[data-block-id="${id}"]`);
if (!blockCellElement) {
html += `<div class="av__row" data-type="ghost" data-id="${id}" data-avid="${avId}" data-previous-id="${options.previousId}">
${colHTML}
</div>`;
} else {
clearSelect(["cell"], options.blockElement);
addDragFill(blockCellElement);
blockCellElement.classList.add("av__cell--select");
}
});
previousElement.insertAdjacentHTML("afterend", html);
if (avId) {
const currentRow = previousElement.nextElementSibling;
if (options.blockElement.querySelector('.av__views [data-type="av-sort"]').classList.contains("block__icon--active") &&
!options.blockElement.querySelector('[data-type="av-load-more"]').classList.contains("fn__none")) {
currentRow.setAttribute("data-need-update", "true");
}
const sideRow = previousElement.classList.contains("av__row--header") ? currentRow.nextElementSibling : previousElement;
fetchPost("/api/av/getAttributeViewFilterSort", {
id: avId,
blockID: options.blockElement.getAttribute("data-node-id")
}, (response) => {
// https://github.com/siyuan-note/siyuan/issues/10517
let hideTextCell = false;
response.data.filters.find((item: IAVFilter) => {
const headerElement = options.blockElement.querySelector(`.av__cell--header[data-col-id="${item.column}"]`);
if (!headerElement) {
return;
}
const filterType = headerElement.getAttribute("data-dtype");
if (item.value && filterType !== item.value.type) {
return;
}
if (["relation", "rollup", "template"].includes(filterType)) {
hideTextCell = true;
return true;
}
// 根据后台计算出显示与否的结果进行标识,以便于在 refreshAV 中更新 UI
if (["created", "updated"].includes(filterType)) {
currentRow.setAttribute("data-need-update", "true");
} else {
response.data.sorts.find((sortItem: IAVSort) => {
if (sortItem.column === item.column) {
currentRow.setAttribute("data-need-update", "true");
return true;
}
});
}
// 当空或非空外,需要根据值进行判断
let isRenderValue = true;
if (item.operator !== "Is empty" && item.operator !== "Is not empty") {
switch (item.value.type) {
case "select":
case "mSelect":
if (!item.value.mSelect || item.value.mSelect.length === 0) {
isRenderValue = false;
}
break;
case "block":
if (!item.value.block || !item.value.block.content) {
isRenderValue = false;
}
break;
case "number":
if (!item.value.number || !item.value.number.isNotEmpty) {
isRenderValue = false;
}
break;
case "date":
case "created":
case "updated":
if (!item.value[item.value.type] || !item.value[item.value.type].isNotEmpty) {
isRenderValue = false;
}
break;
case "mAsset":
if (!item.value.mAsset || item.value.mAsset.length === 0) {
isRenderValue = false;
}
break;
case "checkbox":
if (!item.value.checkbox) {
isRenderValue = false;
}
break;
case "text":
case "url":
case "phone":
case "email":
if (!item.value[item.value.type] || !item.value[item.value.type].content) {
isRenderValue = false;
}
break;
}
}
if (sideRow.classList.contains("av__row") && isRenderValue) {
const sideRowCellElement = sideRow.querySelector(`.av__cell[data-col-id="${item.column}"]`) as HTMLElement;
const cellElement = currentRow.querySelector(`.av__cell[data-col-id="${item.column}"]`);
const cellValue = genCellValueByElement(getTypeByCellElement(sideRowCellElement), sideRowCellElement);
const iconElement = cellElement.querySelector(".b3-menu__avemoji");
cellElement.innerHTML = renderCell(cellValue, 0, iconElement ? !iconElement.classList.contains("fn__none") : false);
renderCellAttr(cellElement, cellValue);
}
});
if (hideTextCell) {
currentRow.remove();
showMessage(window.siyuan.languages.insertRowTip);
} else if (options.srcIDs.length === 1) {
popTextCell(options.protyle, [currentRow.querySelector('.av__cell[data-detached="true"]')], "block");
}
setPage(options.blockElement);
});
}
setPage(options.blockElement);
};
export const stickyRow = (blockElement: HTMLElement, elementRect: DOMRect, status: "top" | "bottom" | "all") => {
if (blockElement.dataset.avType !== "table") {
return;
}
// 只读模式下也需固定 https://github.com/siyuan-note/siyuan/issues/11338
const headerElements = blockElement.querySelectorAll(".av__row--header");
if (headerElements.length > 0 && (status === "top" || status === "all")) {
headerElements.forEach((item: HTMLElement) => {
const bodyRect = item.parentElement.getBoundingClientRect();
const distance = Math.floor(elementRect.top - bodyRect.top);
if (distance > 0 && distance < bodyRect.height - item.clientHeight) {
item.style.transform = `translateY(${distance}px)`;
} else {
item.style.transform = "";
}
});
}
const footerElements = blockElement.querySelectorAll(".av__row--footer");
if (footerElements.length > 0 && (status === "bottom" || status === "all")) {
footerElements.forEach((item: HTMLElement) => {
if (item.querySelector(".av__calc--ashow")) {
const bodyRect = item.parentElement.getBoundingClientRect();
const distance = Math.ceil(elementRect.bottom - bodyRect.bottom);
if (distance < 0 && -distance < bodyRect.height - item.clientHeight) {
item.style.transform = `translateY(${distance}px)`;
} else {
item.style.transform = "";
}
} else {
item.style.transform = "";
}
});
}
};
const updatePageSize = (options: {
currentPageSize: string,
newPageSize: string,
protyle: IProtyle,
avID: string,
nodeElement: Element
}) => {
if (options.currentPageSize === options.newPageSize) {
return;
}
options.nodeElement.querySelectorAll(".av__body").forEach((item: HTMLElement) => {
item.dataset.pageSize = options.newPageSize;
});
const blockID = options.nodeElement.getAttribute("data-node-id");
transaction(options.protyle, [{
action: "setAttrViewPageSize",
avID: options.avID,
data: parseInt(options.newPageSize),
blockID
}], [{
action: "setAttrViewPageSize",
data: parseInt(options.currentPageSize),
avID: options.avID,
blockID
}]);
document.querySelector(".av__panel")?.remove();
};
export const setPageSize = (options: {
target: HTMLElement,
protyle: IProtyle,
avID: string,
nodeElement: Element
}) => {
const menu = new Menu("av-page-size");
if (menu.isOpen) {
return;
}
const currentPageSize = options.target.dataset.size;
menu.addItem({
iconHTML: "",
label: "10",
checked: currentPageSize === "10",
click() {
updatePageSize({
currentPageSize,
newPageSize: "10",
protyle: options.protyle,
avID: options.avID,
nodeElement: options.nodeElement
});
}
});
menu.addItem({
iconHTML: "",
checked: currentPageSize === "25",
label: "25",
click() {
updatePageSize({
currentPageSize,
newPageSize: "25",
protyle: options.protyle,
avID: options.avID,
nodeElement: options.nodeElement
});
}
});
menu.addItem({
iconHTML: "",
checked: currentPageSize === "50",
label: "50",
click() {
updatePageSize({
currentPageSize,
newPageSize: "50",
protyle: options.protyle,
avID: options.avID,
nodeElement: options.nodeElement
});
}
});
menu.addItem({
iconHTML: "",
checked: currentPageSize === "100",
label: "100",
click() {
updatePageSize({
currentPageSize,
newPageSize: "100",
protyle: options.protyle,
avID: options.avID,
nodeElement: options.nodeElement
});
}
});
menu.addItem({
iconHTML: "",
checked: currentPageSize === Constants.SIZE_DATABASE_MAZ_SIZE.toString(),
label: window.siyuan.languages.all,
click() {
updatePageSize({
currentPageSize,
newPageSize: Constants.SIZE_DATABASE_MAZ_SIZE.toString(),
protyle: options.protyle,
avID: options.avID,
nodeElement: options.nodeElement
});
}
});
const rect = options.target.getBoundingClientRect();
menu.open({
x: rect.left,
y: rect.bottom
});
};
export const deleteRow = (blockElement: HTMLElement, protyle: IProtyle) => {
const rowElements = blockElement.querySelectorAll(".av__row--select:not(.av__row--header), .av__gallery-item--select");
if (rowElements.length === 0) {
return;
}
const avID = blockElement.getAttribute("data-av-id");
const undoOperations: IOperation[] = [];
const blockIds: string[] = [];
rowElements.forEach(item => {
blockIds.push(item.querySelector(".av__cell[data-block-id]").getAttribute("data-block-id"));
});
rowElements.forEach(item => {
const blockValue = genCellValueByElement("block", item.querySelector(".av__cell[data-block-id]"));
undoOperations.push({
action: "insertAttrViewBlock",
avID,
previousID: item.previousElementSibling?.getAttribute("data-id") || "",
srcs: [{
id: item.getAttribute("data-id"),
isDetached: blockValue.isDetached,
content: blockValue.block.content
}],
blockID: blockElement.dataset.nodeId,
groupID: item.parentElement.getAttribute("data-group-id")
});
});
const newUpdated = dayjs().format("YYYYMMDDHHmmss");
undoOperations.push({
action: "doUpdateUpdated",
id: blockElement.dataset.nodeId,
data: blockElement.getAttribute("updated")
});
transaction(protyle, [{
action: "removeAttrViewBlock",
srcIDs: blockIds,
avID,
}, {
action: "doUpdateUpdated",
id: blockElement.dataset.nodeId,
data: newUpdated,
}], undoOperations);
rowElements.forEach(item => {
item.remove();
});
stickyRow(blockElement, protyle.contentElement.getBoundingClientRect(), "all");
updateHeader(blockElement.querySelector(".av__row"));
blockElement.setAttribute("updated", newUpdated);
};
export const insertRows = (options: {
blockElement: HTMLElement,
protyle: IProtyle,
count: number,
previousID: string,
groupID?: string
}) => {
const avID = options.blockElement.getAttribute("data-av-id");
const srcIDs: string[] = [];
const srcs: IOperationSrcs[] = [];
new Array(options.count).fill(0).forEach(() => {
const newNodeID = Lute.NewNodeID();
srcIDs.push(newNodeID);
srcs.push({
id: newNodeID,
isDetached: true,
content: "",
});
});
const newUpdated = dayjs().format("YYYYMMDDHHmmss");
transaction(options.protyle, [{
action: "insertAttrViewBlock",
avID,
previousID: options.previousID,
srcs,
blockID: options.blockElement.dataset.nodeId,
groupID: options.groupID
}, {
action: "doUpdateUpdated",
id: options.blockElement.dataset.nodeId,
data: newUpdated,
}], [{
action: "removeAttrViewBlock",
srcIDs,
avID,
}, {
action: "doUpdateUpdated",
id: options.blockElement.dataset.nodeId,
data: options.blockElement.getAttribute("updated")
}]);
if (options.blockElement.getAttribute("data-av-type") === "gallery") {
insertGalleryItemAnimation({
blockElement: options.blockElement,
protyle: options.protyle,
srcIDs,
previousId: options.previousID,
groupID: options.groupID
});
} else {
insertAttrViewBlockAnimation({
protyle: options.protyle,
blockElement: options.blockElement,
srcIDs,
previousId: options.previousID,
groupID: options.groupID
});
}
options.blockElement.setAttribute("updated", newUpdated);
};