mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 23:10:20 +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]
|
[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(
|
const handleAutoRename = useCallback(
|
||||||
async (note: NotesTreeNode) => {
|
async (note: NotesTreeNode) => {
|
||||||
if (note.type !== 'file') return
|
if (note.type !== 'file') return
|
||||||
@ -612,6 +629,16 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
key: 'export',
|
key: 'export',
|
||||||
icon: <UploadIcon size={14} />,
|
icon: <UploadIcon size={14} />,
|
||||||
children: [
|
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 && {
|
exportMenuOptions.markdown && {
|
||||||
label: t('chat.topics.export.md.label'),
|
label: t('chat.topics.export.md.label'),
|
||||||
key: 'markdown',
|
key: 'markdown',
|
||||||
@ -671,6 +698,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
|||||||
handleStartEdit,
|
handleStartEdit,
|
||||||
onToggleStar,
|
onToggleStar,
|
||||||
handleExportKnowledge,
|
handleExportKnowledge,
|
||||||
|
handleImageAction,
|
||||||
handleDeleteNode,
|
handleDeleteNode,
|
||||||
renamingNodeIds,
|
renamingNodeIds,
|
||||||
handleAutoRename,
|
handleAutoRename,
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import { setExportState } from '@renderer/store/runtime'
|
|||||||
import type { Topic } from '@renderer/types'
|
import type { Topic } from '@renderer/types'
|
||||||
import type { Message } from '@renderer/types/newMessage'
|
import type { Message } from '@renderer/types/newMessage'
|
||||||
import { removeSpecialCharactersForFileName } from '@renderer/utils/file'
|
import { removeSpecialCharactersForFileName } from '@renderer/utils/file'
|
||||||
|
import { captureScrollableAsBlob, captureScrollableAsDataURL } from '@renderer/utils/image'
|
||||||
import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown'
|
import { convertMathFormula, markdownToPlainText } from '@renderer/utils/markdown'
|
||||||
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
|
import { getCitationContent, getMainTextContent, getThinkingContent } from '@renderer/utils/messageUtils/find'
|
||||||
import { markdownToBlocks } from '@tryfabric/martian'
|
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 {
|
interface NoteExportOptions {
|
||||||
node: { name: string; externalPath: string }
|
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> => {
|
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)
|
const content = await window.api.file.readExternal(node.externalPath)
|
||||||
|
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
|
case 'copyImage':
|
||||||
|
return await exportNoteAsImageToClipboard()
|
||||||
|
case 'exportImage':
|
||||||
|
return await exportNoteAsImageFile(node.name)
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
return await exportNoteAsMarkdown(node.name, content)
|
return await exportNoteAsMarkdown(node.name, content)
|
||||||
case 'docx':
|
case 'docx':
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user