mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-01 01:30:51 +08:00
feat: support export image for notes (#10559)
* feat: support export image for notes * feat: extract functions
This commit is contained in:
parent
6a8544fb0e
commit
42849e4586
@ -359,6 +359,23 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
[bases.length, t]
|
||||
)
|
||||
|
||||
const handleImageAction = useCallback(
|
||||
async (node: NotesTreeNode, platform: 'copyImage' | 'exportImage') => {
|
||||
try {
|
||||
if (activeNode?.id !== node.id) {
|
||||
onSelectNode(node)
|
||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||
}
|
||||
|
||||
await exportNote({ node, platform })
|
||||
} catch (error) {
|
||||
logger.error(`Failed to ${platform === 'copyImage' ? 'copy' : 'export'} as image:`, error as Error)
|
||||
window.toast.error(t('common.copy_failed'))
|
||||
}
|
||||
},
|
||||
[activeNode, onSelectNode, t]
|
||||
)
|
||||
|
||||
const handleAutoRename = useCallback(
|
||||
async (note: NotesTreeNode) => {
|
||||
if (note.type !== 'file') return
|
||||
@ -612,6 +629,16 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
key: 'export',
|
||||
icon: <UploadIcon size={14} />,
|
||||
children: [
|
||||
exportMenuOptions.image && {
|
||||
label: t('chat.topics.copy.image'),
|
||||
key: 'copy-image',
|
||||
onClick: () => handleImageAction(node, 'copyImage')
|
||||
},
|
||||
exportMenuOptions.image && {
|
||||
label: t('chat.topics.export.image'),
|
||||
key: 'export-image',
|
||||
onClick: () => handleImageAction(node, 'exportImage')
|
||||
},
|
||||
exportMenuOptions.markdown && {
|
||||
label: t('chat.topics.export.md.label'),
|
||||
key: 'markdown',
|
||||
@ -671,6 +698,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
handleStartEdit,
|
||||
onToggleStar,
|
||||
handleExportKnowledge,
|
||||
handleImageAction,
|
||||
handleDeleteNode,
|
||||
renamingNodeIds,
|
||||
handleAutoRename,
|
||||
|
||||
@ -9,6 +9,7 @@ import { setExportState } from '@renderer/store/runtime'
|
||||
import type { Topic } from '@renderer/types'
|
||||
import type { Message } from '@renderer/types/newMessage'
|
||||
import { removeSpecialCharactersForFileName } from '@renderer/utils/file'
|
||||
import { captureScrollableAsBlob, captureScrollableAsDataURL } from '@renderer/utils/image'
|
||||
import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown'
|
||||
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
|
||||
import { markdownToBlocks } from '@tryfabric/martian'
|
||||
@ -1092,9 +1093,57 @@ const exportNoteAsMarkdown = async (noteName: string, content: string): Promise<
|
||||
}
|
||||
}
|
||||
|
||||
const getScrollableElement = (): HTMLElement | null => {
|
||||
const notesPage = document.querySelector('#notes-page')
|
||||
if (!notesPage) return null
|
||||
|
||||
const allDivs = notesPage.querySelectorAll('div')
|
||||
for (const div of Array.from(allDivs)) {
|
||||
const style = window.getComputedStyle(div)
|
||||
if (style.overflowY === 'auto' || style.overflowY === 'scroll') {
|
||||
if (div.querySelector('.ProseMirror')) {
|
||||
return div as HTMLElement
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const getScrollableRef = (): { current: HTMLElement } | null => {
|
||||
const element = getScrollableElement()
|
||||
if (!element) {
|
||||
window.toast.warning(i18n.t('notes.no_content_to_copy'))
|
||||
return null
|
||||
}
|
||||
return { current: element }
|
||||
}
|
||||
|
||||
const exportNoteAsImageToClipboard = async (): Promise<void> => {
|
||||
const scrollableRef = getScrollableRef()
|
||||
if (!scrollableRef) return
|
||||
|
||||
await captureScrollableAsBlob(scrollableRef, async (blob) => {
|
||||
if (blob) {
|
||||
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })])
|
||||
window.toast.success(i18n.t('common.copied'))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const exportNoteAsImageFile = async (noteName: string): Promise<void> => {
|
||||
const scrollableRef = getScrollableRef()
|
||||
if (!scrollableRef) return
|
||||
|
||||
const dataUrl = await captureScrollableAsDataURL(scrollableRef)
|
||||
if (dataUrl) {
|
||||
const fileName = removeSpecialCharactersForFileName(noteName)
|
||||
await window.api.file.saveImage(fileName, dataUrl)
|
||||
}
|
||||
}
|
||||
|
||||
interface NoteExportOptions {
|
||||
node: { name: string; externalPath: string }
|
||||
platform: 'markdown' | 'docx' | 'notion' | 'yuque' | 'obsidian' | 'joplin' | 'siyuan'
|
||||
platform: 'markdown' | 'docx' | 'notion' | 'yuque' | 'obsidian' | 'joplin' | 'siyuan' | 'copyImage' | 'exportImage'
|
||||
}
|
||||
|
||||
export const exportNote = async ({ node, platform }: NoteExportOptions): Promise<void> => {
|
||||
@ -1102,6 +1151,10 @@ export const exportNote = async ({ node, platform }: NoteExportOptions): Promise
|
||||
const content = await window.api.file.readExternal(node.externalPath)
|
||||
|
||||
switch (platform) {
|
||||
case 'copyImage':
|
||||
return await exportNoteAsImageToClipboard()
|
||||
case 'exportImage':
|
||||
return await exportNoteAsImageFile(node.name)
|
||||
case 'markdown':
|
||||
return await exportNoteAsMarkdown(node.name, content)
|
||||
case 'docx':
|
||||
|
||||
Loading…
Reference in New Issue
Block a user