From 8d4882498118c2c2485fad9fc68e3ae474c184e4 Mon Sep 17 00:00:00 2001 From: d5v <88039301+deadmau5v@users.noreply.github.com> Date: Tue, 25 Mar 2025 13:05:21 +0800 Subject: [PATCH] feat: add Siyuan Note export functionality and configuration (#3845) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(i18n): add Siyuan Note export functionality and configuration - 增加导出到思源笔记。 * feat/Add document address --------- Co-authored-by: 亢奋猫 --- src/renderer/src/i18n/locales/en-us.json | 33 +++- src/renderer/src/i18n/locales/ja-jp.json | 34 +++- src/renderer/src/i18n/locales/ru-ru.json | 33 +++- src/renderer/src/i18n/locales/zh-cn.json | 34 +++- src/renderer/src/i18n/locales/zh-tw.json | 33 +++- .../pages/home/Messages/MessageMenubar.tsx | 10 ++ .../src/pages/home/Tabs/TopicsTab.tsx | 9 ++ .../settings/DataSettings/DataSettings.tsx | 23 +++ .../settings/DataSettings/SiyuanSettings.tsx | 150 ++++++++++++++++++ src/renderer/src/store/settings.ts | 28 +++- src/renderer/src/utils/export.ts | 96 +++++++++++ 11 files changed, 460 insertions(+), 23 deletions(-) create mode 100644 src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index aaa8a726..ab4603e8 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -211,7 +211,8 @@ "topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic", "topics.title": "Topics", "topics.unpinned": "Unpinned Topics", - "translate": "Translate" + "translate": "Translate", + "topics.export.siyuan": "Export to Siyuan Note" }, "code_block": { "collapse": "Collapse", @@ -288,7 +289,8 @@ "description": "Failed to render formula. Please check if the formula format is correct", "title": "Render Error" }, - "user_message_not_found": "Cannot find original user message to resend" + "user_message_not_found": "Cannot find original user message to resend", + "unknown": "Unknown error" }, "export": { "assistant": "Assistant", @@ -521,8 +523,11 @@ "upgrade.success.title": "Upgrade successfully", "warn.notion.exporting": "Exporting to Notion, please do not request export repeatedly!", "warning.rate.limit": "Too many requests. Please wait {{seconds}} seconds before trying again.", - "download.success": "Download successful", - "download.failed": "Download failed" + "error.siyuan.export": "Failed to export to Siyuan Note, please check connection status and configuration according to documentation", + "error.siyuan.no_config": "Siyuan Note API address or token is not configured", + "success.siyuan.export": "Successfully exported to Siyuan Note", + "warn.yuque.exporting": "Exporting to Yuque, please do not request export repeatedly!", + "warn.siyuan.exporting": "Exporting to Siyuan Note, please do not request export repeatedly!" }, "minapp": { "sidebar.add.title": "Add to sidebar", @@ -844,6 +849,26 @@ "token": "Yuque Token", "token_placeholder": "Please enter the Yuque Token" }, + "siyuan": { + "title": "Siyuan Note Configuration", + "api_url": "Siyuan Note API URL", + "api_url_placeholder": "e.g.: http://127.0.0.1:6806", + "token": "Siyuan Note Token", + "token.help": "Get Siyuan Note Token", + "token_placeholder": "Please enter Siyuan Note Token", + "box_id": "Siyuan Note Box ID", + "box_id_placeholder": "Please enter Siyuan Note Box ID", + "root_path": "Siyuan Note Root Path", + "root_path_placeholder": "e.g.: /CherryStudio", + "check": { + "title": "Connection Check", + "button": "Check", + "empty_config": "Please fill in the API address and token", + "success": "Connection successful", + "fail": "Connection failed, please check API address and token", + "error": "Connection error, please check network connection" + } + }, "nutstore": { "title": "Nutstore Configuration", "isLogin": "Logged in", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 01a6d953..53acf263 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -211,7 +211,8 @@ "topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供", "topics.title": "トピック", "topics.unpinned": "固定解除", - "translate": "翻訳" + "translate": "翻訳", + "topics.export.siyuan": "思源笔记にエクスポート" }, "code_block": { "collapse": "折りたたむ", @@ -288,7 +289,8 @@ "description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください", "title": "レンダリングエラー" }, - "user_message_not_found": "元のユーザーメッセージを見つけることができませんでした" + "user_message_not_found": "元のユーザーメッセージを見つけることができませんでした", + "unknown": "不明なエラー" }, "export": { "assistant": "アシスタント", @@ -476,7 +478,6 @@ "error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください", "error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません", "error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください", - "error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません", "group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます", "group.delete.title": "分組メッセージを削除", "ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します", @@ -521,8 +522,11 @@ "upgrade.success.title": "アップグレードに成功しました", "warn.notion.exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! ", "warning.rate.limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。", - "download.success": "ダウンロード成功", - "download.failed": "ダウンロードに失敗しました" + "error.siyuan.export": "思源ノートのエクスポートに失敗しました。接続状態を確認し、ドキュメントに従って設定を確認してください", + "error.siyuan.no_config": "思源ノートのAPIアドレスまたはトークンが設定されていません", + "success.siyuan.export": "思源ノートへのエクスポートに成功しました", + "warn.yuque.exporting": "語雀にエクスポート中です。重複してエクスポートしないでください!", + "warn.siyuan.exporting": "思源ノートにエクスポート中です。重複してエクスポートしないでください!" }, "minapp": { "sidebar.add.title": "サイドバーに追加", @@ -845,6 +849,26 @@ "token": "Yuqueトークン", "token_placeholder": "Yuqueトークンを入力してください" }, + "siyuan": { + "title": "思源ノート設定", + "api_url": "APIアドレス", + "api_url_placeholder": "例:http://127.0.0.1:6806", + "token": "APIトークン", + "token.help": "思源ノート->設定->について で取得", + "token_placeholder": "思源ノートトークンを入力してください", + "box_id": "ノートブックID", + "box_id_placeholder": "ノートブックIDを入力してください", + "root_path": "ドキュメントルートパス", + "root_path_placeholder": "例:/CherryStudio", + "check": { + "title": "接続チェック", + "button": "チェック", + "empty_config": "APIアドレスとトークンを入力してください", + "success": "接続成功", + "fail": "接続失敗、APIアドレスとトークンを確認してください", + "error": "接続エラー、ネットワーク接続を確認してください" + } + }, "nutstore": { "title": "Nutstore設定", "isLogin": "ログイン済み", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 9dcc3621..de4b3f8c 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -211,7 +211,8 @@ "topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы", "topics.title": "Топики", "topics.unpinned": "Открепленные темы", - "translate": "Перевести" + "translate": "Перевести", + "topics.export.siyuan": "Экспорт в Siyuan Note" }, "code_block": { "collapse": "Свернуть", @@ -288,7 +289,8 @@ "description": "Не удалось рендерить формулу. Пожалуйста, проверьте, правильно ли формат формулы", "title": "Ошибка рендеринга" }, - "user_message_not_found": "Не удалось найти исходное сообщение пользователя" + "user_message_not_found": "Не удалось найти исходное сообщение пользователя", + "unknown": "Неизвестная ошибка" }, "export": { "assistant": "Ассистент", @@ -521,8 +523,11 @@ "upgrade.success.title": "Обновление успешно", "warn.notion.exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!", "warning.rate.limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова.", - "download.success": "Скачивание успешно завершено", - "download.failed": "Ошибка загрузки" + "error.siyuan.export": "Ошибка экспорта в Siyuan, пожалуйста, проверьте состояние подключения и настройки в документации", + "error.siyuan.no_config": "Не настроен API адрес или токен Siyuan", + "success.siyuan.export": "Успешный экспорт в Siyuan", + "warn.yuque.exporting": "Экспортируется в Yuque, пожалуйста, не отправляйте повторные запросы!", + "warn.siyuan.exporting": "Экспортируется в Siyuan, пожалуйста, не отправляйте повторные запросы!" }, "minapp": { "sidebar.add.title": "Добавить в боковую панель", @@ -845,6 +850,26 @@ "token": "Токен Yuque", "token_placeholder": "Введите токен Yuque" }, + "siyuan": { + "title": "Конфигурация SiYuan Note", + "api_url": "API адрес", + "api_url_placeholder": "Например: http://127.0.0.1:6806", + "token": "API токен", + "token.help": "Получите в SiYuan Note -> Настройки -> О программе", + "token_placeholder": "Введите токен SiYuan Note", + "box_id": "ID блокнота", + "box_id_placeholder": "Введите ID блокнота", + "root_path": "Корневой путь документа", + "root_path_placeholder": "Например: /CherryStudio", + "check": { + "title": "Проверка соединения", + "button": "Проверить", + "empty_config": "Пожалуйста, заполните API адрес и токен", + "success": "Соединение успешно", + "fail": "Не удалось подключиться, проверьте API адрес и токен", + "error": "Ошибка соединения, проверьте сетевое подключение" + } + }, "nutstore": { "title": "Настройки Nutstore", "isLogin": "Выполнен вход", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 0c4d9f5a..4fe2e14e 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -211,7 +211,8 @@ "topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词", "topics.title": "话题", "topics.unpinned": "取消固定", - "translate": "翻译" + "translate": "翻译", + "topics.export.siyuan": "导出到思源笔记" }, "code_block": { "collapse": "收起", @@ -288,7 +289,8 @@ "description": "渲染公式失败,请检查公式格式是否正确", "title": "渲染错误" }, - "user_message_not_found": "无法找到原始用户消息" + "user_message_not_found": "无法找到原始用户消息", + "unknown": "未知错误" }, "export": { "assistant": "助手", @@ -521,8 +523,11 @@ "upgrade.success.title": "升级成功", "warn.notion.exporting": "正在导出到 Notion, 请勿重复请求导出!", "warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试", - "download.success": "下载成功", - "download.failed": "下载失败" + "error.siyuan.export": "导出思源笔记失败,请检查连接状态并对照文档检查配置", + "error.siyuan.no_config": "未配置思源笔记API地址或令牌", + "success.siyuan.export": "导出到思源笔记成功", + "warn.yuque.exporting": "正在导出语雀, 请勿重复请求导出!", + "warn.siyuan.exporting": "正在导出到思源笔记,请勿重复请求导出!" }, "minapp": { "sidebar.add.title": "添加到侧边栏", @@ -845,6 +850,26 @@ "token": "语雀 Token", "token_placeholder": "请输入语雀Token" }, + "siyuan": { + "title": "思源笔记配置", + "api_url": "API地址", + "api_url_placeholder": "例如:http://127.0.0.1:6806", + "token": "API令牌", + "token.help": "在思源笔记->设置->关于中获取", + "token_placeholder": "请输入思源笔记令牌", + "box_id": "笔记本ID", + "box_id_placeholder": "请输入笔记本ID", + "root_path": "文档根路径", + "root_path_placeholder": "例如:/CherryStudio", + "check": { + "title": "连接检查", + "button": "检查", + "empty_config": "请填写API地址和令牌", + "success": "连接成功", + "fail": "连接失败,请检查API地址和令牌", + "error": "连接异常,请检查网络连接" + } + }, "nutstore": { "title": "坚果云配置", "isLogin": "已登录", @@ -866,7 +891,6 @@ "pathSelector.currentPath": "当前路径", "new_folder.button.confirm": "确定", "new_folder.button.cancel": "取消", - "new_folder.button": "新建文件夹" } }, "display.assistant.title": "助手设置", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 45e4a911..d8d5a36d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -211,7 +211,8 @@ "topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞", "topics.title": "話題", "topics.unpinned": "取消固定", - "translate": "翻譯" + "translate": "翻譯", + "topics.export.siyuan": "匯出到思源筆記" }, "code_block": { "collapse": "折疊", @@ -288,7 +289,8 @@ "description": "渲染公式失敗,請檢查公式格式是否正確", "title": "渲染錯誤" }, - "user_message_not_found": "無法找到原始用戶訊息" + "user_message_not_found": "無法找到原始用戶訊息", + "unknown": "未知錯誤" }, "export": { "assistant": "助手", @@ -521,8 +523,11 @@ "upgrade.success.title": "升級成功", "warn.notion.exporting": "正在匯出到 Notion,請勿重複請求匯出!", "warning.rate.limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試", - "download.success": "下載成功", - "download.failed": "下載失敗" + "error.siyuan.export": "導出思源筆記失敗,請檢查連接狀態並對照文檔檢查配置", + "error.siyuan.no_config": "未配置思源筆記API地址或令牌", + "success.siyuan.export": "導出到思源筆記成功", + "warn.yuque.exporting": "正在導出語雀,請勿重複請求導出!", + "warn.siyuan.exporting": "正在導出到思源筆記,請勿重複請求導出!" }, "minapp": { "sidebar.add.title": "新增到側邊欄", @@ -845,6 +850,26 @@ "token": "語雀 Token", "token_placeholder": "請輸入語雀 Token" }, + "siyuan": { + "title": "思源筆記配置", + "api_url": "API地址", + "api_url_placeholder": "例如:http://127.0.0.1:6806", + "token": "API令牌", + "token.help": "在思源筆記->設置->關於中獲取", + "token_placeholder": "請輸入思源筆記令牌", + "box_id": "筆記本ID", + "box_id_placeholder": "請輸入筆記本ID", + "root_path": "文檔根路徑", + "root_path_placeholder": "例如:/CherryStudio", + "check": { + "title": "連接檢查", + "button": "檢查", + "empty_config": "請填寫API地址和令牌", + "success": "連接成功", + "fail": "連接失敗,請檢查API地址和令牌", + "error": "連接異常,請檢查網絡連接" + } + }, "nutstore": { "title": "堅果雲設定", "isLogin": "已登入", diff --git a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx index 903e1fe8..5b518524 100644 --- a/src/renderer/src/pages/home/Messages/MessageMenubar.tsx +++ b/src/renderer/src/pages/home/Messages/MessageMenubar.tsx @@ -27,6 +27,7 @@ import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTraili import { exportMarkdownToJoplin, exportMarkdownToNotion, + exportMarkdownToSiyuan, exportMarkdownToYuque, exportMessageAsMarkdown, messageToMarkdown @@ -254,6 +255,15 @@ const MessageMenubar: FC = (props) => { const markdown = messageToMarkdown(message) exportMarkdownToJoplin(title, markdown) } + }, + { + label: t('chat.topics.export.siyuan'), + key: 'siyuan', + onClick: async () => { + const title = getMessageTitle(message) + const markdown = messageToMarkdown(message) + exportMarkdownToSiyuan(title, markdown) + } } ] } diff --git a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx index aa97cbe2..920a291c 100644 --- a/src/renderer/src/pages/home/Tabs/TopicsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/TopicsTab.tsx @@ -27,6 +27,7 @@ import { removeSpecialCharactersForFileName } from '@renderer/utils' import { copyTopicAsMarkdown } from '@renderer/utils/copy' import { exportMarkdownToJoplin, + exportMarkdownToSiyuan, exportMarkdownToYuque, exportTopicAsMarkdown, exportTopicToNotion, @@ -295,6 +296,14 @@ const Topics: FC = ({ assistant: _assistant, activeTopic, setActiveTopic const markdown = await topicToMarkdown(topic) exportMarkdownToJoplin(topic.name, markdown) } + }, + { + label: t('chat.topics.export.siyuan'), + key: 'siyuan', + onClick: async () => { + const markdown = await topicToMarkdown(topic) + exportMarkdownToSiyuan(topic.name, markdown) + } } ] } diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index 80c7daec..6cf62649 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -28,6 +28,7 @@ import MarkdownExportSettings from './MarkdownExportSettings' import NotionSettings from './NotionSettings' import NutstoreSettings from './NutstoreSettings' import ObsidianSettings from './ObsidianSettings' +import SiyuanSettings from './SiyuanSettings' import WebDavSettings from './WebDavSettings' import YuqueSettings from './YuqueSettings' @@ -45,6 +46,22 @@ const DataSettings: FC = () => { ) + const SiyuanIcon = () => ( + + + + + + ) + const menuItems = [ { key: 'data', title: 'settings.data.data.title', icon: }, { key: 'webdav', title: 'settings.data.webdav.title', icon: }, @@ -70,6 +87,11 @@ const DataSettings: FC = () => { title: 'settings.data.joplin.title', //joplin icon needs to be updated into iconfont icon: + }, + { + key: 'siyuan', + title: 'settings.data.siyuan.title', + icon: } ] @@ -210,6 +232,7 @@ const DataSettings: FC = () => { {menu === 'yuque' && } {menu === 'obsidian' && } {menu === 'joplin' && } + {menu === 'siyuan' && } ) diff --git a/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx new file mode 100644 index 00000000..6357ad29 --- /dev/null +++ b/src/renderer/src/pages/settings/DataSettings/SiyuanSettings.tsx @@ -0,0 +1,150 @@ +import { InfoCircleOutlined } from '@ant-design/icons' +import { HStack } from '@renderer/components/Layout' +import MinApp from '@renderer/components/MinApp' +import { useTheme } from '@renderer/context/ThemeProvider' +import { RootState, useAppDispatch } from '@renderer/store' +import { setSiyuanApiUrl, setSiyuanBoxId, setSiyuanRootPath, setSiyuanToken } from '@renderer/store/settings' +import { Button, Tooltip } from 'antd' +import Input from 'antd/es/input/Input' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import { useSelector } from 'react-redux' + +import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' + +const SiyuanSettings: FC = () => { + const { t } = useTranslation() + const { theme } = useTheme() + const dispatch = useAppDispatch() + + const siyuanApiUrl = useSelector((state: RootState) => state.settings.siyuanApiUrl) + const siyuanToken = useSelector((state: RootState) => state.settings.siyuanToken) + const siyuanBoxId = useSelector((state: RootState) => state.settings.siyuanBoxId) + const siyuanRootPath = useSelector((state: RootState) => state.settings.siyuanRootPath) + + const handleApiUrlChange = (e: React.ChangeEvent) => { + dispatch(setSiyuanApiUrl(e.target.value)) + } + + const handleTokenChange = (e: React.ChangeEvent) => { + dispatch(setSiyuanToken(e.target.value)) + } + + const handleBoxIdChange = (e: React.ChangeEvent) => { + dispatch(setSiyuanBoxId(e.target.value)) + } + + const handleRootPathChange = (e: React.ChangeEvent) => { + dispatch(setSiyuanRootPath(e.target.value)) + } + + const handleSiyuanHelpClick = () => { + MinApp.start({ + id: 'siyuan-help', + name: 'Siyuan Help', + url: 'https://docs.cherry-ai.com/advanced-basic/siyuan' + }) + } + + const handleCheckConnection = async () => { + try { + if (!siyuanApiUrl || !siyuanToken) { + window.message.error(t('settings.data.siyuan.check.empty_config')) + return + } + + const response = await fetch(`${siyuanApiUrl}/api/notebook/lsNotebooks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Token ${siyuanToken}` + } + }) + + if (!response.ok) { + window.message.error(t('settings.data.siyuan.check.fail')) + return + } + + const data = await response.json() + if (data.code !== 0) { + window.message.error(t('settings.data.siyuan.check.fail')) + return + } + + window.message.success(t('settings.data.siyuan.check.success')) + } catch (error) { + console.error('Check Siyuan connection failed:', error) + window.message.error(t('settings.data.siyuan.check.error')) + } + } + + return ( + + {t('settings.data.siyuan.title')} + + + {t('settings.data.siyuan.api_url')} + + + + + + + + {t('settings.data.siyuan.token')} + + + + + + + + + + + + {t('settings.data.siyuan.box_id')} + + + + + + + {t('settings.data.siyuan.root_path')} + + + + + + ) +} + +export default SiyuanSettings diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 98a7a84a..b38557d5 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -96,6 +96,11 @@ export interface SettingsState { obsidianTages: string | null joplinToken: string | null joplinUrl: string | null + // 思源笔记配置 + siyuanApiUrl: string | null + siyuanToken: string | null + siyuanBoxId: string | null + siyuanRootPath: string | null } export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' @@ -171,7 +176,12 @@ const initialState: SettingsState = { obsidianFolder: '', obsidianTages: '', joplinToken: '', - joplinUrl: '' + joplinUrl: '', + // 思源笔记配置初始值 + siyuanApiUrl: null, + siyuanToken: null, + siyuanBoxId: null, + siyuanRootPath: null } const settingsSlice = createSlice({ @@ -391,6 +401,18 @@ const settingsSlice = createSlice({ setJoplinUrl: (state, action: PayloadAction) => { state.joplinUrl = action.payload }, + setSiyuanApiUrl: (state, action: PayloadAction) => { + state.siyuanApiUrl = action.payload + }, + setSiyuanToken: (state, action: PayloadAction) => { + state.siyuanToken = action.payload + }, + setSiyuanBoxId: (state, action: PayloadAction) => { + state.siyuanBoxId = action.payload + }, + setSiyuanRootPath: (state, action: PayloadAction) => { + state.siyuanRootPath = action.payload + }, setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => { state.messageNavigation = action.payload } @@ -467,6 +489,10 @@ export const { setObsidianTages, setJoplinToken, setJoplinUrl, + setSiyuanApiUrl, + setSiyuanToken, + setSiyuanBoxId, + setSiyuanRootPath, setMessageNavigation } = settingsSlice.actions diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index f8d74d04..05b27399 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -450,3 +450,99 @@ export const exportMarkdownToJoplin = async (title: string, content: string) => return } } + +/** + * 导出Markdown到思源笔记 + * @param title 笔记标题 + * @param content 笔记内容 + */ +export const exportMarkdownToSiyuan = async (title: string, content: string) => { + const { isExporting } = store.getState().runtime.export + const { siyuanApiUrl, siyuanToken, siyuanBoxId, siyuanRootPath } = store.getState().settings + + if (isExporting) { + window.message.warning({ content: i18n.t('message.warn.siyuan.exporting'), key: 'siyuan-exporting' }) + return + } + + if (!siyuanApiUrl || !siyuanToken || !siyuanBoxId) { + window.message.error({ content: i18n.t('message.error.siyuan.no_config'), key: 'siyuan-no-config-error' }) + return + } + + setExportState({ isExporting: true }) + + try { + // test connection + const testResponse = await fetch(`${siyuanApiUrl}/api/notebook/lsNotebooks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Token ${siyuanToken}` + } + }) + + if (!testResponse.ok) { + throw new Error('API请求失败') + } + + const testData = await testResponse.json() + if (testData.code !== 0) { + throw new Error(`${testData.msg || i18n.t('message.error.unknown')}`) + } + + // 确保根路径以/开头 + const rootPath = siyuanRootPath?.startsWith('/') ? siyuanRootPath : `/${siyuanRootPath || 'CherryStudio'}` + + // 创建文档 + const docTitle = `${title.replace(/[#|\\^\\[\]]/g, '')}` + const docPath = `${rootPath}/${docTitle}` + + // 创建文档 + await createSiyuanDoc(siyuanApiUrl, siyuanToken, siyuanBoxId, docPath, content) + + window.message.success({ + content: i18n.t('message.success.siyuan.export'), + key: 'siyuan-success' + }) + } catch (error) { + console.error('导出到思源笔记失败:', error) + window.message.error({ + content: i18n.t('message.error.siyuan.export') + (error instanceof Error ? `: ${error.message}` : ''), + key: 'siyuan-error' + }) + } finally { + setExportState({ isExporting: false }) + } +} + +/** + * 创建思源笔记文档 + */ +async function createSiyuanDoc( + apiUrl: string, + token: string, + boxId: string, + path: string, + markdown: string +): Promise { + const response = await fetch(`${apiUrl}/api/filetree/createDocWithMd`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Token ${token}` + }, + body: JSON.stringify({ + notebook: boxId, + path: path, + markdown: markdown + }) + }) + + const data = await response.json() + if (data.code !== 0) { + throw new Error(`${data.msg || i18n.t('message.error.unknown')}`) + } + + return data.data +}