feat: add Siyuan Note export functionality and configuration (#3845)

* feat(i18n): add Siyuan Note export functionality and configuration

- 增加导出到思源笔记。

* feat/Add document address

---------

Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
This commit is contained in:
d5v 2025-03-25 13:05:21 +08:00 committed by GitHub
parent fd66881022
commit 8d48824981
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 460 additions and 23 deletions

View File

@ -211,7 +211,8 @@
"topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic", "topics.prompt.tips": "Topic Prompts: Additional supplementary prompts provided for the current topic",
"topics.title": "Topics", "topics.title": "Topics",
"topics.unpinned": "Unpinned Topics", "topics.unpinned": "Unpinned Topics",
"translate": "Translate" "translate": "Translate",
"topics.export.siyuan": "Export to Siyuan Note"
}, },
"code_block": { "code_block": {
"collapse": "Collapse", "collapse": "Collapse",
@ -288,7 +289,8 @@
"description": "Failed to render formula. Please check if the formula format is correct", "description": "Failed to render formula. Please check if the formula format is correct",
"title": "Render Error" "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": { "export": {
"assistant": "Assistant", "assistant": "Assistant",
@ -521,8 +523,11 @@
"upgrade.success.title": "Upgrade successfully", "upgrade.success.title": "Upgrade successfully",
"warn.notion.exporting": "Exporting to Notion, please do not request export repeatedly!", "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.", "warning.rate.limit": "Too many requests. Please wait {{seconds}} seconds before trying again.",
"download.success": "Download successful", "error.siyuan.export": "Failed to export to Siyuan Note, please check connection status and configuration according to documentation",
"download.failed": "Download failed" "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": { "minapp": {
"sidebar.add.title": "Add to sidebar", "sidebar.add.title": "Add to sidebar",
@ -844,6 +849,26 @@
"token": "Yuque Token", "token": "Yuque Token",
"token_placeholder": "Please enter the 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": { "nutstore": {
"title": "Nutstore Configuration", "title": "Nutstore Configuration",
"isLogin": "Logged in", "isLogin": "Logged in",

View File

@ -211,7 +211,8 @@
"topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供", "topics.prompt.tips": "トピック提示語:現在のトピックに対して追加の補足提示語を提供",
"topics.title": "トピック", "topics.title": "トピック",
"topics.unpinned": "固定解除", "topics.unpinned": "固定解除",
"translate": "翻訳" "translate": "翻訳",
"topics.export.siyuan": "思源笔记にエクスポート"
}, },
"code_block": { "code_block": {
"collapse": "折りたたむ", "collapse": "折りたたむ",
@ -288,7 +289,8 @@
"description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください", "description": "数式のレンダリングに失敗しました。数式の形式が正しいか確認してください",
"title": "レンダリングエラー" "title": "レンダリングエラー"
}, },
"user_message_not_found": "元のユーザーメッセージを見つけることができませんでした" "user_message_not_found": "元のユーザーメッセージを見つけることができませんでした",
"unknown": "不明なエラー"
}, },
"export": { "export": {
"assistant": "アシスタント", "assistant": "アシスタント",
@ -476,7 +478,6 @@
"error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください", "error.notion.export": "Notionへのエクスポートに失敗しました。接続状態と設定を確認してください",
"error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません", "error.notion.no_api_key": "Notion ApiKey または Notion DatabaseID が設定されていません",
"error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください", "error.yuque.export": "語雀へのエクスポートに失敗しました。接続状態と設定を確認してください",
"error.yuque.no_config": "語雀Token または 知識ベースID が設定されていません",
"group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます", "group.delete.content": "分組メッセージを削除するとユーザーの質問と助け手の回答がすべて削除されます",
"group.delete.title": "分組メッセージを削除", "group.delete.title": "分組メッセージを削除",
"ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します", "ignore.knowledge.base": "インターネットモードが有効になっています。ナレッジベースを無視します",
@ -521,8 +522,11 @@
"upgrade.success.title": "アップグレードに成功しました", "upgrade.success.title": "アップグレードに成功しました",
"warn.notion.exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! ", "warn.notion.exporting": "Notionにエクスポート中です。重複してエクスポートしないでください! ",
"warning.rate.limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。", "warning.rate.limit": "送信が頻繁すぎます。{{seconds}} 秒待ってから再試行してください。",
"download.success": "ダウンロード成功", "error.siyuan.export": "思源ノートのエクスポートに失敗しました。接続状態を確認し、ドキュメントに従って設定を確認してください",
"download.failed": "ダウンロードに失敗しました" "error.siyuan.no_config": "思源ートのAPIアドレスまたはトークンが設定されていません",
"success.siyuan.export": "思源ノートへのエクスポートに成功しました",
"warn.yuque.exporting": "語雀にエクスポート中です。重複してエクスポートしないでください!",
"warn.siyuan.exporting": "思源ノートにエクスポート中です。重複してエクスポートしないでください!"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "サイドバーに追加", "sidebar.add.title": "サイドバーに追加",
@ -845,6 +849,26 @@
"token": "Yuqueトークン", "token": "Yuqueトークン",
"token_placeholder": "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": { "nutstore": {
"title": "Nutstore設定", "title": "Nutstore設定",
"isLogin": "ログイン済み", "isLogin": "ログイン済み",

View File

@ -211,7 +211,8 @@
"topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы", "topics.prompt.tips": "Тематические подсказки: Дополнительные подсказки, предоставленные для текущей темы",
"topics.title": "Топики", "topics.title": "Топики",
"topics.unpinned": "Открепленные темы", "topics.unpinned": "Открепленные темы",
"translate": "Перевести" "translate": "Перевести",
"topics.export.siyuan": "Экспорт в Siyuan Note"
}, },
"code_block": { "code_block": {
"collapse": "Свернуть", "collapse": "Свернуть",
@ -288,7 +289,8 @@
"description": "Не удалось рендерить формулу. Пожалуйста, проверьте, правильно ли формат формулы", "description": "Не удалось рендерить формулу. Пожалуйста, проверьте, правильно ли формат формулы",
"title": "Ошибка рендеринга" "title": "Ошибка рендеринга"
}, },
"user_message_not_found": "Не удалось найти исходное сообщение пользователя" "user_message_not_found": "Не удалось найти исходное сообщение пользователя",
"unknown": "Неизвестная ошибка"
}, },
"export": { "export": {
"assistant": "Ассистент", "assistant": "Ассистент",
@ -521,8 +523,11 @@
"upgrade.success.title": "Обновление успешно", "upgrade.success.title": "Обновление успешно",
"warn.notion.exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!", "warn.notion.exporting": "Экспортируется в Notion, пожалуйста, не отправляйте повторные запросы!",
"warning.rate.limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова.", "warning.rate.limit": "Отправка слишком частая, пожалуйста, подождите {{seconds}} секунд, прежде чем попробовать снова.",
"download.success": "Скачивание успешно завершено", "error.siyuan.export": "Ошибка экспорта в Siyuan, пожалуйста, проверьте состояние подключения и настройки в документации",
"download.failed": "Ошибка загрузки" "error.siyuan.no_config": "Не настроен API адрес или токен Siyuan",
"success.siyuan.export": "Успешный экспорт в Siyuan",
"warn.yuque.exporting": "Экспортируется в Yuque, пожалуйста, не отправляйте повторные запросы!",
"warn.siyuan.exporting": "Экспортируется в Siyuan, пожалуйста, не отправляйте повторные запросы!"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "Добавить в боковую панель", "sidebar.add.title": "Добавить в боковую панель",
@ -845,6 +850,26 @@
"token": "Токен Yuque", "token": "Токен Yuque",
"token_placeholder": "Введите токен 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": { "nutstore": {
"title": "Настройки Nutstore", "title": "Настройки Nutstore",
"isLogin": "Выполнен вход", "isLogin": "Выполнен вход",

View File

@ -211,7 +211,8 @@
"topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词", "topics.prompt.tips": "话题提示词: 针对当前话题提供额外的补充提示词",
"topics.title": "话题", "topics.title": "话题",
"topics.unpinned": "取消固定", "topics.unpinned": "取消固定",
"translate": "翻译" "translate": "翻译",
"topics.export.siyuan": "导出到思源笔记"
}, },
"code_block": { "code_block": {
"collapse": "收起", "collapse": "收起",
@ -288,7 +289,8 @@
"description": "渲染公式失败,请检查公式格式是否正确", "description": "渲染公式失败,请检查公式格式是否正确",
"title": "渲染错误" "title": "渲染错误"
}, },
"user_message_not_found": "无法找到原始用户消息" "user_message_not_found": "无法找到原始用户消息",
"unknown": "未知错误"
}, },
"export": { "export": {
"assistant": "助手", "assistant": "助手",
@ -521,8 +523,11 @@
"upgrade.success.title": "升级成功", "upgrade.success.title": "升级成功",
"warn.notion.exporting": "正在导出到 Notion, 请勿重复请求导出!", "warn.notion.exporting": "正在导出到 Notion, 请勿重复请求导出!",
"warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试", "warning.rate.limit": "发送过于频繁,请等待 {{seconds}} 秒后再尝试",
"download.success": "下载成功", "error.siyuan.export": "导出思源笔记失败,请检查连接状态并对照文档检查配置",
"download.failed": "下载失败" "error.siyuan.no_config": "未配置思源笔记API地址或令牌",
"success.siyuan.export": "导出到思源笔记成功",
"warn.yuque.exporting": "正在导出语雀, 请勿重复请求导出!",
"warn.siyuan.exporting": "正在导出到思源笔记,请勿重复请求导出!"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "添加到侧边栏", "sidebar.add.title": "添加到侧边栏",
@ -845,6 +850,26 @@
"token": "语雀 Token", "token": "语雀 Token",
"token_placeholder": "请输入语雀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": { "nutstore": {
"title": "坚果云配置", "title": "坚果云配置",
"isLogin": "已登录", "isLogin": "已登录",
@ -866,7 +891,6 @@
"pathSelector.currentPath": "当前路径", "pathSelector.currentPath": "当前路径",
"new_folder.button.confirm": "确定", "new_folder.button.confirm": "确定",
"new_folder.button.cancel": "取消", "new_folder.button.cancel": "取消",
"new_folder.button": "新建文件夹"
} }
}, },
"display.assistant.title": "助手设置", "display.assistant.title": "助手设置",

View File

@ -211,7 +211,8 @@
"topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞", "topics.prompt.tips": "話題提示詞:針對目前話題提供額外的補充提示詞",
"topics.title": "話題", "topics.title": "話題",
"topics.unpinned": "取消固定", "topics.unpinned": "取消固定",
"translate": "翻譯" "translate": "翻譯",
"topics.export.siyuan": "匯出到思源筆記"
}, },
"code_block": { "code_block": {
"collapse": "折疊", "collapse": "折疊",
@ -288,7 +289,8 @@
"description": "渲染公式失敗,請檢查公式格式是否正確", "description": "渲染公式失敗,請檢查公式格式是否正確",
"title": "渲染錯誤" "title": "渲染錯誤"
}, },
"user_message_not_found": "無法找到原始用戶訊息" "user_message_not_found": "無法找到原始用戶訊息",
"unknown": "未知錯誤"
}, },
"export": { "export": {
"assistant": "助手", "assistant": "助手",
@ -521,8 +523,11 @@
"upgrade.success.title": "升級成功", "upgrade.success.title": "升級成功",
"warn.notion.exporting": "正在匯出到 Notion請勿重複請求匯出", "warn.notion.exporting": "正在匯出到 Notion請勿重複請求匯出",
"warning.rate.limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試", "warning.rate.limit": "發送過於頻繁,請在 {{seconds}} 秒後再嘗試",
"download.success": "下載成功", "error.siyuan.export": "導出思源筆記失敗,請檢查連接狀態並對照文檔檢查配置",
"download.failed": "下載失敗" "error.siyuan.no_config": "未配置思源筆記API地址或令牌",
"success.siyuan.export": "導出到思源筆記成功",
"warn.yuque.exporting": "正在導出語雀,請勿重複請求導出!",
"warn.siyuan.exporting": "正在導出到思源筆記,請勿重複請求導出!"
}, },
"minapp": { "minapp": {
"sidebar.add.title": "新增到側邊欄", "sidebar.add.title": "新增到側邊欄",
@ -845,6 +850,26 @@
"token": "語雀 Token", "token": "語雀 Token",
"token_placeholder": "請輸入語雀 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": { "nutstore": {
"title": "堅果雲設定", "title": "堅果雲設定",
"isLogin": "已登入", "isLogin": "已登入",

View File

@ -27,6 +27,7 @@ import { captureScrollableDivAsBlob, captureScrollableDivAsDataURL, removeTraili
import { import {
exportMarkdownToJoplin, exportMarkdownToJoplin,
exportMarkdownToNotion, exportMarkdownToNotion,
exportMarkdownToSiyuan,
exportMarkdownToYuque, exportMarkdownToYuque,
exportMessageAsMarkdown, exportMessageAsMarkdown,
messageToMarkdown messageToMarkdown
@ -254,6 +255,15 @@ const MessageMenubar: FC<Props> = (props) => {
const markdown = messageToMarkdown(message) const markdown = messageToMarkdown(message)
exportMarkdownToJoplin(title, markdown) exportMarkdownToJoplin(title, markdown)
} }
},
{
label: t('chat.topics.export.siyuan'),
key: 'siyuan',
onClick: async () => {
const title = getMessageTitle(message)
const markdown = messageToMarkdown(message)
exportMarkdownToSiyuan(title, markdown)
}
} }
] ]
} }

