From 9b79051ea5ee6e550ab3be64e5db1df45b1d023b Mon Sep 17 00:00:00 2001 From: happyZYM Date: Tue, 11 Mar 2025 09:59:54 +0800 Subject: [PATCH] feat: enable one-click export for simple markdown exporting (#3137) * feat: enable one-click export for simple markdown exporting * feat: optimize ui for simple markdown export --- src/renderer/src/i18n/locales/en-us.json | 9 +++ src/renderer/src/i18n/locales/ja-jp.json | 9 +++ src/renderer/src/i18n/locales/ru-ru.json | 9 +++ src/renderer/src/i18n/locales/zh-cn.json | 9 +++ src/renderer/src/i18n/locales/zh-tw.json | 9 +++ .../settings/DataSettings/DataSettings.tsx | 60 ++++++++++++++++++- src/renderer/src/store/settings.ts | 6 ++ src/renderer/src/utils/export.ts | 49 +++++++++++++-- 8 files changed, 153 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index e6b1b731..f8679a07 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -411,6 +411,8 @@ "error.invalid.enter.model": "Please select a model", "error.invalid.proxy.url": "Invalid proxy URL", "error.invalid.webdav": "Invalid WebDAV settings", + "error.markdown.export.preconf": "Failed to export the Markdown file to the preconfigured path", + "error.markdown.export.specified": "Failed to export the Markdown file", "error.notion.export": "Failed to export to Notion. Please check connection status and configuration according to documentation", "error.notion.no_api_key": "Notion ApiKey or Notion DatabaseID is not configured", "error.yuque.export": "Failed to export to Yuque. Please check connection status and configuration according to documentation", @@ -441,6 +443,8 @@ "restore.success": "Restored successfully", "save.success.title": "Saved successfully", "searching": "Searching the internet...", + "success.markdown.export.preconf": "Successfully exported the Markdown file to the preconfigured path", + "success.markdown.export.specified": "Successfully exported the Markdown file", "success.notion.export": "Successfully exported to Notion", "success.yuque.export": "Successfully exported to Yuque", "switch.disabled": "Please wait for the current reply to complete", @@ -666,6 +670,11 @@ "hour_interval_other": "{{count}} hours", "minute_interval_one": "{{count}} minute", "minute_interval_other": "{{count}} minutes", + "markdown_export.title": "Markdown Export", + "markdown_export.path": "Default Export Path", + "markdown_export.path_placeholder": "Export Path", + "markdown_export.select": "Select", + "markdown_export.help": "If provided, exports will be automatically saved to this path; otherwise, a save dialog will appear.", "notion.api_key": "Notion API Key", "notion.api_key_placeholder": "Enter Notion API Key", "notion.auto_split": "Auto split when exporting", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 4054ddfa..c08d6fc9 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -411,6 +411,8 @@ "error.invalid.enter.model": "モデルを選択してください", "error.invalid.proxy.url": "無効なプロキシURL", "error.invalid.webdav": "無効なWebDAV設定", + "error.markdown.export.preconf": "Markdown ファイルを事前設定されたパスにエクスポートできませんでした", + "error.markdown.export.specified": "Markdown ファイルのエクスポートに失敗しました", "error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください", "error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません", "error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください", @@ -441,6 +443,8 @@ "restore.success": "復元に成功しました", "save.success.title": "保存に成功しました", "searching": "インターネットで検索中...", + "success.markdown.export.preconf": "Markdown ファイルを事前設定されたパスに正常にエクスポートしました", + "success.markdown.export.specified": "Markdown ファイルを正常にエクスポートしました", "success.notion.export": "Notionへのエクスポートに成功しました", "success.yuque.export": "語雀へのエクスポートに成功しました", "switch.disabled": "現在の応答が完了するまで切り替えを無効にします", @@ -666,6 +670,11 @@ "hour_interval_other": "{{count}} 時間", "minute_interval_one": "{{count}} 分", "minute_interval_other": "{{count}} 分", + "markdown_export.title": "Markdown エクスポート", + "markdown_export.path": "デフォルトのエクスポートパス", + "markdown_export.path_placeholder": "エクスポートパス", + "markdown_export.select": "選択", + "markdown_export.help": "入力された場合、エクスポート時に自動的にこのパスに保存されます。未入力の場合、保存ダイアログが表示されます。", "notion.api_key": "Notion APIキー", "notion.api_key_placeholder": "Notion APIキーを入力してください", "notion.auto_split": "내보내기 시 자동 분할", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 1b46e911..ed145321 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -411,6 +411,8 @@ "error.invalid.enter.model": "Пожалуйста, выберите модель", "error.invalid.proxy.url": "Неверный URL прокси", "error.invalid.webdav": "Неверные настройки WebDAV", + "error.markdown.export.preconf": "Не удалось экспортировать файл Markdown в предуказанный путь", + "error.markdown.export.specified": "Не удалось экспортировать файл Markdown", "error.notion.export": "Ошибка экспорта в Notion, пожалуйста, проверьте состояние подключения и настройки в документации", "error.notion.no_api_key": "Notion ApiKey или Notion DatabaseID не настроен", "error.yuque.export": "Ошибка экспорта в Yuque, пожалуйста, проверьте состояние подключения и настройки в документации", @@ -441,6 +443,8 @@ "restore.success": "Успешно восстановлено", "save.success.title": "Успешно сохранено", "searching": "Поиск в Интернете...", + "success.markdown.export.preconf": "Файл Markdown успешно экспортирован в предуказанный путь", + "success.markdown.export.specified": "Файл Markdown успешно экспортирован", "success.notion.export": "Успешный экспорт в Notion", "success.yuque.export": "Успешный экспорт в Yuque", "switch.disabled": "Пожалуйста, дождитесь завершения текущего ответа", @@ -666,6 +670,11 @@ "hour_interval_other": "{{count}} часов", "minute_interval_one": "{{count}} минута", "minute_interval_other": "{{count}} минут", + "markdown_export.title": "Экспорт в Markdown", + "markdown_export.path": "Путь экспорта по умолчанию", + "markdown_export.path_placeholder": "Путь экспорта", + "markdown_export.select": "Выбрать", + "markdown_export.help": "Если указано, файлы будут автоматически сохраняться в этот путь; в противном случае появится диалоговое окно сохранения.", "notion.api_key": "Ключ API Notion", "notion.api_key_placeholder": "Введите ключ API Notion", "notion.auto_split": "Автоматическое разбиение на страницы при экспорте диалога", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index e088b396..4f9cb34f 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -411,6 +411,8 @@ "error.invalid.enter.model": "请选择一个模型", "error.invalid.proxy.url": "无效的代理地址", "error.invalid.webdav": "无效的 WebDAV 设置", + "error.markdown.export.preconf": "导出Markdown文件到预先设定的路径失败", + "error.markdown.export.specified": "导出Markdown文件失败", "error.notion.export": "导出 Notion 错误,请检查连接状态并对照文档检查配置", "error.notion.no_api_key": "未配置 Notion API Key 或 Notion Database ID", "error.yuque.export": "导出语雀错误,请检查连接状态并对照文档检查配置", @@ -441,6 +443,8 @@ "restore.success": "恢复成功", "save.success.title": "保存成功", "searching": "正在联网搜索...", + "success.markdown.export.preconf": "成功导出Markdown文件到预先设定的路径", + "success.markdown.export.specified": "成功导出Markdown文件", "success.notion.export": "成功导出到Notion", "success.yuque.export": "成功导出到语雀", "switch.disabled": "请等待当前回复完成后操作", @@ -666,6 +670,11 @@ "hour_interval_other": "{{count}} 小时", "minute_interval_one": "{{count}} 分钟", "minute_interval_other": "{{count}} 分钟", + "markdown_export.title": "Markdown 导出", + "markdown_export.path": "默认导出路径", + "markdown_export.path_placeholder": "导出路径", + "markdown_export.select": "选择", + "markdown_export.help": "若填入,则每次导出时将自动保存到该路径;否则,将弹出保存对话框", "notion.api_key": "Notion 密钥", "notion.api_key_placeholder": "请输入Notion 密钥", "notion.auto_split": "导出对话时自动分页", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 66ce6a74..21910d6f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -411,6 +411,8 @@ "error.invalid.enter.model": "請選擇一個模型", "error.invalid.proxy.url": "無效的代理伺服器 URL", "error.invalid.webdav": "無效的 WebDAV 設定", + "error.markdown.export.preconf": "導出 Markdown 文件到預先設定的路徑失敗", + "error.markdown.export.specified": "導出 Markdown 文件失敗", "error.notion.export": "匯出 Notion 錯誤,請檢查連接狀態並對照文件檢查設定", "error.notion.no_api_key": "未設定 Notion API Key 或 Notion Database ID", "error.yuque.export": "匯出語雀錯誤,請檢查連接狀態並對照文件檢查設定", @@ -441,6 +443,8 @@ "restore.success": "恢復成功", "save.success.title": "儲存成功", "searching": "正在網路上搜尋...", + "success.markdown.export.preconf": "成功導出 Markdown 文件到預先設定的路徑", + "success.markdown.export.specified": "成功導出 Markdown 文件", "success.notion.export": "成功匯出到 Notion", "success.yuque.export": "成功匯出到語雀", "switch.disabled": "請等待當前回覆完成", @@ -666,6 +670,11 @@ "hour_interval_other": "{{count}} 小時", "minute_interval_one": "{{count}} 分鐘", "minute_interval_other": "{{count}} 分鐘", + "markdown_export.title": "Markdown 匯出", + "markdown_export.path": "預設匯出路徑", + "markdown_export.path_placeholder": "匯出路徑", + "markdown_export.select": "選擇", + "markdown_export.help": "若填入,每次匯出時將自動儲存至該路徑;否則,將彈出儲存對話框。", "notion.api_key": "Notion 金鑰", "notion.api_key_placeholder": "請輸入 Notion 金鑰", "notion.auto_split": "匯出對話時自動分頁", diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index c20a47cd..3f0358fa 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -1,4 +1,10 @@ -import { FileSearchOutlined, FolderOpenOutlined, InfoCircleOutlined, SaveOutlined } from '@ant-design/icons' +import { + DeleteOutlined, + FileSearchOutlined, + FolderOpenOutlined, + InfoCircleOutlined, + SaveOutlined +} from '@ant-design/icons' import { Client } from '@notionhq/client' import { HStack } from '@renderer/components/Layout' import MinApp from '@renderer/components/MinApp' @@ -9,6 +15,7 @@ import { useKnowledgeFiles } from '@renderer/hooks/useKnowledgeFiles' import { reset } from '@renderer/services/BackupService' import { RootState, useAppDispatch } from '@renderer/store' import { + setmarkdownExportPath, setNotionApiKey, setNotionAutoSplit, setNotionDatabaseID, @@ -38,6 +45,56 @@ import { } from '..' import WebDavSettings from './WebDavSettings' +// 新增的 MarkdownExportSettings 组件 +const MarkdownExportSettings: FC = () => { + const { t } = useTranslation() + const { theme } = useTheme() + const dispatch = useAppDispatch() + + const markdownExportPath = useSelector((state: RootState) => state.settings.markdownExportPath) + + const handleSelectFolder = async () => { + const path = await window.api.file.selectFolder() + if (path) { + dispatch(setmarkdownExportPath(path)) + } + } + + const handleClearPath = () => { + dispatch(setmarkdownExportPath(null)) + } + + return ( + + {t('settings.data.markdown_export.title')} + + + {t('settings.data.markdown_export.path')} + + + ) : null + } + /> + + + + + {t('settings.data.markdown_export.help')} + + + ) +} + // 新增的 NotionSettings 组件 const NotionSettings: FC = () => { const { t } = useTranslation() @@ -382,6 +439,7 @@ const DataSettings: FC = () => { + diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index d269144a..35be6eac 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -72,6 +72,7 @@ export interface SettingsState { notionDatabaseID: string | null notionApiKey: string | null notionPageNameKey: string | null + markdownExportPath: string | null thoughtAutoCollapse: boolean notionAutoSplit: boolean notionSplitSize: number @@ -136,6 +137,7 @@ const initialState: SettingsState = { notionDatabaseID: '', notionApiKey: '', notionPageNameKey: 'Name', + markdownExportPath: null, thoughtAutoCollapse: true, notionAutoSplit: false, notionSplitSize: 90, @@ -310,6 +312,9 @@ const settingsSlice = createSlice({ setNotionPageNameKey: (state, action: PayloadAction) => { state.notionPageNameKey = action.payload }, + setmarkdownExportPath: (state, action: PayloadAction) => { + state.markdownExportPath = action.payload + }, setThoughtAutoCollapse: (state, action: PayloadAction) => { state.thoughtAutoCollapse = action.payload }, @@ -384,6 +389,7 @@ export const { setNotionDatabaseID, setNotionApiKey, setNotionPageNameKey, + setmarkdownExportPath, setThoughtAutoCollapse, setNotionAutoSplit, setNotionSplitSize, diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 32c0f0f5..35a5a475 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -6,6 +6,7 @@ import store from '@renderer/store' import { setExportState } from '@renderer/store/runtime' import { Message, Topic } from '@renderer/types' import { removeSpecialCharactersForFileName } from '@renderer/utils/index' +import dayjs from 'dayjs' export const messageToMarkdown = (message: Message) => { const roleText = message.role === 'user' ? '🧑‍💻 User' : '🤖 Assistant' @@ -31,15 +32,51 @@ export const topicToMarkdown = async (topic: Topic) => { } export const exportTopicAsMarkdown = async (topic: Topic) => { - const fileName = removeSpecialCharactersForFileName(topic.name) + '.md' - const markdown = await topicToMarkdown(topic) - window.api.file.save(fileName, markdown) + const { markdownExportPath } = store.getState().settings + if (!markdownExportPath) { + try { + const fileName = removeSpecialCharactersForFileName(topic.name) + '.md' + const markdown = await topicToMarkdown(topic) + await window.api.file.save(fileName, markdown) + window.message.success({ content: i18n.t('message.success.markdown.export.specified'), key: 'markdown-success' }) + } catch (error: any) { + window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown-error' }) + } + } else { + try { + const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss') + const fileName = removeSpecialCharactersForFileName(topic.name) + ` ${timestamp}.md` + const markdown = await topicToMarkdown(topic) + await window.api.file.write(markdownExportPath + '/' + fileName, markdown) + window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) + } catch (error: any) { + window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown error' }) + } + } } export const exportMessageAsMarkdown = async (message: Message) => { - const fileName = getMessageTitle(message) + '.md' - const markdown = messageToMarkdown(message) - window.api.file.save(fileName, markdown) + const { markdownExportPath } = store.getState().settings + if (!markdownExportPath) { + try { + const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + '.md' + const markdown = messageToMarkdown(message) + await window.api.file.save(fileName, markdown) + window.message.success({ content: i18n.t('message.success.markdown.export.specified'), key: 'markdown-success' }) + } catch (error: any) { + window.message.error({ content: i18n.t('message.error.markdown.export.specified'), key: 'markdown error' }) + } + } else { + try { + const timestamp = dayjs().format('YYYY-MM-DD-HH-mm-ss') + const fileName = removeSpecialCharactersForFileName(getMessageTitle(message)) + ` ${timestamp}.md` + const markdown = messageToMarkdown(message) + await window.api.file.write(markdownExportPath + '/' + fileName, markdown) + window.message.success({ content: i18n.t('message.success.markdown.export.preconf'), key: 'markdown-success' }) + } catch (error: any) { + window.message.error({ content: i18n.t('message.error.markdown.export.preconf'), key: 'markdown-error' }) + } + } } // 修改 splitNotionBlocks 函数