mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-14 06:07:23 +08:00
feat: add notes export (#10488)
* feat: add notes export * chore: fix lint error * feat: unified export interface for notes * fix: hide export reasoning when exporting notes * chore: fix lint error * chore: remove debug log
This commit is contained in:
parent
74db4c4646
commit
f91e7da0a1
@ -38,6 +38,7 @@ interface PopupContainerProps {
|
||||
message?: Message
|
||||
messages?: Message[]
|
||||
topic?: Topic
|
||||
rawContent?: string
|
||||
}
|
||||
|
||||
// 转换文件信息数组为树形结构
|
||||
@ -140,7 +141,8 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
||||
resolve,
|
||||
message,
|
||||
messages,
|
||||
topic
|
||||
topic,
|
||||
rawContent
|
||||
}) => {
|
||||
const defaultObsidianVault = store.getState().settings.defaultObsidianVault
|
||||
const [state, setState] = useState({
|
||||
@ -229,7 +231,9 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
||||
return
|
||||
}
|
||||
let markdown = ''
|
||||
if (topic) {
|
||||
if (rawContent) {
|
||||
markdown = rawContent
|
||||
} else if (topic) {
|
||||
markdown = await topicToMarkdown(topic, exportReasoning)
|
||||
} else if (messages && messages.length > 0) {
|
||||
markdown = messagesToMarkdown(messages, exportReasoning)
|
||||
@ -299,7 +303,6 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={i18n.t('chat.topics.export.obsidian_atributes')}
|
||||
@ -410,9 +413,11 @@ const PopupContainer: React.FC<PopupContainerProps> = ({
|
||||
</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_reasoning')}>
|
||||
<Switch checked={exportReasoning} onChange={setExportReasoning} />
|
||||
</Form.Item>
|
||||
{!rawContent && (
|
||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_reasoning')}>
|
||||
<Switch checked={exportReasoning} onChange={setExportReasoning} />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@ -9,6 +9,7 @@ interface ObsidianExportOptions {
|
||||
topic?: Topic
|
||||
message?: Message
|
||||
messages?: Message[]
|
||||
rawContent?: string
|
||||
}
|
||||
|
||||
export default class ObsidianExportPopup {
|
||||
@ -24,6 +25,7 @@ export default class ObsidianExportPopup {
|
||||
topic={options.topic}
|
||||
message={options.message}
|
||||
messages={options.messages}
|
||||
rawContent={options.rawContent}
|
||||
obsidianTags={''}
|
||||
open={true}
|
||||
resolve={(v) => {
|
||||
|
||||
@ -6,11 +6,13 @@ import { useInPlaceEdit } from '@renderer/hooks/useInPlaceEdit'
|
||||
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
|
||||
import { useActiveNode } from '@renderer/hooks/useNotesQuery'
|
||||
import NotesSidebarHeader from '@renderer/pages/notes/NotesSidebarHeader'
|
||||
import { useAppSelector } from '@renderer/store'
|
||||
import { RootState, useAppSelector } from '@renderer/store'
|
||||
import { selectSortType } from '@renderer/store/note'
|
||||
import { NotesSortType, NotesTreeNode } from '@renderer/types/note'
|
||||
import { exportNote } from '@renderer/utils/export'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import { Dropdown, Input, InputRef, MenuProps } from 'antd'
|
||||
import { ItemType, MenuItemType } from 'antd/es/menu/interface'
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
@ -21,10 +23,12 @@ import {
|
||||
Folder,
|
||||
FolderOpen,
|
||||
Star,
|
||||
StarOff
|
||||
StarOff,
|
||||
UploadIcon
|
||||
} from 'lucide-react'
|
||||
import { FC, memo, Ref, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface NotesSidebarProps {
|
||||
@ -213,6 +217,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
const { bases } = useKnowledgeBases()
|
||||
const { activeNode } = useActiveNode(notesTree)
|
||||
const sortType = useAppSelector(selectSortType)
|
||||
const exportMenuOptions = useSelector((state: RootState) => state.settings.exportMenuOptions)
|
||||
const [editingNodeId, setEditingNodeId] = useState<string | null>(null)
|
||||
const [draggedNodeId, setDraggedNodeId] = useState<string | null>(null)
|
||||
const [dragOverNodeId, setDragOverNodeId] = useState<string | null>(null)
|
||||
@ -525,6 +530,48 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
onClick: () => {
|
||||
handleExportKnowledge(node)
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t('chat.topics.export.title'),
|
||||
key: 'export',
|
||||
icon: <UploadIcon size={14} />,
|
||||
children: [
|
||||
exportMenuOptions.markdown && {
|
||||
label: t('chat.topics.export.md.label'),
|
||||
key: 'markdown',
|
||||
onClick: () => exportNote({ node, platform: 'markdown' })
|
||||
},
|
||||
exportMenuOptions.docx && {
|
||||
label: t('chat.topics.export.word'),
|
||||
key: 'word',
|
||||
onClick: () => exportNote({ node, platform: 'docx' })
|
||||
},
|
||||
exportMenuOptions.notion && {
|
||||
label: t('chat.topics.export.notion'),
|
||||
key: 'notion',
|
||||
onClick: () => exportNote({ node, platform: 'notion' })
|
||||
},
|
||||
exportMenuOptions.yuque && {
|
||||
label: t('chat.topics.export.yuque'),
|
||||
key: 'yuque',
|
||||
onClick: () => exportNote({ node, platform: 'yuque' })
|
||||
},
|
||||
exportMenuOptions.obsidian && {
|
||||
label: t('chat.topics.export.obsidian'),
|
||||
key: 'obsidian',
|
||||
onClick: () => exportNote({ node, platform: 'obsidian' })
|
||||
},
|
||||
exportMenuOptions.joplin && {
|
||||
label: t('chat.topics.export.joplin'),
|
||||
key: 'joplin',
|
||||
onClick: () => exportNote({ node, platform: 'joplin' })
|
||||
},
|
||||
exportMenuOptions.siyuan && {
|
||||
label: t('chat.topics.export.siyuan'),
|
||||
key: 'siyuan',
|
||||
onClick: () => exportNote({ node, platform: 'siyuan' })
|
||||
}
|
||||
].filter(Boolean) as ItemType<MenuItemType>[]
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -543,7 +590,7 @@ const NotesSidebar: FC<NotesSidebarProps> = ({
|
||||
|
||||
return baseMenuItems
|
||||
},
|
||||
[t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode]
|
||||
[t, handleStartEdit, onToggleStar, handleExportKnowledge, handleDeleteNode, exportMenuOptions]
|
||||
)
|
||||
|
||||
const handleDropFiles = useCallback(
|
||||
|
||||
@ -1082,3 +1082,51 @@ export const exportTopicToNotes = async (topic: Topic, folderPath: string): Prom
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const exportNoteAsMarkdown = async (noteName: string, content: string): Promise<void> => {
|
||||
const markdown = `# ${noteName}\n\n${content}`
|
||||
const fileName = removeSpecialCharactersForFileName(noteName) + '.md'
|
||||
const result = await window.api.file.save(fileName, markdown)
|
||||
if (result) {
|
||||
window.toast.success(i18n.t('message.success.markdown.export.specified'))
|
||||
}
|
||||
}
|
||||
|
||||
interface NoteExportOptions {
|
||||
node: { name: string; externalPath: string }
|
||||
platform: 'markdown' | 'docx' | 'notion' | 'yuque' | 'obsidian' | 'joplin' | 'siyuan'
|
||||
}
|
||||
|
||||
export const exportNote = async ({ node, platform }: NoteExportOptions): Promise<void> => {
|
||||
try {
|
||||
const content = await window.api.file.readExternal(node.externalPath)
|
||||
|
||||
switch (platform) {
|
||||
case 'markdown':
|
||||
return await exportNoteAsMarkdown(node.name, content)
|
||||
case 'docx':
|
||||
window.api.export.toWord(`# ${node.name}\n\n${content}`, removeSpecialCharactersForFileName(node.name))
|
||||
return
|
||||
case 'notion':
|
||||
await exportMessageToNotion(node.name, content)
|
||||
return
|
||||
case 'yuque':
|
||||
await exportMarkdownToYuque(node.name, `# ${node.name}\n\n${content}`)
|
||||
return
|
||||
case 'obsidian': {
|
||||
const { default: ObsidianExportPopup } = await import('@renderer/components/Popups/ObsidianExportPopup')
|
||||
await ObsidianExportPopup.show({ title: node.name, processingMethod: '1', rawContent: content })
|
||||
return
|
||||
}
|
||||
case 'joplin':
|
||||
await exportMarkdownToJoplin(node.name, content)
|
||||
return
|
||||
case 'siyuan':
|
||||
await exportMarkdownToSiyuan(node.name, `# ${node.name}\n\n${content}`)
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to export note to ${platform}:`, error as Error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user