diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 37e59515..b016c9ff 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -604,6 +604,20 @@ "notion.page_name_key": "Page Title Field Name", "notion.page_name_key_placeholder": "Enter page title field name, default is Name", "notion.title": "Notion Configuration", + "notion.help": "Notion Configuration Documentation", + "notion.check": { + "button": "Check", + "fail": "Connection failed, please check network and Api_key and Database_id", + "success": "Connection successful", + "error": "Connection error, please check network configuration and Api_key and Database_id", + "empty_api_key": "Api_key is not configured", + "empty_database_id": "Database_id is not configured" + }, + "notion.auto_split": "Auto split when exporting", + "notion.auto_split_tip": "Automatically split pages when exporting long topics to Notion", + "notion.split_size": "Split size", + "notion.split_size_placeholder": "Enter block limit per page (default 90)", + "notion.split_size_help": "Recommended: 90 for Free plan, 24990 for Plus plan, default is 90", "title": "Data Settings", "webdav": { "autoSync": "Auto Backup", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 0049fe1b..574d56a4 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -604,6 +604,20 @@ "notion.page_name_key": "ページタイトルフィールド名", "notion.page_name_key_placeholder": "ページタイトルフィールド名を入力してください。デフォルトは Name です", "notion.title": "Notion 設定", + "notion.help": "Notion 設定ドキュメント", + "notion.check": { + "button": "確認", + "fail": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください", + "success": "接続に成功しました。", + "error": "接続エラー、ネットワーク設定とApi_keyとDatabase_idを確認してください", + "empty_api_key": "Api_keyが設定されていません", + "empty_database_id": "Database_idが設定されていません" + }, + "notion.auto_split": "내보내기 시 자동 분할", + "notion.auto_split_tip": "긴 주제를 Notion으로 내보낼 때 자동으로 페이지 분할", + "notion.split_size": "분할 크기", + "notion.split_size_placeholder": "페이지당 블록 제한 입력(기본값 90)", + "notion.split_size_help": "권장: 무료 플랜 90, Plus 플랜 24990, 기본값 90", "title": "データ設定", "webdav": { "autoSync": "自動バックアップ", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 32b4b5c5..e7f01aee 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -401,7 +401,8 @@ "upgrade.success.button": "重启", "upgrade.success.content": "重启用以完成升级", "upgrade.success.title": "升级成功", - "warn.notion.exporting": "正在导出到Notion, 请勿重复请求导出!" + "warn.notion.exporting": "正在导出到Notion, 请勿重复请求导出!", + "info.notion.block_reach_limit": "对话过长,正在分页导出到Notion" }, "minapp": { "sidebar.add.title": "添加到侧边栏", @@ -604,6 +605,20 @@ "notion.page_name_key": "页面标题字段名", "notion.page_name_key_placeholder": "请输入页面标题字段名,默认为 Name", "notion.title": "Notion 配置", + "notion.help": "Notion 配置文档", + "notion.check": { + "button": "检查", + "fail": "连接失败,请检查网络及Api_key和Database_id是否正确", + "success": "连接成功", + "error": "连接异常,请检查网络及Api_key和Database_id是否正确", + "empty_api_key": "未配置Api_key", + "empty_database_id": "未配置Database_id" + }, + "notion.auto_split_tip": "当要导出的话题过长时自动分页导出到Notion", + "notion.auto_split": "导出对话时自动分页", + "notion.split_size": "自动分页大小", + "notion.split_size_placeholder": "请输入每页块数限制(默认90)", + "notion.split_size_help": "Notion免费版用户建议设置为90,高级版用户建议设置为24990,默认值为90", "title": "数据设置", "webdav": { "autoSync": "自动备份", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 4aa813eb..ad8944b0 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -604,6 +604,20 @@ "notion.page_name_key": "頁面標題欄位名稱", "notion.page_name_key_placeholder": "請輸入頁面標題欄位名稱,預設為 Name", "notion.title": "Notion 配置", + "notion.help": "Notion 配置文檔", + "notion.check": { + "button": "檢查", + "fail": "連接失敗,請檢查網絡及Api_key和Database_id是否正確", + "success": "連線成功", + "error": "連接異常,請檢查網絡及Api_key和Database_id是否正確", + "empty_api_key": "未配置Api_key", + "empty_database_id": "未配置Database_id" + }, + "notion.auto_split": "導出對話時自動分頁", + "notion.auto_split_tip": "當要導出的話題過長時自動分頁導出到Notion", + "notion.split_size": "自動分頁大小", + "notion.split_size_placeholder": "請輸入每頁塊數限制(默認90)", + "notion.split_size_help": "Notion免費版用戶建議設置為90,高級版用戶建議設置為24990,默認值為90", "title": "數據設定", "webdav": { "autoSync": "自動備份", diff --git a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx index 887c564d..c8a5e036 100644 --- a/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx +++ b/src/renderer/src/pages/settings/DataSettings/DataSettings.tsx @@ -5,16 +5,30 @@ import MinApp from '@renderer/components/MinApp' import { useTheme } from '@renderer/context/ThemeProvider' import { backup, reset, restore } from '@renderer/services/BackupService' import { RootState, useAppDispatch } from '@renderer/store' -import { setNotionApiKey, setNotionDatabaseID, setNotionPageNameKey } from '@renderer/store/settings' +import { + setNotionApiKey, + setNotionAutoSplit, + setNotionDatabaseID, + setNotionPageNameKey, + setNotionSplitSize +} from '@renderer/store/settings' import { AppInfo } from '@renderer/types' -import { Button, Modal, Tooltip, Typography } from 'antd' +import { Button, InputNumber, Modal, Switch, Tooltip, Typography } from 'antd' import Input from 'antd/es/input/Input' import { FC, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' import styled from 'styled-components' -import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..' +import { + SettingContainer, + SettingDivider, + SettingGroup, + SettingHelpText, + SettingRow, + SettingRowTitle, + SettingTitle +} from '..' import WebDavSettings from './WebDavSettings' // 新增的 NotionSettings 组件 @@ -26,6 +40,8 @@ const NotionSettings: FC = () => { const notionApiKey = useSelector((state: RootState) => state.settings.notionApiKey) const notionDatabaseID = useSelector((state: RootState) => state.settings.notionDatabaseID) const notionPageNameKey = useSelector((state: RootState) => state.settings.notionPageNameKey) + const notionAutoSplit = useSelector((state: RootState) => state.settings.notionAutoSplit) + const notionSplitSize = useSelector((state: RootState) => state.settings.notionSplitSize) const handleNotionTokenChange = (e: React.ChangeEvent) => { dispatch(setNotionApiKey(e.target.value)) @@ -73,6 +89,16 @@ const NotionSettings: FC = () => { }) } + const handleNotionAutoSplitChange = (checked: boolean) => { + dispatch(setNotionAutoSplit(checked)) + } + + const handleNotionSplitSizeChange = (value: number | null) => { + if (value !== null) { + dispatch(setNotionSplitSize(value)) + } + } + return ( @@ -128,6 +154,37 @@ const NotionSettings: FC = () => { {/* 添加分割线 */} + + + + + {t('settings.data.notion.auto_split')} + + + + + + + {notionAutoSplit && ( + <> + + + {t('settings.data.notion.split_size')} + + + + {t('settings.data.notion.split_size_help')} + + + )} ) } diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index ec75721f..02bacdef 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -71,6 +71,8 @@ export interface SettingsState { notionApiKey: string | null notionPageNameKey: string | null thoughtAutoCollapse: boolean + notionAutoSplit: boolean + notionSplitSize: number } export type MultiModelMessageStyle = 'horizontal' | 'vertical' | 'fold' | 'grid' @@ -127,7 +129,9 @@ const initialState: SettingsState = { notionDatabaseID: '', notionApiKey: '', notionPageNameKey: 'Name', - thoughtAutoCollapse: true + thoughtAutoCollapse: true, + notionAutoSplit: false, + notionSplitSize: 90 } const settingsSlice = createSlice({ @@ -293,6 +297,12 @@ const settingsSlice = createSlice({ }, setThoughtAutoCollapse: (state, action: PayloadAction) => { state.thoughtAutoCollapse = action.payload + }, + setNotionAutoSplit: (state, action: PayloadAction) => { + state.notionAutoSplit = action.payload + }, + setNotionSplitSize: (state, action: PayloadAction) => { + state.notionSplitSize = action.payload } } }) @@ -348,7 +358,9 @@ export const { setNotionDatabaseID, setNotionApiKey, setNotionPageNameKey, - setThoughtAutoCollapse + setThoughtAutoCollapse, + setNotionAutoSplit, + setNotionSplitSize } = settingsSlice.actions export default settingsSlice.reducer diff --git a/src/renderer/src/utils/export.ts b/src/renderer/src/utils/export.ts index 153d6a1e..38996eef 100644 --- a/src/renderer/src/utils/export.ts +++ b/src/renderer/src/utils/export.ts @@ -41,6 +41,127 @@ export const exportMessageAsMarkdown = async (message: Message) => { window.api.file.save(fileName, markdown) } +// 修改 splitNotionBlocks 函数 +const splitNotionBlocks = (blocks: any[]) => { + const { notionAutoSplit, notionSplitSize } = store.getState().settings + + // 如果未开启自动分页,返回单页 + if (!notionAutoSplit) { + return [blocks] + } + + const pages: any[][] = [] + let currentPage: any[] = [] + + blocks.forEach((block) => { + if (currentPage.length >= notionSplitSize) { + window.message.info({ content: i18n.t('message.info.notion.block_reach_limit'), key: 'notion-block-reach-limit' }) + pages.push(currentPage) + currentPage = [] + } + currentPage.push(block) + }) + + if (currentPage.length > 0) { + pages.push(currentPage) + } + + return pages +} + +// 创建页面标题块 +const createPageTitleBlocks = (title: string, pageNumber: number, totalPages: number) => { + return [ + { + object: 'block', + type: 'heading_1', + heading_1: { + rich_text: [{ type: 'text', text: { content: `${title} (${pageNumber}/${totalPages})` } }] + } + }, + { + object: 'block', + type: 'paragraph', + paragraph: { + rich_text: [] + } + } + ] +} + +export const exportTopicToNotion = async (topic: Topic) => { + const { isExporting } = store.getState().runtime.export + if (isExporting) { + window.message.warning({ content: i18n.t('message.warn.notion.exporting'), key: 'notion-exporting' }) + return + } + setExportState({ + isExporting: true + }) + const { notionDatabaseID, notionApiKey } = store.getState().settings + if (!notionApiKey || !notionDatabaseID) { + window.message.error({ content: i18n.t('message.error.notion.no_api_key'), key: 'notion-no-apikey-error' }) + return + } + + try { + const notion = new Client({ auth: notionApiKey }) + const markdown = await topicToMarkdown(topic) + const requestBody = JSON.stringify({ md: markdown }) + + const res = await fetch('https://md2notion.hilars.dev', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: requestBody + }) + + const data = await res.json() + const allBlocks = data + const blockPages = splitNotionBlocks(allBlocks) + + if (blockPages.length === 0) { + throw new Error('No content to export') + } + + // 创建主页面和子页面 + let mainPageResponse: any = null + for (let i = 0; i < blockPages.length; i++) { + const pageTitle = blockPages.length > 1 ? `${topic.name} (${i + 1}/${blockPages.length})` : topic.name + const pageBlocks = blockPages[i] + + const pageContent = + i === 0 ? pageBlocks : [...createPageTitleBlocks(topic.name, i + 1, blockPages.length), ...pageBlocks] + + const response = await notion.pages.create({ + parent: { database_id: notionDatabaseID }, + properties: { + [store.getState().settings.notionPageNameKey || 'Name']: { + title: [{ text: { content: pageTitle } }] + } + }, + children: pageContent + }) + + // 保存主页面响应 + if (i === 0) { + mainPageResponse = response + } + } + + window.message.success({ content: i18n.t('message.success.notion.export'), key: 'notion-success' }) + return mainPageResponse + } catch (error: any) { + window.message.error({ content: i18n.t('message.error.notion.export'), key: 'notion-error' }) + return null + } finally { + setExportState({ + isExporting: false + }) + } +} + export const exportMarkdownToNotion = async (title: string, content: string) => { const { isExporting } = store.getState().runtime.export @@ -93,4 +214,4 @@ export const exportMarkdownToNotion = async (title: string, content: string) => isExporting: false }) } -} +} \ No newline at end of file