diff --git a/app/src/assets/scss/component/_menu.scss b/app/src/assets/scss/component/_menu.scss
index 6bc11d7ec..dd4668a6f 100644
--- a/app/src/assets/scss/component/_menu.scss
+++ b/app/src/assets/scss/component/_menu.scss
@@ -154,6 +154,7 @@
color: var(--b3-theme-on-background);
position: relative;
box-sizing: border-box;
+ margin: 1px 0;
&[disabled="disabled"] {
cursor: not-allowed;
diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts
index 3ab8b3783..2f6966b4b 100644
--- a/app/src/protyle/render/av/cell.ts
+++ b/app/src/protyle/render/av/cell.ts
@@ -1,15 +1,20 @@
import {transaction} from "../../wysiwyg/transaction";
import {hasClosestBlock, hasClosestByClassName} from "../../util/hasClosest";
+import {openMenuPanel} from "./openMenuPanel";
export const popTextCell = (protyle: IProtyle, cellElement: HTMLElement) => {
- const type = cellElement.parentElement.parentElement.firstElementChild.children[parseInt(cellElement.getAttribute("data-index")) + 1].getAttribute("data-dtype") as TAVCol;
+ const type = cellElement.parentElement.parentElement.firstElementChild.querySelector(`[data-col-id="${cellElement.getAttribute("data-col-id")}"]`).getAttribute("data-dtype") as TAVCol;
const cellRect = cellElement.getBoundingClientRect();
let html = "";
const style = `style="position:absolute;left: ${cellRect.left}px;top: ${cellRect.top}px;width:${Math.max(cellRect.width, 200)}px;height: ${cellRect.height}px"`
+ const blockElement = hasClosestBlock(cellElement);
if (type === "block" || type === "text") {
html = ``;
} else if (type === "number") {
html = ``;
+ } else if (type === "select" && blockElement) {
+ openMenuPanel(protyle, blockElement, "select", {cellElement});
+ return;
}
document.body.insertAdjacentHTML("beforeend", `
${html}
diff --git a/app/src/protyle/render/av/col.ts b/app/src/protyle/render/av/col.ts
index b7996538d..fb8b4fc14 100644
--- a/app/src/protyle/render/av/col.ts
+++ b/app/src/protyle/render/av/col.ts
@@ -50,20 +50,21 @@ export const updateHeader = (rowElement: HTMLElement) => {
avHeadElement.style.position = "sticky";
};
-const removeCol = (cellElement: HTMLElement) => {
- const index = cellElement.getAttribute("data-index");
+const removeCol = (cellElement:HTMLElement) => {
const blockElement = hasClosestBlock(cellElement);
if (!blockElement) {
return false;
}
+ const colId = cellElement.getAttribute("data-col-id");
blockElement.querySelectorAll(".av__row").forEach((item) => {
- item.querySelector(`[data-index="${index}"]`).remove();
+ item.querySelector(`[data-col-id="${colId}"]`).remove();
});
+ cellElement.remove();
};
export const showColMenu = (protyle: IProtyle, blockElement: HTMLElement, cellElement: HTMLElement) => {
const type = cellElement.getAttribute("data-dtype") as TAVCol;
- const colId = cellElement.getAttribute("data-id");
+ const colId = cellElement.getAttribute("data-col-id");
const avId = blockElement.getAttribute("data-av-id");
const menu = new Menu("av-header-cell", () => {
const newValue = (window.siyuan.menus.menu.element.querySelector(".b3-text-field") as HTMLInputElement).value;
diff --git a/app/src/protyle/render/av/filter.ts b/app/src/protyle/render/av/filter.ts
new file mode 100644
index 000000000..754a40761
--- /dev/null
+++ b/app/src/protyle/render/av/filter.ts
@@ -0,0 +1,222 @@
+import {Menu} from "../../../plugin/Menu";
+import {transaction} from "../../wysiwyg/transaction";
+import {hasClosestByClassName} from "../../util/hasClosest";
+import {getColIconByType} from "./col";
+import {setPosition} from "../../../util/setPosition";
+
+export const setFilter = (options: {
+ filter: IAVFilter,
+ protyle: IProtyle,
+ data: IAV,
+ target: HTMLElement,
+}) => {
+ const colType = Object.keys(options.filter.value)[0] as TAVCol;
+ const rectTarget = options.target.getBoundingClientRect();
+ const menu = new Menu("set-filter-" + options.filter.column, () => {
+ const oldFilters = JSON.parse(JSON.stringify(options.data.filters));
+ options.data.filters.find((filter) => {
+ if (filter.column === options.filter.column) {
+ let cellValue: IAVCellValue;
+ if (colType === "number") {
+ if (textElement.value) {
+ cellValue = {
+ content: parseFloat(textElement.value),
+ isNotEmpty: true
+ }
+ } else {
+ cellValue = {}
+ }
+ } else {
+ cellValue = {
+ content: textElement.value
+ }
+ }
+ filter.value[colType] = cellValue;
+ filter.operator = (window.siyuan.menus.menu.element.querySelector(".b3-select") as HTMLSelectElement).value as TAVFilterOperator;
+ return true;
+ }
+ });
+ transaction(options.protyle, [{
+ action: "setAttrView",
+ id: options.data.id,
+ data: {
+ filters: options.data.filters
+ }
+ }], [{
+ action: "setAttrView",
+ id: options.data.id,
+ data: {
+ filters: oldFilters
+ }
+ }]);
+ const menuElement = hasClosestByClassName(options.target, "b3-menu");
+ if (menuElement) {
+ menuElement.innerHTML = getFiltersHTML(options.data);
+ }
+ });
+ if (menu.isOpen) {
+ return;
+ }
+ let selectHTML = "";
+ switch (colType) {
+ case "text":
+ selectHTML = `
+
+
+
+
+
+
+
+`;
+ break;
+ case "number":
+ selectHTML = `
+
+
+
+
+
+
+
+`;
+ break;
+ }
+ menu.addItem({
+ iconHTML: "",
+ label: `
`
+ });
+ menu.addItem({
+ iconHTML: "",
+ label: `
`
+ });
+ const textElement = (window.siyuan.menus.menu.element.querySelector(".b3-text-field") as HTMLInputElement);
+ textElement.addEventListener("keydown", (event) => {
+ if (event.isComposing) {
+ event.preventDefault();
+ return;
+ }
+ if (event.key === "Enter") {
+ menu.close();
+ event.preventDefault();
+ }
+ });
+ menu.open({x: rectTarget.left, y: rectTarget.bottom});
+ textElement.select();
+};
+
+export const addFilter = (options: {
+ data: IAV,
+ rect: DOMRect,
+ menuElement: HTMLElement,
+ tabRect: DOMRect,
+ avId: string,
+ protyle: IProtyle
+}) => {
+ const menu = new Menu("av-add-filter");
+ options.data.columns.forEach((column) => {
+ let hasFilter = false;
+ options.data.filters.find((filter) => {
+ if (filter.column === column.id) {
+ hasFilter = true;
+ return true;
+ }
+ });
+ if (!hasFilter) {
+ menu.addItem({
+ label: column.name,
+ icon: getColIconByType(column.type),
+ click: () => {
+ const oldFilters = Object.assign([], options.data.filters);
+ let cellValue = {}
+ if (column.type !== "number") {
+ cellValue = {content: ""}
+ }
+ options.data.filters.push({
+ column: column.id,
+ operator: "Contains",
+ value: {
+ [column.type]: cellValue
+ },
+ });
+ transaction(options.protyle, [{
+ action: "setAttrView",
+ id: options.avId,
+ data: {
+ filters: options.data.filters
+ }
+ }], [{
+ action: "setAttrView",
+ id: options.avId,
+ data: {
+ filters: oldFilters
+ }
+ }]);
+ options.menuElement.innerHTML = getFiltersHTML(options.data);
+ setPosition(options.menuElement, options.tabRect.right - options.menuElement.clientWidth, options.tabRect.bottom, options.tabRect.height);
+ const filterElement = options.menuElement.querySelector(`[data-id="${column.id}"] .b3-chip`) as HTMLElement;
+ const colType = filterElement.getAttribute("data-coltype") as TAVCol;
+ setFilter({
+ filter: {
+ operator: filterElement.dataset.op as TAVFilterOperator,
+ column: filterElement.parentElement.parentElement.dataset.id,
+ value: {
+ [colType]: {content: filterElement.dataset.value}
+ }
+ },
+ protyle: options.protyle,
+ data: options.data,
+ target: filterElement
+ });
+ }
+ });
+ }
+ });
+ menu.open({
+ x: options.rect.left,
+ y: options.rect.bottom,
+ h: options.rect.height,
+ });
+};
+
+export const getFiltersHTML = (data: IAV) => {
+ let html = "";
+ const genFilterItem = (filter: IAVFilter) => {
+ let filterHTML = "";
+ data.columns.find((item) => {
+ if (item.id === filter.column) {
+ const filterValue = (filter.value && filter.value[item.type] && filter.value[item.type].content) ? filter.value[item.type].content : "";
+ filterHTML += `
+
+ ${item.name}${filterValue ? ": " + filterValue : ""}
+`;
+ return true;
+ }
+ });
+ return filterHTML;
+ };
+ data.filters.forEach((item: IAVFilter) => {
+ html += ``;
+ });
+ return `
+
+${html}
+
+`;
+};
diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts
index 07a93fc01..b0ee488a4 100644
--- a/app/src/protyle/render/av/openMenuPanel.ts
+++ b/app/src/protyle/render/av/openMenuPanel.ts
@@ -3,10 +3,15 @@ import {fetchPost} from "../../../util/fetch";
import {addCol} from "./addCol";
import {getColIconByType} from "./col";
import {setPosition} from "../../../util/setPosition";
-import {Menu} from "../../../plugin/Menu";
-import {hasClosestByAttribute, hasClosestByClassName} from "../../util/hasClosest";
+import {hasClosestByAttribute} from "../../util/hasClosest";
+import {bindSelectEvent, getSelectHTML, setSelectOption} from "./select";
+import {addFilter, getFiltersHTML} from "./filter";
+import {addSort, bindSortsEvent, getSortsHTML} from "./sort";
-export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type: "properties" | "config" | "sorts" | "filters" = "config") => {
+export const openMenuPanel = (protyle: IProtyle,
+ blockElement: HTMLElement,
+ type: "select" | "properties" | "config" | "sorts" | "filters" = "config",
+ options?: any) => {
let avPanelElement = document.querySelector(".av__panel");
if (avPanelElement) {
avPanelElement.remove();
@@ -25,16 +30,26 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type
html = getSortsHTML(data);
} else if (type === "filters") {
html = getFiltersHTML(data);
+ } else if (type === "select") {
+ html = getSelectHTML(data, options);
}
+
document.body.insertAdjacentHTML("beforeend", `
`);
-
avPanelElement = document.querySelector(".av__panel");
const menuElement = avPanelElement.lastElementChild as HTMLElement;
const tabRect = blockElement.querySelector(".layout-tab-bar").getBoundingClientRect();
- setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height);
+ if (options && options.cellElement) {
+ const cellRect = options.cellElement.getBoundingClientRect();
+ setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height);
+ bindSelectEvent(protyle, data, menuElement, options);
+ menuElement.querySelector("input").select();
+ menuElement.querySelector("input").focus();
+ } else {
+ setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height);
+ }
bindSortsEvent(protyle, menuElement, data);
avPanelElement.addEventListener("dragstart", (event) => {
@@ -155,7 +170,6 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type
}
dragoverElement = targetElement;
});
-
avPanelElement.addEventListener("dragleave", (event) => {
const target = event.target as HTMLElement;
const targetElement = hasClosestByAttribute(target, "data-id", null);
@@ -169,6 +183,7 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type
window.siyuan.dragElement = undefined;
}
});
+
avPanelElement.addEventListener("click", (event) => {
event.preventDefault();
let target = event.target as HTMLElement;
@@ -416,6 +431,10 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type
setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height);
event.stopPropagation();
break;
+ } else if (type === "editOption") {
+ setSelectOption(protyle, data, options, target)
+ event.stopPropagation();
+ break;
}
target = target.parentElement;
}
@@ -423,358 +442,12 @@ export const openMenuPanel = (protyle: IProtyle, blockElement: HTMLElement, type
});
};
-const addSort = (options: {
- data: IAV,
- rect: DOMRect,
- menuElement: HTMLElement,
- tabRect: DOMRect,
- avId: string,
- protyle: IProtyle
-}) => {
- const menu = new Menu("av-add-sort");
- options.data.columns.forEach((column) => {
- let hasSort = false;
- options.data.sorts.find((sort) => {
- if (sort.column === column.id) {
- hasSort = true;
- return true;
- }
- });
- if (!hasSort) {
- menu.addItem({
- label: column.name,
- icon: getColIconByType(column.type),
- click: () => {
- const oldSorts = Object.assign([], options.data.sorts);
- options.data.sorts.push({
- column: column.id,
- order: "ASC",
- });
- transaction(options.protyle, [{
- action: "setAttrView",
- id: options.avId,
- data: {
- sorts: options.data.sorts
- }
- }], [{
- action: "setAttrView",
- id: options.avId,
- data: {
- sorts: oldSorts
- }
- }]);
- options.menuElement.innerHTML = getSortsHTML(options.data);
- bindSortsEvent(options.protyle, options.menuElement, options.data);
- setPosition(options.menuElement, options.tabRect.right - options.menuElement.clientWidth, options.tabRect.bottom, options.tabRect.height);
- }
- });
- }
- });
- menu.open({
- x: options.rect.left,
- y: options.rect.bottom,
- h: options.rect.height,
- });
-};
-
-const bindSortsEvent = (protyle: IProtyle, menuElement: HTMLElement, data: IAV) => {
- menuElement.querySelectorAll("select").forEach((item: HTMLSelectElement) => {
- item.addEventListener("change", () => {
- const colId = item.parentElement.getAttribute("data-id");
- const oldSort = JSON.parse(JSON.stringify(data.sorts));
- if (item.previousElementSibling.classList.contains("b3-menu__icon")) {
- data.sorts.find((sort: IAVSort) => {
- if (sort.column === colId) {
- sort.column = item.value;
- item.parentElement.setAttribute("data-id", item.value);
- return true;
- }
- });
- } else {
- data.sorts.find((sort: IAVSort) => sort.column === colId).order = item.value as "ASC" | "DESC";
- }
- transaction(protyle, [{
- action: "setAttrView",
- id: data.id,
- data: {
- sorts: data.sorts
- }
- }], [{
- action: "setAttrView",
- id: data.id,
- data: {
- sorts: oldSort
- }
- }]);
- });
- });
-};
-
-const getSortsHTML = (data: IAV) => {
- let html = "";
- const genSortItem = (id: string) => {
- let sortHTML = "";
- data.columns.forEach((item) => {
- sortHTML += `
`;
- });
- return sortHTML;
- };
- data.sorts.forEach((item: IAVSort) => {
- html += ``;
- });
- return `
-
-${html}
-
-`;
-};
-
-export const setFilter = (options: {
- filter: IAVFilter,
- protyle: IProtyle,
- data: IAV,
- target: HTMLElement,
-}) => {
- const colType = Object.keys(options.filter.value)[0] as TAVCol;
- const rectTarget = options.target.getBoundingClientRect();
- const menu = new Menu("set-filter-" + options.filter.column, () => {
- const oldFilters = JSON.parse(JSON.stringify(options.data.filters));
- options.data.filters.find((filter) => {
- if (filter.column === options.filter.column) {
- let cellValue: IAVCellValue;
- if (colType === "number") {
- if (textElement.value) {
- cellValue = {
- content: parseFloat(textElement.value),
- isNotEmpty: true
- }
- } else {
- cellValue = {}
- }
- } else {
- cellValue = {
- content: textElement.value
- }
- }
- filter.value[colType] = cellValue;
- filter.operator = (window.siyuan.menus.menu.element.querySelector(".b3-select") as HTMLSelectElement).value as TAVFilterOperator;
- return true;
- }
- });
- transaction(options.protyle, [{
- action: "setAttrView",
- id: options.data.id,
- data: {
- filters: options.data.filters
- }
- }], [{
- action: "setAttrView",
- id: options.data.id,
- data: {
- filters: oldFilters
- }
- }]);
- const menuElement = hasClosestByClassName(options.target, "b3-menu");
- if (menuElement) {
- menuElement.innerHTML = getFiltersHTML(options.data);
- }
- });
- if (menu.isOpen) {
- return;
- }
- let selectHTML = "";
- switch (colType) {
- case "text":
- selectHTML = `
-
-
-
-
-
-
-
-`;
- break;
- case "number":
- selectHTML = `
-
-
-
-
-
-
-
-`;
- break;
- }
- menu.addItem({
- iconHTML: "",
- label: `
`
- });
- menu.addItem({
- iconHTML: "",
- label: `
`
- });
- const textElement = (window.siyuan.menus.menu.element.querySelector(".b3-text-field") as HTMLInputElement);
- textElement.addEventListener("keydown", (event) => {
- if (event.isComposing) {
- event.preventDefault();
- return;
- }
- if (event.key === "Enter") {
- menu.close();
- event.preventDefault();
- }
- });
- menu.open({x: rectTarget.left, y: rectTarget.bottom});
- textElement.select();
-};
-
-const addFilter = (options: {
- data: IAV,
- rect: DOMRect,
- menuElement: HTMLElement,
- tabRect: DOMRect,
- avId: string,
- protyle: IProtyle
-}) => {
- const menu = new Menu("av-add-filter");
- options.data.columns.forEach((column) => {
- let hasFilter = false;
- options.data.filters.find((filter) => {
- if (filter.column === column.id) {
- hasFilter = true;
- return true;
- }
- });
- if (!hasFilter) {
- menu.addItem({
- label: column.name,
- icon: getColIconByType(column.type),
- click: () => {
- const oldFilters = Object.assign([], options.data.filters);
- let cellValue = {}
- if (column.type !== "number") {
- cellValue = {content: ""}
- }
- options.data.filters.push({
- column: column.id,
- operator: "Contains",
- value: {
- [column.type]: cellValue
- },
- });
- transaction(options.protyle, [{
- action: "setAttrView",
- id: options.avId,
- data: {
- filters: options.data.filters
- }
- }], [{
- action: "setAttrView",
- id: options.avId,
- data: {
- filters: oldFilters
- }
- }]);
- options.menuElement.innerHTML = getFiltersHTML(options.data);
- setPosition(options.menuElement, options.tabRect.right - options.menuElement.clientWidth, options.tabRect.bottom, options.tabRect.height);
- const filterElement = options.menuElement.querySelector(`[data-id="${column.id}"] .b3-chip`) as HTMLElement;
- const colType = filterElement.getAttribute("data-coltype") as TAVCol;
- setFilter({
- filter: {
- operator: filterElement.dataset.op as TAVFilterOperator,
- column: filterElement.parentElement.parentElement.dataset.id,
- value: {
- [colType]: {content: filterElement.dataset.value}
- }
- },
- protyle: options.protyle,
- data: options.data,
- target: filterElement
- });
- }
- });
- }
- });
- menu.open({
- x: options.rect.left,
- y: options.rect.bottom,
- h: options.rect.height,
- });
-};
-
-const getFiltersHTML = (data: IAV) => {
- let html = "";
- const genFilterItem = (filter: IAVFilter) => {
- let filterHTML = "";
- data.columns.find((item) => {
- if (item.id === filter.column) {
- const filterValue = (filter.value && filter.value[item.type] && filter.value[item.type].content) ? filter.value[item.type].content : "";
- filterHTML += `
-
- ${item.name}${filterValue ? ": " + filterValue : ""}
-`;
- return true;
- }
- });
- return filterHTML;
- };
- data.filters.forEach((item: IAVFilter) => {
- html += ``;
- });
- return `
-
-${html}
-
-`;
-};
-
const getPropertiesHTML = (data: IAV) => {
let showHTML = "";
let hideHTML = "";
data.columns.forEach((item: IAVColumn) => {
if (item.hidden) {
- hideHTML += `