From ea85184ec873d2bec84a53181c360696167f338d Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 06:28:32 +0000 Subject: [PATCH 01/11] feat: paste to insert image or files --- web/src/components/MemoEditor/handlers.ts | 31 +++++++++++++++++++++++ web/src/components/MemoEditor/index.tsx | 20 ++++++++++++--- 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/web/src/components/MemoEditor/handlers.ts b/web/src/components/MemoEditor/handlers.ts index 2e0c2bb20ab52..21f1e96d77e33 100644 --- a/web/src/components/MemoEditor/handlers.ts +++ b/web/src/components/MemoEditor/handlers.ts @@ -1,3 +1,5 @@ +import { Resource } from "@/types/proto/api/v1/resource_service"; +import { getResourceUrl } from "@/utils/resource"; import { EditorRefActions } from "./Editor"; export const handleEditorKeydownWithMarkdownShortcuts = (event: React.KeyboardEvent, editorRef: EditorRefActions) => { @@ -50,3 +52,32 @@ const styleHighlightedText = (editor: EditorRefActions, delimiter: string) => { editor.setCursorPosition(cursorPosition + delimiter.length, cursorPosition + delimiter.length + selectedContent.length); } }; + +export function insertResourceText(editor: EditorRefActions, resources: Resource[], placeholder: string) { + let text = editor.getContent(); + const pos = text.indexOf(placeholder); + if (pos === -1) return; + + const insertingParts: string[] = []; + for (const res of resources) { + const isImage = String(res.type).startsWith("image/"); + const title = res.filename; + const url = getResourceUrl(res); + + let part = `[${title}](${url})`; + if (isImage) part = `!${part}`; + + insertingParts.push(part); + } + const inserting = insertingParts.join(" "); + + // compute new cursorPos + let cursorPos = editor.getCursorPosition(); + if (cursorPos >= pos + placeholder.length) cursorPos += inserting.length - placeholder.length; + else if (cursorPos > pos) cursorPos = pos + inserting.length; + + text = text.slice(0, pos) + inserting + text.slice(pos + placeholder.length); + + editor.setContent(text); + editor.setCursorPosition(cursorPos); +} diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index b14c0d0e6566e..ee8dc3ff5460a 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -29,7 +29,7 @@ import UploadResourceButton from "./ActionButton/UploadResourceButton"; import Editor, { EditorRefActions } from "./Editor"; import RelationListView from "./RelationListView"; import ResourceListView from "./ResourceListView"; -import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers"; +import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText, insertResourceText } from "./handlers"; import { MemoEditorContext } from "./types"; import "react-datepicker/dist/react-datepicker.css"; @@ -233,7 +233,7 @@ const MemoEditor = observer((props: Props) => { } }; - const uploadMultiFiles = async (files: FileList) => { + const uploadMultiFiles = async (files: FileList): Promise => { const uploadedResourceList: Resource[] = []; for (const file of files) { const resource = await handleUploadResource(file); @@ -256,6 +256,7 @@ const MemoEditor = observer((props: Props) => { resourceList: [...prevState.resourceList, ...uploadedResourceList], })); } + return uploadedResourceList; }; const handleDropEvent = async (event: React.DragEvent) => { @@ -294,7 +295,20 @@ const MemoEditor = observer((props: Props) => { const handlePasteEvent = async (event: React.ClipboardEvent) => { if (event.clipboardData && event.clipboardData.files.length > 0) { event.preventDefault(); - await uploadMultiFiles(event.clipboardData.files); + + const editor = editorRef.current; + let placeholder = ""; + + if (editor) { + placeholder = ``; + editor.insertText(placeholder); + } + + const resources = await uploadMultiFiles(event.clipboardData.files); + + if (editor) { + insertResourceText(editor, resources, placeholder); + } } else if ( editorRef.current != null && editorRef.current.getSelectedContent().length != 0 && From dcd64b2ef0c51387705ea5e87e1ae4ae1cb94a0a Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 07:44:23 +0000 Subject: [PATCH 02/11] feat: confirm before deleting a referenced resource --- .../components/MemoEditor/ResourceListView.tsx | 12 +++++++++++- web/src/components/MemoEditor/index.tsx | 15 ++++++++++++++- web/src/locales/en.json | 1 + web/src/locales/zh-Hans.json | 1 + 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/web/src/components/MemoEditor/ResourceListView.tsx b/web/src/components/MemoEditor/ResourceListView.tsx index 4d35c14001ff1..3bede99f8d2d5 100644 --- a/web/src/components/MemoEditor/ResourceListView.tsx +++ b/web/src/components/MemoEditor/ResourceListView.tsx @@ -2,19 +2,29 @@ import { DndContext, closestCenter, MouseSensor, TouchSensor, useSensor, useSens import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { XIcon } from "lucide-react"; import { Resource } from "@/types/proto/api/v1/resource_service"; +import { useTranslate } from "@/utils/i18n"; import ResourceIcon from "../ResourceIcon"; import SortableItem from "./SortableItem"; interface Props { resourceList: Resource[]; setResourceList: (resourceList: Resource[]) => void; + checkIfSafeToDeleteResource?: (resource: Resource) => boolean; } const ResourceListView = (props: Props) => { - const { resourceList, setResourceList } = props; + const { resourceList, setResourceList, checkIfSafeToDeleteResource } = props; const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor)); + const t = useTranslate(); const handleDeleteResource = async (name: string) => { + if ( + typeof checkIfSafeToDeleteResource === "function" && + !checkIfSafeToDeleteResource(resourceList.find((resource) => resource.name === name)!) + ) { + const confirmationText = t("resource.delete-confirm-referenced"); + if (!window.confirm(confirmationText)) return; + } setResourceList(resourceList.filter((resource) => resource.name !== name)); }; diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index ee8dc3ff5460a..3111344b31de4 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -20,6 +20,7 @@ import { Resource } from "@/types/proto/api/v1/resource_service"; import { UserSetting } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; +import { getResourceUrl } from "@/utils/resource"; import VisibilityIcon from "../VisibilityIcon"; import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover"; import LocationSelector from "./ActionButton/LocationSelector"; @@ -187,6 +188,14 @@ const MemoEditor = observer((props: Props) => { })); }; + const checkIfSafeToDeleteResource = (resource: Resource): boolean => { + const content = editorRef.current?.getContent(); + const marker = `(${getResourceUrl(resource)})`; + if (content && content.includes(marker)) return false; // referenced! + + return true; + }; + const handleSetRelationList = (relationList: MemoRelation[]) => { setState((prevState) => ({ ...prevState, @@ -513,7 +522,11 @@ const MemoEditor = observer((props: Props) => { /> )} - +
e.stopPropagation()}>
diff --git a/web/src/locales/en.json b/web/src/locales/en.json index c457cecaf73da..827585f32318f 100644 --- a/web/src/locales/en.json +++ b/web/src/locales/en.json @@ -213,6 +213,7 @@ "title": "Create Resource", "upload-method": "Upload method" }, + "delete-confirm-referenced": "Resource is referenced in memo content, delete anyway?", "delete-resource": "Delete Resource", "delete-selected-resources": "Delete Selected Resources", "fetching-data": "Fetching data…", diff --git a/web/src/locales/zh-Hans.json b/web/src/locales/zh-Hans.json index 5725017959201..9984ba9422d75 100644 --- a/web/src/locales/zh-Hans.json +++ b/web/src/locales/zh-Hans.json @@ -205,6 +205,7 @@ "title": "创建资源", "upload-method": "上传方式" }, + "delete-confirm-referenced": "资源在正文已中被引用,确定要删除吗?", "delete-resource": "删除资源", "delete-selected-resources": "删除选中资源", "fetching-data": "正在获取数据…", From 14dc8a992b7c0ec6595a7e55f013ad9937673c6a Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 08:07:39 +0000 Subject: [PATCH 03/11] feat: make selection to placeholder --- web/src/components/MemoEditor/handlers.ts | 14 +++++++++++--- web/src/components/MemoEditor/index.tsx | 3 +++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/web/src/components/MemoEditor/handlers.ts b/web/src/components/MemoEditor/handlers.ts index 21f1e96d77e33..35c1ce4fe1886 100644 --- a/web/src/components/MemoEditor/handlers.ts +++ b/web/src/components/MemoEditor/handlers.ts @@ -54,6 +54,8 @@ const styleHighlightedText = (editor: EditorRefActions, delimiter: string) => { }; export function insertResourceText(editor: EditorRefActions, resources: Resource[], placeholder: string) { + if (!placeholder) return; + let text = editor.getContent(); const pos = text.indexOf(placeholder); if (pos === -1) return; @@ -73,11 +75,17 @@ export function insertResourceText(editor: EditorRefActions, resources: Resource // compute new cursorPos let cursorPos = editor.getCursorPosition(); - if (cursorPos >= pos + placeholder.length) cursorPos += inserting.length - placeholder.length; - else if (cursorPos > pos) cursorPos = pos + inserting.length; + let selectionLength = 0; + + if (cursorPos > pos + placeholder.length) { + cursorPos += inserting.length - placeholder.length; + } else if (cursorPos >= pos) { + cursorPos = pos; + selectionLength = inserting.length; + } text = text.slice(0, pos) + inserting + text.slice(pos + placeholder.length); editor.setContent(text); - editor.setCursorPosition(cursorPos); + editor.setCursorPosition(cursorPos, cursorPos + selectionLength); } diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index 3111344b31de4..fbdac5c6d7987 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -309,8 +309,11 @@ const MemoEditor = observer((props: Props) => { let placeholder = ""; if (editor) { + // create a placeholder for uploaded files, and select it. placeholder = ``; + const position = editor.getCursorPosition(); editor.insertText(placeholder); + editor.setCursorPosition(position, position + placeholder.length); } const resources = await uploadMultiFiles(event.clipboardData.files); From 090bac7b4d5d10967216c1d7b86b948aaf40949a Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 08:42:13 +0000 Subject: [PATCH 04/11] fix: embedded syntax cannot display resources --- .../components/MemoContent/EmbeddedContent/EmbeddedResource.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/MemoContent/EmbeddedContent/EmbeddedResource.tsx b/web/src/components/MemoContent/EmbeddedContent/EmbeddedResource.tsx index 7711158ce06c6..94052681390a3 100644 --- a/web/src/components/MemoContent/EmbeddedContent/EmbeddedResource.tsx +++ b/web/src/components/MemoContent/EmbeddedContent/EmbeddedResource.tsx @@ -38,7 +38,7 @@ const getAdditionalClassNameWithParams = (params: URLSearchParams) => { const EmbeddedResource = ({ resourceId: uid, params: paramsStr }: Props) => { const loadingState = useLoading(); const resourceStore = useResourceStore(); - const resource = resourceStore.getResourceByName(uid); + const resource = resourceStore.getResourceByName(`resources/${uid}`); const params = new URLSearchParams(paramsStr); useEffect(() => { From 2dd74d272606c3a0ff0d9e2a28324174b93b668b Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 08:51:18 +0000 Subject: [PATCH 05/11] feat: do not display thumbnail if media resource is already embedded --- .../EmbeddedContent/EmbeddedMemo.tsx | 2 +- web/src/components/MemoResourceListView.tsx | 26 ++++++++++++++++--- web/src/components/MemoView.tsx | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx index b0caf3ce1688d..6108b4822aff2 100644 --- a/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx +++ b/web/src/components/MemoContent/EmbeddedContent/EmbeddedMemo.tsx @@ -53,7 +53,7 @@ const EmbeddedMemo = ({ resourceId: uid, params: paramsStr }: Props) => { nodes={memo.nodes} embeddedMemos={context.embeddedMemos} /> - + ); if (inlineMode) { diff --git a/web/src/components/MemoResourceListView.tsx b/web/src/components/MemoResourceListView.tsx index 0d07f7c29373e..41414d415f77e 100644 --- a/web/src/components/MemoResourceListView.tsx +++ b/web/src/components/MemoResourceListView.tsx @@ -1,19 +1,39 @@ import { memo } from "react"; +import { NodeType } from "@/types/proto/api/v1/markdown_service"; +import { Memo } from "@/types/proto/api/v1/memo_service"; import { Resource } from "@/types/proto/api/v1/resource_service"; import { cn } from "@/utils"; import { getResourceType, getResourceUrl } from "@/utils/resource"; import MemoResource from "./MemoResource"; import showPreviewImageDialog from "./PreviewImageDialog"; -const MemoResourceListView = ({ resources = [] }: { resources: Resource[] }) => { +const MemoResourceListView = ({ + memo, + resources = [], + noThumbnailForEmbedded, +}: { + memo?: Memo; + resources: Resource[]; + noThumbnailForEmbedded?: boolean; +}) => { const mediaResources: Resource[] = []; const otherResources: Resource[] = []; resources.forEach((resource) => { const type = getResourceType(resource); if (type === "image/*" || type === "video/*") { - mediaResources.push(resource); - return; + let useThumbnail = true; + if (memo && noThumbnailForEmbedded) { + const urlMarker = `(${getResourceUrl(resource)})`; + if (memo.content.includes(urlMarker)) useThumbnail = false; + if (memo.nodes?.some((x) => x.type === NodeType.EMBEDDED_CONTENT && x.embeddedContentNode?.resourceName === resource.name)) + useThumbnail = false; + } + + if (useThumbnail) { + mediaResources.push(resource); + return; + } } otherResources.push(resource); diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 6118c7105b908..4857dfa171f52 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -231,7 +231,7 @@ const MemoView: React.FC = (props: Props) => { parentPage={parentPage} /> {memo.location && } - +
From fc4f857155e9a8e024cc4cfd01164841c0fe7a77 Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 08:55:28 +0000 Subject: [PATCH 06/11] fix: duplicated image preview dialog when click embedded image resource --- web/src/components/MemoResourceListView.tsx | 1 + web/src/components/MemoView.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/web/src/components/MemoResourceListView.tsx b/web/src/components/MemoResourceListView.tsx index 41414d415f77e..1dfbf4a19a3bb 100644 --- a/web/src/components/MemoResourceListView.tsx +++ b/web/src/components/MemoResourceListView.tsx @@ -57,6 +57,7 @@ const MemoResourceListView = ({ className={cn("cursor-pointer h-full w-auto rounded-lg border dark:border-zinc-800 object-contain hover:opacity-80", className)} src={resource.externalLink ? resourceUrl : resourceUrl + "?thumbnail=true"} onClick={() => handleImageClick(resourceUrl)} + data-is-resource-media decoding="async" loading="lazy" /> diff --git a/web/src/components/MemoView.tsx b/web/src/components/MemoView.tsx index 4857dfa171f52..e5e74844717e2 100644 --- a/web/src/components/MemoView.tsx +++ b/web/src/components/MemoView.tsx @@ -79,7 +79,7 @@ const MemoView: React.FC = (props: Props) => { const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; - if (targetEl.tagName === "IMG") { + if (targetEl.tagName === "IMG" && !targetEl.hasAttribute("data-is-resource-media")) { const imgUrl = targetEl.getAttribute("src"); if (imgUrl) { showPreviewImageDialog([imgUrl], 0); From 947a11e06f0bdcef7d895261cf8b409ea9c2f1d6 Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 08:59:02 +0000 Subject: [PATCH 07/11] fix: use link to display resource name --- web/src/components/MemoResource.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/web/src/components/MemoResource.tsx b/web/src/components/MemoResource.tsx index 365d03d4a750a..faccdd3295146 100644 --- a/web/src/components/MemoResource.tsx +++ b/web/src/components/MemoResource.tsx @@ -11,10 +11,6 @@ const MemoResource: React.FC = (props: Props) => { const { className, resource } = props; const resourceUrl = getResourceUrl(resource); - const handlePreviewBtnClick = () => { - window.open(resourceUrl); - }; - return ( From 9d86767b088c244e4425a9824de324d3416dd8bc Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 09:08:00 +0000 Subject: [PATCH 08/11] refactor: refine logic to check embedded --- web/src/components/MemoEditor/index.tsx | 7 ++----- web/src/components/MemoResourceListView.tsx | 10 ++-------- web/src/utils/resource.ts | 9 +++++++++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/web/src/components/MemoEditor/index.tsx b/web/src/components/MemoEditor/index.tsx index fbdac5c6d7987..3d727ad7215e9 100644 --- a/web/src/components/MemoEditor/index.tsx +++ b/web/src/components/MemoEditor/index.tsx @@ -20,7 +20,7 @@ import { Resource } from "@/types/proto/api/v1/resource_service"; import { UserSetting } from "@/types/proto/api/v1/user_service"; import { useTranslate } from "@/utils/i18n"; import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo"; -import { getResourceUrl } from "@/utils/resource"; +import { isResourceEmbeddedInContent } from "@/utils/resource"; import VisibilityIcon from "../VisibilityIcon"; import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover"; import LocationSelector from "./ActionButton/LocationSelector"; @@ -190,10 +190,7 @@ const MemoEditor = observer((props: Props) => { const checkIfSafeToDeleteResource = (resource: Resource): boolean => { const content = editorRef.current?.getContent(); - const marker = `(${getResourceUrl(resource)})`; - if (content && content.includes(marker)) return false; // referenced! - - return true; + return !isResourceEmbeddedInContent(content, resource); }; const handleSetRelationList = (relationList: MemoRelation[]) => { diff --git a/web/src/components/MemoResourceListView.tsx b/web/src/components/MemoResourceListView.tsx index 1dfbf4a19a3bb..577cbf9d1213c 100644 --- a/web/src/components/MemoResourceListView.tsx +++ b/web/src/components/MemoResourceListView.tsx @@ -1,9 +1,8 @@ import { memo } from "react"; -import { NodeType } from "@/types/proto/api/v1/markdown_service"; import { Memo } from "@/types/proto/api/v1/memo_service"; import { Resource } from "@/types/proto/api/v1/resource_service"; import { cn } from "@/utils"; -import { getResourceType, getResourceUrl } from "@/utils/resource"; +import { getResourceType, getResourceUrl, isResourceEmbeddedInContent } from "@/utils/resource"; import MemoResource from "./MemoResource"; import showPreviewImageDialog from "./PreviewImageDialog"; @@ -23,12 +22,7 @@ const MemoResourceListView = ({ const type = getResourceType(resource); if (type === "image/*" || type === "video/*") { let useThumbnail = true; - if (memo && noThumbnailForEmbedded) { - const urlMarker = `(${getResourceUrl(resource)})`; - if (memo.content.includes(urlMarker)) useThumbnail = false; - if (memo.nodes?.some((x) => x.type === NodeType.EMBEDDED_CONTENT && x.embeddedContentNode?.resourceName === resource.name)) - useThumbnail = false; - } + if (memo && noThumbnailForEmbedded) useThumbnail = !isResourceEmbeddedInContent(memo.content, resource); if (useThumbnail) { mediaResources.push(resource); diff --git a/web/src/utils/resource.ts b/web/src/utils/resource.ts index 7bdefa285a786..eb646a0130a85 100644 --- a/web/src/utils/resource.ts +++ b/web/src/utils/resource.ts @@ -43,3 +43,12 @@ export const isImage = (t: string) => { const isPSD = (t: string) => { return t === "image/vnd.adobe.photoshop" || t === "image/x-photoshop" || t === "image/photoshop"; }; + +export const isResourceEmbeddedInContent = (content: string | undefined, resource: Resource): boolean => { + if (!content) return false; + + if (content.includes(`(${getResourceUrl(resource)})`)) return true; + if (content.includes(`[[${resource.name}]]`)) return true; // marker like ![[resources/1234]] + + return false; +}; From 020d91e1330e7ca197ce02543f76bca2a7664bcb Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 10:23:26 +0000 Subject: [PATCH 09/11] feat: paste resource as reference --- .../ReferencedContent/ReferencedResource.tsx | 12 ++++++++++++ .../MemoContent/ReferencedContent/index.tsx | 4 ++++ web/src/components/MemoEditor/handlers.ts | 18 +++++++++++------- web/src/utils/resource.ts | 6 +++++- 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 web/src/components/MemoContent/ReferencedContent/ReferencedResource.tsx diff --git a/web/src/components/MemoContent/ReferencedContent/ReferencedResource.tsx b/web/src/components/MemoContent/ReferencedContent/ReferencedResource.tsx new file mode 100644 index 0000000000000..fc3dd70d79d9c --- /dev/null +++ b/web/src/components/MemoContent/ReferencedContent/ReferencedResource.tsx @@ -0,0 +1,12 @@ +import EmbeddedResource from "../EmbeddedContent/EmbeddedResource"; + +interface Props { + resourceId: string; + params: string; +} + +const ReferencedResource = ({ resourceId: uid, params }: Props) => { + return ; +}; + +export default ReferencedResource; diff --git a/web/src/components/MemoContent/ReferencedContent/index.tsx b/web/src/components/MemoContent/ReferencedContent/index.tsx index 374aec2d13caa..33b4b43519087 100644 --- a/web/src/components/MemoContent/ReferencedContent/index.tsx +++ b/web/src/components/MemoContent/ReferencedContent/index.tsx @@ -1,5 +1,6 @@ import Error from "./Error"; import ReferencedMemo from "./ReferencedMemo"; +import ReferencedResource from "./ReferencedResource"; interface Props { resourceName: string; @@ -16,6 +17,9 @@ const ReferencedContent = ({ resourceName, params }: Props) => { if (resourceType === "memos") { return ; } + if (resourceType === "resources") { + return ; + } return ; }; diff --git a/web/src/components/MemoEditor/handlers.ts b/web/src/components/MemoEditor/handlers.ts index 35c1ce4fe1886..b8e04f701b858 100644 --- a/web/src/components/MemoEditor/handlers.ts +++ b/web/src/components/MemoEditor/handlers.ts @@ -1,5 +1,4 @@ import { Resource } from "@/types/proto/api/v1/resource_service"; -import { getResourceUrl } from "@/utils/resource"; import { EditorRefActions } from "./Editor"; export const handleEditorKeydownWithMarkdownShortcuts = (event: React.KeyboardEvent, editorRef: EditorRefActions) => { @@ -62,14 +61,19 @@ export function insertResourceText(editor: EditorRefActions, resources: Resource const insertingParts: string[] = []; for (const res of resources) { - const isImage = String(res.type).startsWith("image/"); - const title = res.filename; - const url = getResourceUrl(res); + insertingParts.push(`[[${res.name}?name=${encodeURIComponent(res.filename)}]]`); - let part = `[${title}](${url})`; - if (isImage) part = `!${part}`; + // ----- + // or create a normal Markdown? - insertingParts.push(part); + // const isImage = String(res.type).startsWith("image/"); + // const title = res.filename; + // const url = getResourceUrl(res); + + // let part = `[${title}](${url})`; + // if (isImage) part = `!${part}`; + + // insertingParts.push(part); } const inserting = insertingParts.join(" "); diff --git a/web/src/utils/resource.ts b/web/src/utils/resource.ts index eb646a0130a85..1def3c6c08c93 100644 --- a/web/src/utils/resource.ts +++ b/web/src/utils/resource.ts @@ -48,7 +48,11 @@ export const isResourceEmbeddedInContent = (content: string | undefined, resourc if (!content) return false; if (content.includes(`(${getResourceUrl(resource)})`)) return true; - if (content.includes(`[[${resource.name}]]`)) return true; // marker like ![[resources/1234]] + + // match markers like ![[resources/1234]] + // regex: /\[\[resources\/123456(\?[^\]]*)?\]\]/ + const re = new RegExp(`\\[\\[${resource.name.replace(/\//g, "\\/")}(\\?[^\\]]*)?\\]\\]`); + if (re.test(content)) return true; return false; }; From 61b25835c751e1f9726da17cbff79dd5175465c6 Mon Sep 17 00:00:00 2001 From: lyonbot Date: Sun, 18 May 2025 10:41:01 +0000 Subject: [PATCH 10/11] feat: make link url copyable --- .../MemoEditor/ResourceListView.tsx | 31 ++++++++++++++++++- web/src/components/MemoEditor/index.tsx | 2 +- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/web/src/components/MemoEditor/ResourceListView.tsx b/web/src/components/MemoEditor/ResourceListView.tsx index 3bede99f8d2d5..25421924a2a01 100644 --- a/web/src/components/MemoEditor/ResourceListView.tsx +++ b/web/src/components/MemoEditor/ResourceListView.tsx @@ -3,6 +3,7 @@ import { arrayMove, SortableContext, verticalListSortingStrategy } from "@dnd-ki import { XIcon } from "lucide-react"; import { Resource } from "@/types/proto/api/v1/resource_service"; import { useTranslate } from "@/utils/i18n"; +import { getResourceUrl } from "@/utils/resource"; import ResourceIcon from "../ResourceIcon"; import SortableItem from "./SortableItem"; @@ -52,7 +53,14 @@ const ResourceListView = (props: Props) => { > - {resource.filename} + + {resource.filename} +