View File

@ -27,6 +27,7 @@ import { removeSpecialCharactersForFileName } from '@renderer/utils'
import { copyTopicAsMarkdown } from '@renderer/utils/copy' import { copyTopicAsMarkdown } from '@renderer/utils/copy'
import { import {
exportMarkdownToJoplin, exportMarkdownToJoplin,
exportMarkdownToSiyuan,
exportMarkdownToYuque, exportMarkdownToYuque,
exportTopicAsMarkdown, exportTopicAsMarkdown,
exportTopicToNotion, exportTopicToNotion,
@ -295,6 +296,14 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const markdown = await topicToMarkdown(topic) const markdown = await topicToMarkdown(topic)
exportMarkdownToJoplin(topic.name, markdown) exportMarkdownToJoplin(topic.name, markdown)
} }
},
{
label: t('chat.topics.export.siyuan'),
key: 'siyuan',
onClick: async () => {
const markdown = await topicToMarkdown(topic)
exportMarkdownToSiyuan(topic.name, markdown)
}
} }
] ]
} }

View File

@ -28,6 +28,7 @@ import MarkdownExportSettings from './MarkdownExportSettings'
import NotionSettings from './NotionSettings' import NotionSettings from './NotionSettings'
import NutstoreSettings from './NutstoreSettings' import NutstoreSettings from './NutstoreSettings'
import ObsidianSettings from './ObsidianSettings' import ObsidianSettings from './ObsidianSettings'
import SiyuanSettings from './SiyuanSettings'
import WebDavSettings from './WebDavSettings' import WebDavSettings from './WebDavSettings'
import YuqueSettings from './YuqueSettings' import YuqueSettings from './YuqueSettings'
@ -45,6 +46,22 @@ const DataSettings: FC = () => {
</svg> </svg>
) )
const SiyuanIcon = () => (
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2962" width="16" height="16">
<path
d="M309.76 148.16a84.8 84.8 0 0 0-10.88 11.84S288 170.24 288 171.2s-6.72 4.8-6.72 6.72-3.52 1.92-2.88 2.88a12.48 12.48 0 0 0-6.4 6.4 121.28 121.28 0 0 0-20.8 19.2 456.64 456.64 0 0 1-37.76 37.12v2.88c0 2.88 0 0 0 0s-3.52 1.92-6.72 5.12c-8.64 9.28-19.84 20.48-28.16 28.16l-7.04 7.04-2.56 2.88a114.88 114.88 0 0 0-20.16 21.76 2.88 2.88 0 0 1-8 8.64l-1.6 1.6a99.52 99.52 0 0 0-19.52 18.88 21.44 21.44 0 0 0-6.4 5.44c-14.08 14.4-22.4 23.04-22.72 23.04l-9.28 8.96-8.96 8.96V887.04c0 1.28 3.2 2.56 6.72-1.92s3.52-3.84 4.16-3.84 0-1.6 0 0S163.84 800 219.84 744.64l38.4-38.08c16-16.32 29.12-29.76 28.8-30.4s6.72-4.16 5.76-5.76 5.44-3.2 5.44-5.12 23.68-23.04 23.04-26.56 0-115.52 0-252.16V138.56a128 128 0 0 0-11.84 10.88z m373.76 2.24a96 96 0 0 0-13.44 15.04s-33.92 32-76.48 74.56l-42.56 42.88L512 320v504.96s5.76-5.12 5.12-5.76a29.44 29.44 0 0 0 8.32-7.68c3.84-4.16 9.92-10.24 13.76-13.76l21.44-21.76 21.76-21.44c18.56-18.24 32-32 32-32l8.96-9.6a69.76 69.76 0 0 1 10.56-9.6s3.84-1.92 3.84-3.52 6.4-4.48 5.76-5.12 3.2-2.56 2.56-3.2 1.6 0 0 0 11.52-10.24 24-22.72l22.72-22.4v-256-251.84c0-0.96 0-2.24-15.36 11.84z"
fill="#cdcdcd"
p-id="2963"></path>
<path
d="M322.24 136h0c-1.6 0 0-0.64 0 0z m2.88 0v504.64l45.12 44.16c37.44 36.8 93.76 92.8 116.48 114.88l14.4 15.04a64 64 0 0 0 10.24 9.6V320l-4.8-4.48c-2.88-2.24-7.68-7.36-11.52-10.88l-42.24-41.92-20.8-21.12-16-14.4a76.48 76.48 0 0 1-7.36-7.04l-23.36-23.68-42.56-44.16c-15.04-15.04-16-16-17.6-14.72z m376 1.92V640l123.84 123.84c98.24 97.92 124.48 123.52 126.4 123.52h2.56V386.56l-124.8-124.8C760 192 704 136.96 704 136.96a3.52 3.52 0 0 0-1.6 2.56z"
fill="#707070"
p-id="2964"></path>
<path
d="M699.52 136.64V136z m-376.96 249.6V136.96s-0.32 50.56 0 249.28zM512 573.76v-127.04zM667.84 672l-6.72 7.36 7.04-7.04c6.72-6.08 7.68-7.36 6.72-7.36zM184 272.96v1.92l2.56-1.92c2.56-1.92 0-2.24 0-2.24a5.44 5.44 0 0 0-2.56 2.24zM141.76 314.88a2.24 2.24 0 0 0 1.92 0v-1.6z m483.2 399.04a71.36 71.36 0 0 0-8.96 10.24 69.76 69.76 0 0 0 10.56-9.6 56 56 0 0 0 8.96-10.24 73.28 73.28 0 0 0-10.56 9.6z m-448 75.52l-3.2 3.2 3.52-2.88 3.52-3.52s-2.56 0-5.44 3.2z m-97.92 96v1.92l2.88-1.92s1.92-2.24 0-2.24a6.72 6.72 0 0 0-4.48 2.88z"
p-id="2965"></path>
</svg>
)
const menuItems = [ const menuItems = [
{ key: 'data', title: 'settings.data.data.title', icon: <DatabaseOutlined style={{ fontSize: 16 }} /> }, { key: 'data', title: 'settings.data.data.title', icon: <DatabaseOutlined style={{ fontSize: 16 }} /> },
{ key: 'webdav', title: 'settings.data.webdav.title', icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> }, { key: 'webdav', title: 'settings.data.webdav.title', icon: <CloudSyncOutlined style={{ fontSize: 16 }} /> },
@ -70,6 +87,11 @@ const DataSettings: FC = () => {
title: 'settings.data.joplin.title', title: 'settings.data.joplin.title',
//joplin icon needs to be updated into iconfont //joplin icon needs to be updated into iconfont
icon: <JoplinIcon /> icon: <JoplinIcon />
},
{
key: 'siyuan',
title: 'settings.data.siyuan.title',
icon: <SiyuanIcon />
} }
] ]
@ -210,6 +232,7 @@ const DataSettings: FC = () => {
{menu === 'yuque' && <YuqueSettings />} {menu === 'yuque' && <YuqueSettings />}
{menu === 'obsidian' && <ObsidianSettings />} {menu === 'obsidian' && <ObsidianSettings />}
{menu === 'joplin' && <JoplinSettings />} {menu === 'joplin' && <JoplinSettings />}
{menu === 'siyuan' && <SiyuanSettings />}
</SettingContainer> </SettingContainer>
</Container> </Container>
) )

