diff --git a/app/src/assets/scss/business/_av.scss b/app/src/assets/scss/business/_av.scss index e042695e1..de33d0a37 100644 --- a/app/src/assets/scss/business/_av.scss +++ b/app/src/assets/scss/business/_av.scss @@ -191,6 +191,13 @@ opacity: 1; background-color: var(--b3-theme-background-light) !important; } + + .av__cellassetimg { + max-height: 24px; + border-radius: var(--b3-border-radius); + margin: 1px 2px; + max-width: none; + } } &__cellheader { diff --git a/app/src/protyle/render/av/asset.ts b/app/src/protyle/render/av/asset.ts new file mode 100644 index 000000000..ee05aac25 --- /dev/null +++ b/app/src/protyle/render/av/asset.ts @@ -0,0 +1,185 @@ +import {Menu} from "../../../plugin/Menu"; +import {transaction} from "../../wysiwyg/transaction"; +import {updateAttrViewCellAnimation} from "./action"; +import {isMobile} from "../../../util/functions"; +import {Constants} from "../../../constants"; +import {uploadFiles} from "../../upload"; +import {pathPosix} from "../../../util/pathName"; + + +export const bindAssetEvent = (options: { + protyle: IProtyle, + data: IAV, + menuElement: HTMLElement, + cellElements: HTMLElement[] +}) => { + options.menuElement.querySelector("input").addEventListener("change", (event: InputEvent & { + target: HTMLInputElement + }) => { + if (event.target.files.length === 0) { + return; + } + uploadFiles(options.protyle, event.target.files, event.target, (res) => { + const resData = JSON.parse(res) + const value: IAVCellAssetValue[] = [] + Object.keys(resData.data.succMap).forEach((key) => { + value.push({ + name: key, + content: resData.data.succMap[key], + type: Constants.SIYUAN_ASSETS_IMAGE.includes(pathPosix().extname(resData.data.succMap[key]).toLowerCase()) ? "image" : "file" + }) + }) + updateAssetCell({ + protyle: options.protyle, + data: options.data, + cellElements: options.cellElements, + value + }) + }); + }); +}; + +export const getAssetHTML = (data: IAVTable, cellElements: HTMLElement[]) => { + const cellId = cellElements[0].dataset.id; + const rowId = cellElements[0].parentElement.dataset.id; + let cellData: IAVCell + data.rows.find(row => { + if (row.id === rowId) { + row.cells.find(cell => { + if (cell.id === cellId) { + cellData = cell + return true + } + }) + return true + } + }) + let html = "" + if (cellData?.value?.mAsset) { + cellData.value.mAsset.forEach(item => { + if (!item.content) { + return + } + let contentHTML + if (item.type === "image") { + contentHTML = `` + } else { + contentHTML = `${item.name}` + } + + html += `` + }) + } + return `
+ ${html} + + +
`; +}; + +const updateAssetCell = (options: { + protyle: IProtyle, + data: IAV, + cellElements: HTMLElement[], + value: IAVCellAssetValue[] +}) => { + if (!options.value || options.value.length === 0 || !options.value[0].content) { + return; + } + let cellIndex = 0; + Array.from(options.cellElements[0].parentElement.querySelectorAll(".av__cell")).find((item: HTMLElement, index) => { + if (item.dataset.id === options.cellElements[0].dataset.id) { + cellIndex = index; + return true; + } + }); + + const colId = options.cellElements[0].dataset.colId; + const cellDoOperations: IOperation[] = []; + const cellUndoOperations: IOperation[] = []; + options.cellElements.forEach(item => { + let cellData: IAVCell; + const rowID = item.parentElement.dataset.id; + options.data.view.rows.find(row => { + if (row.id === rowID) { + cellData = row.cells[cellIndex]; + // 为空时 cellId 每次请求都不一致 + cellData.id = item.dataset.id; + if (!cellData.value || !cellData.value.mAsset) { + cellData.value = {mAsset: []} as IAVCellValue; + } + return true; + } + }); + const oldValue = Object.assign([], cellData.value.mAsset); + options.value.forEach(item => { + cellData.value.mAsset.push(item); + }) + cellDoOperations.push({ + action: "updateAttrViewCell", + id: cellData.id, + keyID: colId, + rowID, + avID: options.data.id, + data: cellData.value + }); + cellUndoOperations.push({ + action: "updateAttrViewCell", + id: cellData.id, + keyID: colId, + rowID, + avID: options.data.id, + data: { + mAsset: oldValue + } + }); + updateAttrViewCellAnimation(item); + }); + transaction(options.protyle, cellDoOperations, cellUndoOperations); +} + +export const addAssetLink = (protyle: IProtyle, data: IAV, cellElements: HTMLElement[], target: HTMLElement) => { + const menu = new Menu("av-asset-link", () => { + const textElements = menu.element.querySelectorAll("textarea") + if (!textElements[0].value) { + return + } + updateAssetCell({ + protyle, + data, + cellElements, + value: [{ + type: "file", + name: textElements[1].value, + content: textElements[0].value, + }] + }) + }) + + menu.addItem({ + iconHTML: "", + label: ``, + }); + menu.addItem({ + iconHTML: "", + label: ``, + }); + const rect = target.getBoundingClientRect(); + menu.open({ + x: rect.right, + y: rect.bottom, + w: rect.width, + h: rect.height, + }); +} diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts index 1ffacafc3..09f74ba94 100644 --- a/app/src/protyle/render/av/cell.ts +++ b/app/src/protyle/render/av/cell.ts @@ -356,6 +356,9 @@ export const popTextCell = (protyle: IProtyle, cellElements: HTMLElement[]) => { } 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}); return; diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index c4b16493f..276496db0 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -10,11 +10,12 @@ import {addSort, bindSortsEvent, getSortsHTML} from "./sort"; import {bindDateEvent, getDateHTML, setDateValue} from "./date"; import {formatNumber} from "./number"; import {removeAttrViewColAnimation} from "./action"; +import {addAssetLink, bindAssetEvent, getAssetHTML} from "./asset"; export const openMenuPanel = (options: { protyle: IProtyle, blockElement: Element, - type: "select" | "properties" | "config" | "sorts" | "filters" | "edit" | "date", + type: "select" | "properties" | "config" | "sorts" | "filters" | "edit" | "date" | "asset", colId?: string, // for edit cellElements?: HTMLElement[] // for select & date }) => { @@ -42,6 +43,8 @@ export const openMenuPanel = (options: { html = getFiltersHTML(data.view); } else if (options.type === "select") { html = getSelectHTML(data.view, options.cellElements); + } else if (options.type === "asset") { + html = getAssetHTML(data.view, options.cellElements); } else if (options.type === "edit") { html = getEditHTML({protyle: options.protyle, data, colId: options.colId}); } else if (options.type === "date") { @@ -55,20 +58,21 @@ export const openMenuPanel = (options: { avPanelElement = document.querySelector(".av__panel"); const menuElement = avPanelElement.lastElementChild as HTMLElement; const tabRect = options.blockElement.querySelector(".layout-tab-bar").getBoundingClientRect(); - if (options.type === "select") { + if (["select", "date", "asset"].includes(options.type)) { const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); - bindSelectEvent(options.protyle, data, menuElement, options.cellElements); - const inputElement = menuElement.querySelector("input"); - inputElement.select(); - inputElement.focus(); - } else if (options.type === "date") { - const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); - setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); - bindDateEvent({protyle: options.protyle, data, menuElement, cellElements: options.cellElements}); - const inputElement = menuElement.querySelector("input"); - inputElement.select(); - inputElement.focus(); + if (options.type === "select") { + bindSelectEvent(options.protyle, data, menuElement, options.cellElements); + } else if (options.type === "date") { + bindDateEvent({protyle: options.protyle, data, menuElement, cellElements: options.cellElements}); + } else if (options.type === "asset") { + bindAssetEvent({protyle: options.protyle, data, menuElement, cellElements: options.cellElements}); + } + if (["select", "date"].includes(options.type)) { + const inputElement = menuElement.querySelector("input"); + inputElement.select(); + inputElement.focus(); + } } else { setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height); if (options.type === "sorts") { @@ -630,6 +634,11 @@ export const openMenuPanel = (options: { event.preventDefault(); event.stopPropagation(); break; + } else if (type === "addAssetLink") { + addAssetLink(options.protyle, data, options.cellElements, target) + event.preventDefault(); + event.stopPropagation(); + break; } else if (type === "clearDate") { setDateValue({ cellElements: options.cellElements, diff --git a/app/src/protyle/render/av/render.ts b/app/src/protyle/render/av/render.ts index 1a0839a30..057d02af2 100644 --- a/app/src/protyle/render/av/render.ts +++ b/app/src/protyle/render/av/render.ts @@ -101,7 +101,7 @@ style="width: ${column.width || "200px"}">${getCalcValue(column) || ' { if (item.type === "image") { - text += ``; + text += ``; } else { text += `${item.name}`; } diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index ac5043ede..6583771e3 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -84,7 +84,9 @@ interface Window { dataLayer: any[] siyuan: ISiyuan webkit: any - html2canvas: (element: Element, opitons: { useCORS: boolean }) => Promise; + html2canvas: (element: Element, opitons: { + useCORS: boolean + }) => Promise; JSAndroid: { returnDesktop(): void openExternal(url: string): void @@ -237,7 +239,10 @@ interface IBackStack { }, scrollTop?: number, callback?: string[], - position?: { start: number, end: number } + position?: { + start: number, + end: number + } // 仅桌面端 protyle?: IProtyle, zoomId?: string @@ -269,14 +274,18 @@ interface INotebook { interface ISiyuan { zIndex: number - storage?: { [key: string]: any }, + storage?: { + [key: string]: any + }, printWin?: import("electron").BrowserWindow transactions?: { protyle: IProtyle, doOperations: IOperation[], undoOperations: IOperation[] }[] - reqIds: { [key: string]: number }, + reqIds: { + [key: string]: number + }, editorIsFullscreen?: boolean, hideBreadcrumb?: boolean, notebooks?: INotebook[], @@ -300,7 +309,11 @@ interface ISiyuan { userSiYuanSubscriptionType: number // 0 年付;1 终生;2 月付 userSiYuanSubscriptionStatus: number // -1:未订阅,0:订阅可用,1:订阅封禁,2:订阅过期 userToken: string - userTitles: { name: string, icon: string, desc: string }[] + userTitles: { + name: string, + icon: string, + desc: string + }[] }, dragElement?: HTMLElement, layout?: { @@ -395,7 +408,10 @@ interface ILayoutJSON extends ILayoutOptions { interface IDockTab { type: string; - size: { width: number, height: number } + size: { + width: number, + height: number + } show: boolean icon: string title: string @@ -425,7 +441,10 @@ interface IPluginData { interface IPluginDockTab { position: TPluginDockPosition, - size: { width: number, height: number }, + size: { + width: number, + height: number + }, icon: string, hotkey?: string, title: string, @@ -666,7 +685,10 @@ interface IConfig { localIPs: string[] readonly: boolean // 全局只读 uiLayout: Record - langs: { label: string, name: string }[] + langs: { + label: string, + name: string + }[] appearance: IAppearance editor: IEditor, fileTree: IFileTree @@ -761,13 +783,25 @@ interface IKeymap { [key: string]: IKeymapItem } } - general: { [key: string]: IKeymapItem } + general: { + [key: string]: IKeymapItem + } editor: { - general: { [key: string]: IKeymapItem } - insert: { [key: string]: IKeymapItem } - heading: { [key: string]: IKeymapItem } - list: { [key: string]: IKeymapItem } - table: { [key: string]: IKeymapItem } + general: { + [key: string]: IKeymapItem + } + insert: { + [key: string]: IKeymapItem + } + heading: { + [key: string]: IKeymapItem + } + list: { + [key: string]: IKeymapItem + } + table: { + [key: string]: IKeymapItem + } } } @@ -964,16 +998,41 @@ interface IAVCell { valueType: TAVCol, } +interface IAVCellAssetValue { + content: string, + name: string, + type: "file" | "image" +} + interface IAVCellValue { type?: TAVCol, - text?: { content: string }, - number?: { content?: number, isNotEmpty: boolean, format?: string, formattedContent?: string }, - mSelect?: { content: string, color: string }[] - mAsset?: { content: string, name: string, type: "file" | "image" }[] - block?: { content: string, id?: string } - url?: { content: string } - phone?: { content: string } - email?: { content: string } + text?: { + content: string + }, + number?: { + content?: number, + isNotEmpty: boolean, + format?: string, + formattedContent?: string + }, + mSelect?: { + content: string, + color: string + }[] + mAsset?: IAVCellAssetValue[] + block?: { + content: string, + id?: string + } + url?: { + content: string + } + phone?: { + content: string + } + email?: { + content: string + } date?: IAVCellDateValue }