Vanessa 2025-11-03 13:47:41 +08:00
parent b1a00c6ebd
commit a0b238075d
8 changed files with 174 additions and 7 deletions

View file

@ -56,7 +56,7 @@
<svg>
<use xlink:href="#iconBoard"></use>
</svg>
<del>iconBoard</del>
iconBoard
</div>
<div>
<svg>

View file

@ -1,4 +1,5 @@
{
"useBackground": "填充列背景颜色",
"print": "打印",
"clickArrow": "点击箭头",
"foldAll": "全部折叠",

View file

@ -972,6 +972,25 @@
transform: rotate(90deg);
}
}
&__kanban {
overflow: auto;
gap: 16px;
padding: 0 1px 16px;
display: flex;
&-group {
width: 260px;
&--small {
width: 180px;
}
&--big {
width: 320px;
}
}
}
}
img.av__cellassetimg {

View file

@ -368,3 +368,105 @@ export const renderGallery = async (options: {
options.blockElement.querySelector(".av__gallery").classList.add("av__gallery--top");
}
};
export const renderKanban = async (options: {
blockElement: HTMLElement,
protyle: IProtyle,
cb?: (data: IAV) => void,
renderAll: boolean,
data?: IAV,
}) => {
const searchInputElement = options.blockElement.querySelector('[data-type="av-search"]') as HTMLInputElement;
const editIds: IIds[] = [];
options.blockElement.querySelectorAll(".av__gallery-fields--edit").forEach(item => {
editIds.push({
groupId: (hasClosestByClassName(item, "av__body") as HTMLElement).dataset.groupId || "",
fieldId: item.parentElement.getAttribute("data-id"),
});
});
const selectItemIds: IIds[] = [];
options.blockElement.querySelectorAll(".av__gallery-item--select").forEach(galleryItem => {
const fieldId = galleryItem.getAttribute("data-id");
if (fieldId) {
selectItemIds.push({
groupId: (hasClosestByClassName(galleryItem, "av__body") as HTMLElement).dataset.groupId || "",
fieldId
});
}
});
const pageSizes: { [key: string]: string } = {};
options.blockElement.querySelectorAll(".av__body").forEach((item: HTMLElement) => {
pageSizes[item.dataset.groupId || "unGroup"] = item.dataset.pageSize;
});
const resetData = {
isSearching: searchInputElement && document.activeElement === searchInputElement,
query: searchInputElement?.value || "",
alignSelf: options.blockElement.style.alignSelf,
oldOffset: options.protyle.contentElement.scrollTop,
editIds,
selectItemIds,
pageSizes,
};
if (options.blockElement.firstElementChild.innerHTML === "") {
options.blockElement.style.alignSelf = "";
options.blockElement.firstElementChild.outerHTML = `<div class="av__kanban fn__flex">
<span style="width: 260px;height: 178px;" class="av__pulse"></span>
<span style="width: 260px;height: 178px;" class="av__pulse"></span>
<span style="width: 260px;height: 178px;" class="av__pulse"></span>
</div>`;
}
const created = options.protyle.options.history?.created;
const snapshot = options.protyle.options.history?.snapshot;
let data: IAV = options.data;
if (!data) {
const avPageSize = getPageSize(options.blockElement);
const response = await fetchSyncPost(created ? "/api/av/renderHistoryAttributeView" : (snapshot ? "/api/av/renderSnapshotAttributeView" : "/api/av/renderAttributeView"), {
id: options.blockElement.getAttribute("data-av-id"),
created,
snapshot,
pageSize: avPageSize.unGroupPageSize,
groupPaging: avPageSize.groupPageSize,
viewID: options.blockElement.getAttribute(Constants.CUSTOM_SY_AV_VIEW) || "",
query: resetData.query.trim()
});
data = response.data;
}
if (data.viewType === "table") {
options.blockElement.setAttribute("data-av-type", "table");
avRender(options.blockElement, options.protyle, options.cb, options.renderAll);
return;
}
const view: IAVGallery = data.view as IAVGallery;
let bodyHTML = ""
view.groups.forEach((group: IAVGallery) => {
if (group.groupHidden === 0) {
bodyHTML += `<div class="av__kanban-group">
${getGroupTitleHTML(group, group.cards.length)}
<div data-group-id="${group.id}" data-page-size="${group.pageSize}" data-dtype="${group.groupKey.type}" data-content="${Lute.EscapeHTMLStr(group.groupValue.text?.content)}" class="av__body${group.groupFolded ? " fn__none" : ""}">${getGalleryHTML(group)}</div>
</div>`;
}
});
if (options.renderAll) {
options.blockElement.firstElementChild.outerHTML = `<div class="av__container fn__block">
${genTabHeaderHTML(data, resetData.isSearching || !!resetData.query, !options.protyle.disabled && !hasClosestByAttribute(options.blockElement, "data-type", "NodeBlockQueryEmbed"))}
<div class="av__kanban">
${bodyHTML}
</div>
<div class="av__cursor" contenteditable="true">${Constants.ZWSP}</div>
</div>`;
} else {
options.blockElement.querySelector(".av__kanban").innerHTML = bodyHTML;
}
afterRenderGallery({
resetData,
renderAll: options.renderAll,
data,
cb: options.cb,
protyle: options.protyle,
blockElement: options.blockElement,
});
if (view.hideAttrViewName) {
options.blockElement.querySelector(".av__gallery").classList.add("av__gallery--top");
}
};

View file

@ -7,7 +7,7 @@ import {getFieldsByData} from "./view";
export const getLayoutHTML = (data: IAV) => {
let html = "";
const view = data.view as IAVGallery;
if (data.viewType === "gallery") {
if (data.viewType === "gallery" || data.viewType === "kanban") {
let coverFromTitle = "";
if (view.coverFrom === 0) {
coverFromTitle = window.siyuan.languages.calcOperatorNone;
@ -52,7 +52,7 @@ export const getLayoutHTML = (data: IAV) => {
<input data-type="toggle-gallery-name" type="checkbox" class="b3-switch b3-switch--menu" ${view.displayFieldName ? "checked" : ""}>
</label>`;
}
return `<div class="b3-menu__items">
html = `<div class="b3-menu__items">
<div class="b3-menu__items">
<button class="b3-menu__item" data-type="nobg">
<span class="block__icon" style="padding: 8px;margin-left: -4px;" data-type="go-config">
@ -68,6 +68,11 @@ export const getLayoutHTML = (data: IAV) => {
<div class="fn__hr"></div>
<div>${window.siyuan.languages.table}</div>
</div>
<div data-type="set-layout" data-view-type="kanban" class="av__layout-item${data.viewType === "kanban" ? " av__layout-item--select" : ""}">
<svg><use xlink:href="#iconBoard"></use></svg>
<div class="fn__hr"></div>
<div>${window.siyuan.languages.kanban}</div>
</div>
<div data-type="set-layout" data-view-type="gallery" class="av__layout-item${data.viewType === "gallery" ? " av__layout-item--select" : ""}">
<svg><use xlink:href="#iconGallery"></use></svg>
<div class="fn__hr"></div>
@ -90,8 +95,21 @@ export const getLayoutHTML = (data: IAV) => {
<span class="fn__flex-center">${window.siyuan.languages.wrapAllFields}</span>
<span class="fn__space fn__flex-1"></span>
<input data-type="toggle-entries-wrap" type="checkbox" class="b3-switch b3-switch--menu" ${view.wrapField ? "checked" : ""}>
</label>
<button class="b3-menu__item" data-type="set-page-size" data-size="${view.pageSize}">
</label>`;
if (data.viewType === "kanban") {
html += `<button class="b3-menu__item" data-type="set-kanban-group">
<span class="fn__flex-center">${window.siyuan.languages.groups}</span>
<span class="fn__flex-1"></span>
<span class="b3-menu__accelerator">${view.cardSize === 0 ? window.siyuan.languages.small : (view.cardSize === 1 ? window.siyuan.languages.medium : window.siyuan.languages.large)}</span>
<svg class="b3-menu__icon b3-menu__icon--small"><use xlink:href="#iconRight"></use></svg>
</button>
<label class="b3-menu__item">
<span class="fn__flex-center">${window.siyuan.languages.useBackground}</span>
<span class="fn__space fn__flex-1"></span>
<input data-type="toggle-kanban-bg" type="checkbox" class="b3-switch b3-switch--menu" ${view.displayFieldName ? "checked" : ""}>
</label>`;
}
return html + `<button class="b3-menu__item" data-type="set-page-size" data-size="${view.pageSize}">
<span class="fn__flex-center">${window.siyuan.languages.entryNum}</span>
<span class="fn__flex-1"></span>
<span class="b3-menu__accelerator">${view.pageSize === Constants.SIZE_DATABASE_MAZ_SIZE ? window.siyuan.languages.all : view.pageSize}</span>

View file

@ -13,7 +13,7 @@ import {escapeAriaLabel, escapeAttr, escapeHtml} from "../../../util/escape";
import {electronUndo} from "../../undo";
import {isInAndroid, isInHarmony, isInIOS} from "../../util/compatibility";
import {isMobile} from "../../../util/functions";
import {renderGallery} from "./gallery/render";
import {renderKanban, renderGallery} from "./gallery/render";
import {getFieldsByData, getViewIcon} from "./view";
import {openMenuPanel} from "./openMenuPanel";
import {getPageSize} from "./groups";
@ -467,6 +467,10 @@ export const avRender = (element: Element, protyle: IProtyle, cb?: (data: IAV) =
renderGallery({blockElement: e, protyle, cb, renderAll});
return;
}
if (e.getAttribute("data-av-type") === "kanban") {
renderKanban({blockElement: e, protyle, cb, renderAll});
return;
}
let selectCellId;
const selectCellElement = e.querySelector(".av__cell--select") as HTMLElement;

View file

@ -344,6 +344,25 @@ export const addView = (protyle: IProtyle, blockElement: Element) => {
}]);
}
});
addMenu.addItem({
icon: "iconBoard",
label: window.siyuan.languages.kanban,
click() {
transaction(protyle, [{
action: "addAttrViewView",
avID,
layout: "kanban",
id,
blockID: blockElement.getAttribute("data-node-id")
}], [{
action: "removeAttrViewView",
layout: "kanban",
avID,
id,
blockID: blockElement.getAttribute("data-node-id")
}]);
}
});
addMenu.addItem({
icon: "iconGallery",
label: window.siyuan.languages.gallery,
@ -377,6 +396,8 @@ export const getViewIcon = (type: string) => {
return "iconTable";
case "gallery":
return "iconGallery";
case "kanban":
return "iconBoard";
}
};
@ -386,6 +407,8 @@ export const getViewName = (type: string) => {
return window.siyuan.languages.table;
case "gallery":
return window.siyuan.languages.gallery;
case "kanban":
return window.siyuan.languages.kanban;
}
};

View file

@ -88,7 +88,7 @@ type TEventBus = "ws-main" | "sync-start" | "sync-end" | "sync-fail" |
"lock-screen" |
"mobile-keyboard-show" | "mobile-keyboard-hide" |
"code-language-update" | "code-language-change"
type TAVView = "table" | "gallery"
type TAVView = "table" | "gallery" | "kanban"
type TAVCol =
"text"
| "date"