View File

@ -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<HTMLInputElement>) => {
dispatch(setSiyuanApiUrl(e.target.value))
}
const handleTokenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanToken(e.target.value))
}
const handleBoxIdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSiyuanBoxId(e.target.value))
}
const handleRootPathChange = (e: React.ChangeEvent<HTMLInputElement>) => {
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 (
<SettingGroup theme={theme}>
<SettingTitle>{t('settings.data.siyuan.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.api_url')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="text"
value={siyuanApiUrl || ''}
onChange={handleApiUrlChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.api_url_placeholder')}
/>
</HStack>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle style={{ display: 'flex', alignItems: 'center' }}>
<span>{t('settings.data.siyuan.token')}</span>
<Tooltip title={t('settings.data.siyuan.token.help')} placement="left">
<InfoCircleOutlined
style={{ color: 'var(--color-text-2)', cursor: 'pointer', marginLeft: 4 }}
onClick={handleSiyuanHelpClick}
/>
</Tooltip>
</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="password"
value={siyuanToken || ''}
onChange={handleTokenChange}
style={{ width: 250 }}
placeholder={t('settings.data.siyuan.token_placeholder')}
/>
<Button onClick={handleCheckConnection}>{t('settings.data.siyuan.check.button')}</Button>
</HStack>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.box_id')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="text"
value={siyuanBoxId || ''}
onChange={handleBoxIdChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.box_id_placeholder')}
/>
</HStack>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.data.siyuan.root_path')}</SettingRowTitle>
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
<Input
type="text"
value={siyuanRootPath || ''}
onChange={handleRootPathChange}
style={{ width: 315 }}
placeholder={t('settings.data.siyuan.root_path_placeholder')}
/>
</HStack>
</SettingRow>
</SettingGroup>
)
}
export default SiyuanSettings

View File

@ -96,6 +96,11 @@ export interface SettingsState {
obsidianTages: string | null obsidianTages: string | null
joplinToken: string | null joplinToken: string | null
joplinUrl: string | null joplinUrl: string | null
// 思源笔记配置
siyuanApiUrl: string | null
siyuanToken: string | null
siyuanBoxId: string | null
siyuanRootPath: string | null
} }
export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid'
@ -171,7 +176,12 @@ const initialState: SettingsState = {
obsidianFolder: '', obsidianFolder: '',
obsidianTages: '', obsidianTages: '',
joplinToken: '', joplinToken: '',
joplinUrl: '' joplinUrl: '',
// 思源笔记配置初始值
siyuanApiUrl: null,
siyuanToken: null,
siyuanBoxId: null,
siyuanRootPath: null
} }
const settingsSlice = createSlice({ const settingsSlice = createSlice({
@ -391,6 +401,18 @@ const settingsSlice = createSlice({
setJoplinUrl: (state, action: PayloadAction<string>) => { setJoplinUrl: (state, action: PayloadAction<string>) => {
state.joplinUrl = action.payload state.joplinUrl = action.payload
}, },
setSiyuanApiUrl: (state, action: PayloadAction<string>) => {
state.siyuanApiUrl = action.payload
},
setSiyuanToken: (state, action: PayloadAction<string>) => {
state.siyuanToken = action.payload
},
setSiyuanBoxId: (state, action: PayloadAction<string>) => {
state.siyuanBoxId = action.payload
},
setSiyuanRootPath: (state, action: PayloadAction<string>) => {
state.siyuanRootPath = action.payload
},
setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => { setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => {
state.messageNavigation = action.payload state.messageNavigation = action.payload
} }
@ -467,6 +489,10 @@ export const {
setObsidianTages, setObsidianTages,
setJoplinToken, setJoplinToken,
setJoplinUrl, setJoplinUrl,
setSiyuanApiUrl,
setSiyuanToken,
setSiyuanBoxId,
setSiyuanRootPath,
setMessageNavigation setMessageNavigation
} = settingsSlice.actions } = settingsSlice.actions

View File

@ -450,3 +450,99 @@ export const exportMarkdownToJoplin = async (title: string, content: string) =>
return 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<string> {
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
}