diff --git a/app/src/protyle/preview/image.ts b/app/src/protyle/preview/image.ts index e27ab4bbb..75cb76e2b 100644 --- a/app/src/protyle/preview/image.ts +++ b/app/src/protyle/preview/image.ts @@ -2,7 +2,47 @@ import {Constants} from "../../constants"; import {addScript} from "../util/addScript"; import {fetchPost} from "../../util/fetch"; -export const previewImage = (src: string, id: string) => { +export const previewImage = (src: string) => { + addScript(`${Constants.PROTYLE_CDN}/js/viewerjs/viewer.js?v=1.10.4`, "protyleViewerScript").then(() => { + const imagesElement = document.createElement("ul"); + imagesElement.innerHTML = `
  • `; + // @ts-ignore + window.siyuan.viewer = new Viewer(imagesElement, { + title: [1, (image: HTMLImageElement, imageData: IObject) => { + let name = image.alt; + if (!name) { + name = image.src.substring(image.src.lastIndexOf("/") + 1); + } + name = name.substring(0, name.lastIndexOf(".")).replace(/-\d{14}-\w{7}$/, ""); + return `${name} [${imageData.naturalWidth} × ${imageData.naturalHeight}]`; + }], + button: false, + transition: false, + hidden: function () { + window.siyuan.viewer.destroy(); + }, + toolbar: { + zoomIn: true, + zoomOut: true, + oneToOne: true, + reset: true, + prev: true, + play: true, + next: true, + rotateLeft: true, + rotateRight: true, + flipHorizontal: true, + flipVertical: true, + close: function () { + window.siyuan.viewer.destroy(); + }, + }, + }); + window.siyuan.viewer.show(); + }); +}; + +export const previewDocImage = (src: string, id: string) => { addScript(`${Constants.PROTYLE_CDN}/js/viewerjs/viewer.js?v=1.10.4`, "protyleViewerScript").then(() => { fetchPost("/api/asset/getDocImageAssets", {id}, (response) => { const imagesElement = document.createElement("ul"); diff --git a/app/src/protyle/preview/index.ts b/app/src/protyle/preview/index.ts index 9eb4bcfc9..7fda735f6 100644 --- a/app/src/protyle/preview/index.ts +++ b/app/src/protyle/preview/index.ts @@ -2,7 +2,7 @@ import {openByMobile, writeText} from "../util/compatibility"; import {focusByRange} from "../util/selection"; import {showMessage} from "../../dialog/message"; import {isLocalPath, pathPosix} from "../../util/pathName"; -import {previewImage} from "./image"; +import {previewDocImage} from "./image"; import {needSubscribe} from "../../util/needSubscribe"; import {Constants} from "../../constants"; import {getSearch, isMobile} from "../../util/functions"; @@ -117,7 +117,7 @@ export class Preview { } break; } else if (target.tagName === "IMG") { - previewImage((event.target as HTMLImageElement).src, protyle.block.rootID); + previewDocImage((event.target as HTMLImageElement).src, protyle.block.rootID); event.stopPropagation(); event.preventDefault(); break; diff --git a/app/src/protyle/render/av/asset.ts b/app/src/protyle/render/av/asset.ts index ee05aac25..6e6d1f934 100644 --- a/app/src/protyle/render/av/asset.ts +++ b/app/src/protyle/render/av/asset.ts @@ -5,7 +5,11 @@ import {isMobile} from "../../../util/functions"; import {Constants} from "../../../constants"; import {uploadFiles} from "../../upload"; import {pathPosix} from "../../../util/pathName"; - +import {openMenu} from "../../../menus/commonMenuItem"; +import {MenuItem} from "../../../menus/Menu"; +import {exportAsset} from "../../../menus/util"; +import {setPosition} from "../../../util/setPosition"; +import {previewImage} from "../../preview/image"; export const bindAssetEvent = (options: { protyle: IProtyle, @@ -62,15 +66,17 @@ export const getAssetHTML = (data: IAVTable, cellElements: HTMLElement[]) => { } let contentHTML if (item.type === "image") { - contentHTML = `` + contentHTML = ` + +` } else { contentHTML = `${item.name}` } - html += `` }) } @@ -92,11 +98,9 @@ const updateAssetCell = (options: { protyle: IProtyle, data: IAV, cellElements: HTMLElement[], - value: IAVCellAssetValue[] + value?: IAVCellAssetValue[], + removeContent?: string }) => { - 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) { @@ -123,9 +127,33 @@ const updateAssetCell = (options: { } }); const oldValue = Object.assign([], cellData.value.mAsset); - options.value.forEach(item => { - cellData.value.mAsset.push(item); - }) + if (options.removeContent) { + cellData.value.mAsset.find((oldItem, index) => { + if (oldItem.content === options.removeContent) { + cellData.value.mAsset.splice(index, 1) + return true; + } + }); + } else { + options.value.forEach(newitem => { + if (!newitem.content) { + return + } + const hasMatch = cellData.value.mAsset.find(oldItem => { + if (oldItem.content === newitem.content) { + oldItem.name = newitem.name; + oldItem.type = newitem.type; + return true; + } + }) + if (!hasMatch) { + if (newitem.type === "file" && !newitem.name) { + newitem.name = newitem.content + } + cellData.value.mAsset.push(newitem); + } + }) + } cellDoOperations.push({ action: "updateAttrViewCell", id: cellData.id, @@ -147,6 +175,81 @@ const updateAssetCell = (options: { updateAttrViewCellAnimation(item); }); transaction(options.protyle, cellDoOperations, cellUndoOperations); + const menuElement = document.querySelector(".av__panel > .b3-menu") as HTMLElement; + if (menuElement) { + menuElement.innerHTML = getAssetHTML(options.data.view, options.cellElements); + bindAssetEvent({protyle: options.protyle, data: options.data, menuElement, cellElements: options.cellElements}) + const cellRect = options.protyle.wysiwyg.element.querySelector(`.av__cell[data-id="${options.cellElements[0].dataset.id}"]`).getBoundingClientRect(); + setTimeout(() => { + setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); + }, Constants.TIMEOUT_LOAD); // 等待图片加载 + } +} + +export const editAssetItem = (protyle: IProtyle, data: IAV, cellElements: HTMLElement[], target: HTMLElement) => { + const linkAddress = target.dataset.content + const type = target.dataset.type as "image" | "file" + const menu = new Menu("av-asset-edit", () => { + if (!textElement || !textElement.value || textElement.value === target.dataset.name) { + return + } + updateAssetCell({ + protyle, + data, + cellElements, + value: [{ + content: linkAddress, + name: textElement.value, + type + }] + }) + }) + if (menu.isOpen) { + return; + } + if (type === "file") { + menu.addItem({ + iconHTML: "", + label: ``, + }); + } else { + menu.addItem({ + icon: "iconPreview", + label: window.siyuan.languages.cardPreview, + click() { + previewImage(linkAddress); + } + }); + } + menu.addItem({ + icon: "iconTrashcan", + label: window.siyuan.languages.delete, + click() { + updateAssetCell({ + protyle, + data, + cellElements, + removeContent: linkAddress + }) + } + }); + openMenu(protyle.app, linkAddress, false, true); + /// #if !BROWSER + if (linkAddress?.startsWith("assets/")) { + window.siyuan.menus.menu.append(new MenuItem(exportAsset(linkAddress)).element); + } + /// #endif + const textElement = menu.element.querySelector("textarea") + if (textElement) { + textElement.value = target.dataset.name; + } + const rect = target.getBoundingClientRect(); + menu.open({ + x: rect.right, + y: rect.top, + w: rect.width, + h: rect.height, + }); } export const addAssetLink = (protyle: IProtyle, data: IAV, cellElements: HTMLElement[], target: HTMLElement) => { @@ -166,7 +269,9 @@ export const addAssetLink = (protyle: IProtyle, data: IAV, cellElements: HTMLEle }] }) }) - + if (menu.isOpen) { + return; + } menu.addItem({ iconHTML: "", label: ``, diff --git a/app/src/protyle/render/av/cell.ts b/app/src/protyle/render/av/cell.ts index 09f74ba94..87ccd69e7 100644 --- a/app/src/protyle/render/av/cell.ts +++ b/app/src/protyle/render/av/cell.ts @@ -64,10 +64,7 @@ export const getCalcValue = (column: IAVColumn) => { return value; }; -export const genCellValue = (colType: TAVCol, value: string | { - content: string, - color: string -}[] | IAVCellDateValue) => { +export const genCellValue = (colType: TAVCol, value: string | any) => { let cellValue: IAVCellValue; if (typeof value === "string") { if (colType === "number") { @@ -118,16 +115,18 @@ export const genCellValue = (colType: TAVCol, value: string | { if (colType === "mSelect" || colType === "select") { cellValue = { type: colType, - mSelect: value as { - content: string, - color: string - }[] + mSelect: value as IAVCellSelectValue[] }; } else if (colType === "date") { cellValue = { type: colType, date: value as IAVCellDateValue }; + } else if (colType === "mAsset") { + cellValue = { + type: colType, + mAsset: value as IAVCellAssetValue[] + }; } } return cellValue; diff --git a/app/src/protyle/render/av/openMenuPanel.ts b/app/src/protyle/render/av/openMenuPanel.ts index 276496db0..c8f129174 100644 --- a/app/src/protyle/render/av/openMenuPanel.ts +++ b/app/src/protyle/render/av/openMenuPanel.ts @@ -10,7 +10,8 @@ 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"; +import {addAssetLink, bindAssetEvent, editAssetItem, getAssetHTML} from "./asset"; +import {Constants} from "../../../constants"; export const openMenuPanel = (options: { protyle: IProtyle, @@ -60,18 +61,21 @@ export const openMenuPanel = (options: { const tabRect = options.blockElement.querySelector(".layout-tab-bar").getBoundingClientRect(); if (["select", "date", "asset"].includes(options.type)) { const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect(); - setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); 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}); + setTimeout(() => { + setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); + }, Constants.TIMEOUT_LOAD); // 等待图片加载 } if (["select", "date"].includes(options.type)) { const inputElement = menuElement.querySelector("input"); inputElement.select(); inputElement.focus(); + setPosition(menuElement, cellRect.left, cellRect.bottom, cellRect.height); } } else { setPosition(menuElement, tabRect.right - menuElement.clientWidth, tabRect.bottom, tabRect.height); @@ -639,6 +643,11 @@ export const openMenuPanel = (options: { event.preventDefault(); event.stopPropagation(); break; + } else if (type === "editAssetItem") { + editAssetItem(options.protyle, data, options.cellElements, target.parentElement) + event.preventDefault(); + event.stopPropagation(); + break; } else if (type === "clearDate") { setDateValue({ cellElements: options.cellElements, diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index a12366dd4..474b9d8a6 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -19,7 +19,7 @@ import {Constants} from "../../constants"; import {getSearch, isMobile} from "../../util/functions"; import {isLocalPath, pathPosix} from "../../util/pathName"; import {genEmptyElement} from "../../block/util"; -import {previewImage} from "../preview/image"; +import {previewDocImage} from "../preview/image"; import { contentMenu, enterBack, @@ -1592,7 +1592,7 @@ export class WYSIWYG { this.element.addEventListener("dblclick", (event: MouseEvent & { target: HTMLElement }) => { if (event.target.tagName === "IMG" && !event.target.classList.contains("emoji")) { - previewImage((event.target as HTMLImageElement).src, protyle.block.rootID); + previewDocImage((event.target as HTMLImageElement).src, protyle.block.rootID); return; } }); diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts index 6583771e3..dffa9ebcf 100644 --- a/app/src/types/index.d.ts +++ b/app/src/types/index.d.ts @@ -998,12 +998,6 @@ interface IAVCell { valueType: TAVCol, } -interface IAVCellAssetValue { - content: string, - name: string, - type: "file" | "image" -} - interface IAVCellValue { type?: TAVCol, text?: { @@ -1015,10 +1009,7 @@ interface IAVCellValue { format?: string, formattedContent?: string }, - mSelect?: { - content: string, - color: string - }[] + mSelect?: IAVCellSelectValue[] mAsset?: IAVCellAssetValue[] block?: { content: string, @@ -1043,3 +1034,14 @@ interface IAVCellDateValue { isNotEmpty2?: boolean hasEndDate?: boolean } + +interface IAVCellSelectValue { + content: string, + color: string +} + +interface IAVCellAssetValue { + content: string, + name: string, + type: "file" | "image" +}