feat: 优化导出obsidian,自动选择库路径,不再需要手动配置 (#3854)
* feat: 优化导出obsidian,自动选择库路径,不再需要手动配置 * fix: eslint报错 * feat: 增加预设置默认仓库 * fix: 解决合并冲突
This commit is contained in:
parent
aee0f9ea3f
commit
917943386e
@ -17,6 +17,7 @@ import FileStorage from './services/FileStorage'
|
|||||||
import { GeminiService } from './services/GeminiService'
|
import { GeminiService } from './services/GeminiService'
|
||||||
import KnowledgeService from './services/KnowledgeService'
|
import KnowledgeService from './services/KnowledgeService'
|
||||||
import MCPService from './services/MCPService'
|
import MCPService from './services/MCPService'
|
||||||
|
import ObsidianVaultService from './services/ObsidianVaultService'
|
||||||
import * as NutstoreService from './services/NutstoreService'
|
import * as NutstoreService from './services/NutstoreService'
|
||||||
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
import { ProxyConfig, proxyManager } from './services/ProxyManager'
|
||||||
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
import { registerShortcuts, unregisterAllShortcuts } from './services/ShortcutService'
|
||||||
@ -31,6 +32,7 @@ const fileManager = new FileStorage()
|
|||||||
const backupManager = new BackupManager()
|
const backupManager = new BackupManager()
|
||||||
const exportService = new ExportService(fileManager)
|
const exportService = new ExportService(fileManager)
|
||||||
const mcpService = new MCPService()
|
const mcpService = new MCPService()
|
||||||
|
const obsidianVaultService = new ObsidianVaultService()
|
||||||
|
|
||||||
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
||||||
const appUpdater = new AppUpdater(mainWindow)
|
const appUpdater = new AppUpdater(mainWindow)
|
||||||
@ -300,6 +302,15 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
|
|||||||
ipcMain.handle('copilot:logout', CopilotService.logout)
|
ipcMain.handle('copilot:logout', CopilotService.logout)
|
||||||
ipcMain.handle('copilot:get-user', CopilotService.getUser)
|
ipcMain.handle('copilot:get-user', CopilotService.getUser)
|
||||||
|
|
||||||
|
// Obsidian service
|
||||||
|
ipcMain.handle('obsidian:get-vaults', () => {
|
||||||
|
return obsidianVaultService.getVaults()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle('obsidian:get-files', (_event, vaultName) => {
|
||||||
|
return obsidianVaultService.getFilesByVaultName(vaultName)
|
||||||
|
})
|
||||||
|
|
||||||
// nutstore
|
// nutstore
|
||||||
ipcMain.handle('nutstore:get-sso-url', NutstoreService.getNutstoreSSOUrl)
|
ipcMain.handle('nutstore:get-sso-url', NutstoreService.getNutstoreSSOUrl)
|
||||||
ipcMain.handle('nutstore:decrypt-token', (_, token: string) => NutstoreService.decryptToken(token))
|
ipcMain.handle('nutstore:decrypt-token', (_, token: string) => NutstoreService.decryptToken(token))
|
||||||
|
|||||||
167
src/main/services/ObsidianVaultService.ts
Normal file
167
src/main/services/ObsidianVaultService.ts
Normal file
@ -0,0 +1,167 @@
|
|||||||
|
import { app } from 'electron'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
|
interface VaultInfo {
|
||||||
|
path: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileInfo {
|
||||||
|
path: string
|
||||||
|
type: 'folder' | 'markdown'
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObsidianVaultService {
|
||||||
|
private obsidianConfigPath: string
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
// 根据操作系统获取Obsidian配置文件路径
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
this.obsidianConfigPath = path.join(app.getPath('appData'), 'obsidian', 'obsidian.json')
|
||||||
|
} else if (process.platform === 'darwin') {
|
||||||
|
this.obsidianConfigPath = path.join(
|
||||||
|
app.getPath('home'),
|
||||||
|
'Library',
|
||||||
|
'Application Support',
|
||||||
|
'obsidian',
|
||||||
|
'obsidian.json'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Linux
|
||||||
|
this.obsidianConfigPath = path.join(app.getPath('home'), '.config', 'obsidian', 'obsidian.json')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有的Obsidian Vault
|
||||||
|
*/
|
||||||
|
getVaults(): VaultInfo[] {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(this.obsidianConfigPath)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const configContent = fs.readFileSync(this.obsidianConfigPath, 'utf8')
|
||||||
|
const config = JSON.parse(configContent)
|
||||||
|
|
||||||
|
if (!config.vaults) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(config.vaults).map(([, vault]: [string, any]) => ({
|
||||||
|
path: vault.path,
|
||||||
|
name: vault.name || path.basename(vault.path)
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取Obsidian Vault失败:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Vault中的文件夹和Markdown文件结构
|
||||||
|
*/
|
||||||
|
getVaultStructure(vaultPath: string): FileInfo[] {
|
||||||
|
const results: FileInfo[] = []
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查vault路径是否存在
|
||||||
|
if (!fs.existsSync(vaultPath)) {
|
||||||
|
console.error('Vault路径不存在:', vaultPath)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是目录
|
||||||
|
const stats = fs.statSync(vaultPath)
|
||||||
|
if (!stats.isDirectory()) {
|
||||||
|
console.error('Vault路径不是一个目录:', vaultPath)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
this.traverseDirectory(vaultPath, '', results)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取Vault文件夹结构失败:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归遍历目录获取所有文件夹和Markdown文件
|
||||||
|
*/
|
||||||
|
private traverseDirectory(dirPath: string, relativePath: string, results: FileInfo[]) {
|
||||||
|
try {
|
||||||
|
// 首先添加当前文件夹
|
||||||
|
if (relativePath) {
|
||||||
|
results.push({
|
||||||
|
path: relativePath,
|
||||||
|
type: 'folder',
|
||||||
|
name: path.basename(relativePath)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目录存在且可访问
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
console.error('目录不存在:', dirPath)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let items
|
||||||
|
try {
|
||||||
|
items = fs.readdirSync(dirPath, { withFileTypes: true })
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`无法读取目录 ${dirPath}:`, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
// 忽略以.开头的隐藏文件夹和文件
|
||||||
|
if (item.name.startsWith('.')) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const newRelativePath = relativePath ? `${relativePath}/${item.name}` : item.name
|
||||||
|
const fullPath = path.join(dirPath, item.name)
|
||||||
|
|
||||||
|
if (item.isDirectory()) {
|
||||||
|
this.traverseDirectory(fullPath, newRelativePath, results)
|
||||||
|
} else if (item.isFile() && item.name.endsWith('.md')) {
|
||||||
|
// 收集.md文件
|
||||||
|
results.push({
|
||||||
|
path: newRelativePath,
|
||||||
|
type: 'markdown',
|
||||||
|
name: item.name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`遍历目录出错 ${dirPath}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定Vault的文件夹和Markdown文件结构
|
||||||
|
* @param vaultName vault名称
|
||||||
|
*/
|
||||||
|
getFilesByVaultName(vaultName: string): FileInfo[] {
|
||||||
|
try {
|
||||||
|
const vaults = this.getVaults()
|
||||||
|
const vault = vaults.find((v) => v.name === vaultName)
|
||||||
|
|
||||||
|
if (!vault) {
|
||||||
|
console.error('未找到指定名称的Vault:', vaultName)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('获取Vault文件结构:', vault.name, vault.path)
|
||||||
|
return this.getVaultStructure(vault.path)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取Vault文件结构时发生错误:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ObsidianVaultService
|
||||||
@ -174,6 +174,11 @@ if (process.contextIsolated) {
|
|||||||
try {
|
try {
|
||||||
contextBridge.exposeInMainWorld('electron', electronAPI)
|
contextBridge.exposeInMainWorld('electron', electronAPI)
|
||||||
contextBridge.exposeInMainWorld('api', api)
|
contextBridge.exposeInMainWorld('api', api)
|
||||||
|
contextBridge.exposeInMainWorld('obsidian', {
|
||||||
|
getVaults: () => ipcRenderer.invoke('obsidian:get-vaults'),
|
||||||
|
getFolders: (vaultName: string) => ipcRenderer.invoke('obsidian:get-files', vaultName),
|
||||||
|
getFiles: (vaultName: string) => ipcRenderer.invoke('obsidian:get-files', vaultName)
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,223 @@
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
|
import store from '@renderer/store'
|
||||||
import { exportMarkdownToObsidian } from '@renderer/utils/export'
|
import { exportMarkdownToObsidian } from '@renderer/utils/export'
|
||||||
import { Form, Input, Modal, Select } from 'antd'
|
import { Alert, Empty, Form, Input, Modal, Select, Spin, TreeSelect } from 'antd'
|
||||||
import React, { useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
interface ObsidianExportDialogProps {
|
interface ObsidianExportDialogProps {
|
||||||
title: string
|
title: string
|
||||||
markdown: string
|
markdown: string
|
||||||
open: boolean // 使用 open 属性替代 visible
|
open: boolean
|
||||||
onClose: (success: boolean) => void
|
onClose: (success: boolean) => void
|
||||||
obsidianTags: string | null
|
obsidianTags: string | null
|
||||||
processingMethod: string | '3' //默认新增(存在就覆盖)
|
processingMethod: string | '3' //默认新增(存在就覆盖)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface FileInfo {
|
||||||
|
path: string
|
||||||
|
type: 'folder' | 'markdown'
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换文件信息数组为树形结构
|
||||||
|
const convertToTreeData = (files: FileInfo[]) => {
|
||||||
|
const treeData: any[] = [
|
||||||
|
{
|
||||||
|
title: i18n.t('chat.topics.export.obsidian_root_directory'),
|
||||||
|
value: '',
|
||||||
|
isLeaf: false,
|
||||||
|
selectable: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 记录已创建的节点路径
|
||||||
|
const pathMap: Record<string, any> = {
|
||||||
|
'': treeData[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先按类型分组,确保先处理文件夹
|
||||||
|
const folders = files.filter((file) => file.type === 'folder')
|
||||||
|
const mdFiles = files.filter((file) => file.type === 'markdown')
|
||||||
|
|
||||||
|
// 按路径排序,确保父文件夹先被创建
|
||||||
|
const sortedFolders = [...folders].sort((a, b) => a.path.split('/').length - b.path.split('/').length)
|
||||||
|
|
||||||
|
// 先处理所有文件夹,构建目录结构
|
||||||
|
for (const folder of sortedFolders) {
|
||||||
|
const parts = folder.path.split('/')
|
||||||
|
let currentPath = ''
|
||||||
|
let parentPath = ''
|
||||||
|
|
||||||
|
// 遍历文件夹路径的每一部分,确保创建完整路径
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const part = parts[i]
|
||||||
|
|
||||||
|
// 构建当前路径
|
||||||
|
currentPath = currentPath ? `${currentPath}/${part}` : part
|
||||||
|
|
||||||
|
// 如果这个路径节点还没创建
|
||||||
|
if (!pathMap[currentPath]) {
|
||||||
|
const node = {
|
||||||
|
title: part,
|
||||||
|
value: currentPath,
|
||||||
|
key: currentPath,
|
||||||
|
isLeaf: false,
|
||||||
|
selectable: true,
|
||||||
|
children: []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取父节点,将当前节点添加到父节点的children中
|
||||||
|
const parentNode = pathMap[parentPath]
|
||||||
|
if (parentNode) {
|
||||||
|
if (!parentNode.children) {
|
||||||
|
parentNode.children = []
|
||||||
|
}
|
||||||
|
parentNode.children.push(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
pathMap[currentPath] = node
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新父路径为当前路径,为下一级做准备
|
||||||
|
parentPath = currentPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后处理md文件
|
||||||
|
for (const file of mdFiles) {
|
||||||
|
const fullPath = file.path
|
||||||
|
const dirPath = fullPath.substring(0, fullPath.lastIndexOf('/'))
|
||||||
|
const fileName = file.name
|
||||||
|
|
||||||
|
// 获取父文件夹节点
|
||||||
|
const parentNode = pathMap[dirPath] || pathMap['']
|
||||||
|
|
||||||
|
// 创建文件节点
|
||||||
|
const fileNode = {
|
||||||
|
title: fileName,
|
||||||
|
value: fullPath,
|
||||||
|
isLeaf: true,
|
||||||
|
selectable: true,
|
||||||
|
icon: <span style={{ marginRight: 4 }}>📄</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加到父节点
|
||||||
|
if (!parentNode.children) {
|
||||||
|
parentNode.children = []
|
||||||
|
}
|
||||||
|
parentNode.children.push(fileNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return treeData
|
||||||
|
}
|
||||||
|
|
||||||
const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
||||||
title,
|
title,
|
||||||
markdown,
|
markdown,
|
||||||
obsidianTags,
|
|
||||||
processingMethod,
|
|
||||||
open,
|
open,
|
||||||
onClose
|
onClose,
|
||||||
|
obsidianTags,
|
||||||
|
processingMethod
|
||||||
}) => {
|
}) => {
|
||||||
|
const defaultObsidianVault = store.getState().settings.defaultObsidianVault
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
title: title,
|
title,
|
||||||
tags: obsidianTags || '',
|
tags: obsidianTags || '',
|
||||||
createdAt: new Date().toISOString().split('T')[0],
|
createdAt: new Date().toISOString().split('T')[0],
|
||||||
source: 'Cherry Studio',
|
source: 'Cherry Studio',
|
||||||
processingMethod: processingMethod
|
processingMethod: processingMethod,
|
||||||
|
folder: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [vaults, setVaults] = useState<Array<{ path: string; name: string }>>([])
|
||||||
|
const [files, setFiles] = useState<FileInfo[]>([])
|
||||||
|
const [fileTreeData, setFileTreeData] = useState<any[]>([])
|
||||||
|
const [selectedVault, setSelectedVault] = useState<string>('')
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
// 处理文件数据转为树形结构
|
||||||
|
useEffect(() => {
|
||||||
|
if (files.length > 0) {
|
||||||
|
const treeData = convertToTreeData(files)
|
||||||
|
setFileTreeData(treeData)
|
||||||
|
} else {
|
||||||
|
setFileTreeData([
|
||||||
|
{
|
||||||
|
title: i18n.t('chat.topics.export.obsidian_root_directory'),
|
||||||
|
value: '',
|
||||||
|
isLeaf: false,
|
||||||
|
selectable: true
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}, [files])
|
||||||
|
|
||||||
|
// 组件加载时获取Vault列表
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchVaults = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
const vaultsData = await window.obsidian.getVaults()
|
||||||
|
|
||||||
|
if (vaultsData.length === 0) {
|
||||||
|
setError(i18n.t('chat.topics.export.obsidian_no_vaults'))
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setVaults(vaultsData)
|
||||||
|
|
||||||
|
// 如果没有选择的vault,使用默认值或第一个
|
||||||
|
const vaultToUse = defaultObsidianVault || vaultsData[0]?.name
|
||||||
|
if (vaultToUse) {
|
||||||
|
setSelectedVault(vaultToUse)
|
||||||
|
|
||||||
|
// 获取选中vault的文件和文件夹
|
||||||
|
const filesData = await window.obsidian.getFiles(vaultToUse)
|
||||||
|
setFiles(filesData)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取Obsidian Vault失败:', error)
|
||||||
|
setError(i18n.t('chat.topics.export.obsidian_fetch_error'))
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchVaults()
|
||||||
|
}, [defaultObsidianVault])
|
||||||
|
|
||||||
|
// 当选择的vault变化时,获取其文件和文件夹
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedVault) {
|
||||||
|
const fetchFiles = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
const filesData = await window.obsidian.getFiles(selectedVault)
|
||||||
|
setFiles(filesData)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取Obsidian文件失败:', error)
|
||||||
|
setError(i18n.t('chat.topics.export.obsidian_fetch_folders_error'))
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchFiles()
|
||||||
|
}
|
||||||
|
}, [selectedVault])
|
||||||
|
|
||||||
const handleOk = async () => {
|
const handleOk = async () => {
|
||||||
|
if (!selectedVault) {
|
||||||
|
setError(i18n.t('chat.topics.export.obsidian_no_vault_selected'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
//构建content 并复制到粘贴板
|
//构建content 并复制到粘贴板
|
||||||
let content = ''
|
let content = ''
|
||||||
if (state.processingMethod !== '3') {
|
if (state.processingMethod !== '3') {
|
||||||
@ -45,10 +232,18 @@ const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
|||||||
}
|
}
|
||||||
if (content === '') {
|
if (content === '') {
|
||||||
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
|
window.message.error(i18n.t('chat.topics.export.obsidian_export_failed'))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
await navigator.clipboard.writeText(content)
|
await navigator.clipboard.writeText(content)
|
||||||
markdown = ''
|
|
||||||
exportMarkdownToObsidian(state)
|
// 导出到Obsidian
|
||||||
|
exportMarkdownToObsidian({
|
||||||
|
...state,
|
||||||
|
folder: state.folder,
|
||||||
|
vault: selectedVault
|
||||||
|
})
|
||||||
|
|
||||||
onClose(true)
|
onClose(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,18 +255,56 @@ const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
|||||||
setState((prevState) => ({ ...prevState, [key]: value }))
|
setState((prevState) => ({ ...prevState, [key]: value }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleVaultChange = (value: string) => {
|
||||||
|
setSelectedVault(value)
|
||||||
|
// 文件夹会通过useEffect自动获取
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
folder: ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件选择
|
||||||
|
const handleFileSelect = (value: string) => {
|
||||||
|
// 更新folder值
|
||||||
|
handleChange('folder', value)
|
||||||
|
|
||||||
|
// 检查是否选中md文件
|
||||||
|
if (value) {
|
||||||
|
const selectedFile = files.find((file) => file.path === value)
|
||||||
|
if (selectedFile) {
|
||||||
|
if (selectedFile.type === 'markdown') {
|
||||||
|
// 如果是md文件,自动设置标题为文件名并设置处理方式为1(追加)
|
||||||
|
const fileName = selectedFile.name
|
||||||
|
const titleWithoutExt = fileName.endsWith('.md') ? fileName.substring(0, fileName.length - 3) : fileName
|
||||||
|
handleChange('title', titleWithoutExt)
|
||||||
|
handleChange('processingMethod', '1')
|
||||||
|
} else {
|
||||||
|
// 如果是文件夹,自动设置标题为话题名并设置处理方式为3(新建)
|
||||||
|
handleChange('processingMethod', '3')
|
||||||
|
handleChange('title', title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
title={i18n.t('chat.topics.export.obsidian_atributes')}
|
title={i18n.t('chat.topics.export.obsidian_atributes')}
|
||||||
open={open} // 使用 open 属性
|
open={open}
|
||||||
onOk={handleOk}
|
onOk={handleOk}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
width={600}
|
width={600}
|
||||||
closable
|
closable
|
||||||
maskClosable
|
maskClosable
|
||||||
centered
|
centered
|
||||||
okButtonProps={{ type: 'primary' }}
|
okButtonProps={{
|
||||||
|
type: 'primary',
|
||||||
|
disabled: vaults.length === 0 || loading || !!error
|
||||||
|
}}
|
||||||
okText={i18n.t('chat.topics.export.obsidian_btn')}>
|
okText={i18n.t('chat.topics.export.obsidian_btn')}>
|
||||||
|
{error && <Alert message={error} type="error" showIcon style={{ marginBottom: 16 }} />}
|
||||||
|
|
||||||
<Form layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} labelAlign="left">
|
<Form layout="horizontal" labelCol={{ span: 6 }} wrapperCol={{ span: 18 }} labelAlign="left">
|
||||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_title')}>
|
<Form.Item label={i18n.t('chat.topics.export.obsidian_title')}>
|
||||||
<Input
|
<Input
|
||||||
@ -80,6 +313,55 @@ const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
|||||||
placeholder={i18n.t('chat.topics.export.obsidian_title_placeholder')}
|
placeholder={i18n.t('chat.topics.export.obsidian_title_placeholder')}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={i18n.t('chat.topics.export.obsidian_vault')}>
|
||||||
|
{vaults.length > 0 ? (
|
||||||
|
<Select
|
||||||
|
loading={loading}
|
||||||
|
value={selectedVault}
|
||||||
|
onChange={handleVaultChange}
|
||||||
|
placeholder={i18n.t('chat.topics.export.obsidian_vault_placeholder')}
|
||||||
|
style={{ width: '100%' }}>
|
||||||
|
{vaults.map((vault) => (
|
||||||
|
<Option key={vault.name} value={vault.name}>
|
||||||
|
{vault.name}
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
description={
|
||||||
|
loading
|
||||||
|
? i18n.t('chat.topics.export.obsidian_loading')
|
||||||
|
: i18n.t('chat.topics.export.obsidian_no_vaults')
|
||||||
|
}
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item label={i18n.t('chat.topics.export.obsidian_path')}>
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
{selectedVault ? (
|
||||||
|
<TreeSelect
|
||||||
|
value={state.folder}
|
||||||
|
onChange={handleFileSelect}
|
||||||
|
placeholder={i18n.t('chat.topics.export.obsidian_path_placeholder')}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
showSearch
|
||||||
|
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||||
|
treeDefaultExpandAll={false}
|
||||||
|
treeNodeFilterProp="title"
|
||||||
|
treeData={fileTreeData}></TreeSelect>
|
||||||
|
) : (
|
||||||
|
<Empty
|
||||||
|
description={i18n.t('chat.topics.export.obsidian_select_vault_first')}
|
||||||
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_tags')}>
|
<Form.Item label={i18n.t('chat.topics.export.obsidian_tags')}>
|
||||||
<Input
|
<Input
|
||||||
value={state.tags}
|
value={state.tags}
|
||||||
@ -101,6 +383,7 @@ const ObsidianExportDialog: React.FC<ObsidianExportDialogProps> = ({
|
|||||||
placeholder={i18n.t('chat.topics.export.obsidian_source_placeholder')}
|
placeholder={i18n.t('chat.topics.export.obsidian_source_placeholder')}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={i18n.t('chat.topics.export.obsidian_operate')}>
|
<Form.Item label={i18n.t('chat.topics.export.obsidian_operate')}>
|
||||||
<Select
|
<Select
|
||||||
value={state.processingMethod}
|
value={state.processingMethod}
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import ObsidianExportDialog from '@renderer/components/ObsidianExportDialog'
|
import ObsidianExportDialog from '@renderer/components/ObsidianExportDialog'
|
||||||
import i18n from '@renderer/i18n'
|
|
||||||
import store from '@renderer/store'
|
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
|
|
||||||
interface ObsidianExportOptions {
|
interface ObsidianExportOptions {
|
||||||
@ -17,14 +15,6 @@ interface ObsidianExportOptions {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
const showObsidianExportDialog = async (options: ObsidianExportOptions): Promise<boolean> => {
|
const showObsidianExportDialog = async (options: ObsidianExportOptions): Promise<boolean> => {
|
||||||
const obsidianValut = store.getState().settings.obsidianValut
|
|
||||||
const obsidianFolder = store.getState().settings.obsidianFolder
|
|
||||||
|
|
||||||
if (!obsidianValut || !obsidianFolder) {
|
|
||||||
window.message.error(i18n.t('chat.topics.export.obsidian_not_configured'))
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
const div = document.createElement('div')
|
const div = document.createElement('div')
|
||||||
document.body.appendChild(div)
|
document.body.appendChild(div)
|
||||||
@ -35,12 +25,12 @@ const showObsidianExportDialog = async (options: ObsidianExportOptions): Promise
|
|||||||
document.body.removeChild(div)
|
document.body.removeChild(div)
|
||||||
resolve(success)
|
resolve(success)
|
||||||
}
|
}
|
||||||
const obsidianTags = store.getState().settings.obsidianTages
|
// 不再从store中获取tag配置
|
||||||
root.render(
|
root.render(
|
||||||
<ObsidianExportDialog
|
<ObsidianExportDialog
|
||||||
title={options.title}
|
title={options.title}
|
||||||
markdown={options.markdown}
|
markdown={options.markdown}
|
||||||
obsidianTags={obsidianTags}
|
obsidianTags=""
|
||||||
processingMethod={options.processingMethod}
|
processingMethod={options.processingMethod}
|
||||||
open={true}
|
open={true}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
@ -49,8 +39,6 @@ const showObsidianExportDialog = async (options: ObsidianExportOptions): Promise
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const ObsidianExportPopup = {
|
export default {
|
||||||
show: showObsidianExportDialog
|
show: showObsidianExportDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ObsidianExportPopup
|
|
||||||
|
|||||||
@ -180,13 +180,16 @@
|
|||||||
"topics.export.md": "Export as markdown",
|
"topics.export.md": "Export as markdown",
|
||||||
"topics.export.notion": "Export to Notion",
|
"topics.export.notion": "Export to Notion",
|
||||||
"topics.export.obsidian": "Export to Obsidian",
|
"topics.export.obsidian": "Export to Obsidian",
|
||||||
|
"topics.export.obsidian_vault": "Vault",
|
||||||
|
"topics.export.obsidian_vault_placeholder": "Please select the vault name",
|
||||||
|
"topics.export.obsidian_path": "Path",
|
||||||
|
"topics.export.obsidian_path_placeholder": "Please select the path",
|
||||||
"topics.export.obsidian_atributes": "Configure Note Attributes",
|
"topics.export.obsidian_atributes": "Configure Note Attributes",
|
||||||
"topics.export.obsidian_btn": "Confirm",
|
"topics.export.obsidian_btn": "Confirm",
|
||||||
"topics.export.obsidian_created": "Creation Time",
|
"topics.export.obsidian_created": "Creation Time",
|
||||||
"topics.export.obsidian_created_placeholder": "Please select the creation time",
|
"topics.export.obsidian_created_placeholder": "Please select the creation time",
|
||||||
"topics.export.obsidian_export_failed": "Export failed",
|
"topics.export.obsidian_export_failed": "Export failed",
|
||||||
"topics.export.obsidian_export_success": "Export success",
|
"topics.export.obsidian_export_success": "Export success",
|
||||||
"topics.export.obsidian_not_configured": "Obsidian not configured",
|
|
||||||
"topics.export.obsidian_operate": "Operation Method",
|
"topics.export.obsidian_operate": "Operation Method",
|
||||||
"topics.export.obsidian_operate_append": "Append",
|
"topics.export.obsidian_operate_append": "Append",
|
||||||
"topics.export.obsidian_operate_new_or_overwrite": "Create New (Overwrite if it exists)",
|
"topics.export.obsidian_operate_new_or_overwrite": "Create New (Overwrite if it exists)",
|
||||||
@ -199,6 +202,13 @@
|
|||||||
"topics.export.obsidian_title": "Title",
|
"topics.export.obsidian_title": "Title",
|
||||||
"topics.export.obsidian_title_placeholder": "Please enter the title",
|
"topics.export.obsidian_title_placeholder": "Please enter the title",
|
||||||
"topics.export.obsidian_title_required": "The title cannot be empty",
|
"topics.export.obsidian_title_required": "The title cannot be empty",
|
||||||
|
"topics.export.obsidian_no_vaults": "No Obsidian vaults found",
|
||||||
|
"topics.export.obsidian_loading": "Loading...",
|
||||||
|
"topics.export.obsidian_fetch_error": "Failed to fetch Obsidian vaults",
|
||||||
|
"topics.export.obsidian_fetch_folders_error": "Failed to fetch folder structure",
|
||||||
|
"topics.export.obsidian_no_vault_selected": "Please select a vault first",
|
||||||
|
"topics.export.obsidian_select_vault_first": "Please select a vault first",
|
||||||
|
"topics.export.obsidian_root_directory": "Root Directory",
|
||||||
"topics.export.title": "Export",
|
"topics.export.title": "Export",
|
||||||
"topics.export.word": "Export as Word",
|
"topics.export.word": "Export as Word",
|
||||||
"topics.export.yuque": "Export to Yuque",
|
"topics.export.yuque": "Export to Yuque",
|
||||||
@ -796,15 +806,6 @@
|
|||||||
"notion.split_size_help": "Recommended: 90 for Free plan, 24990 for Plus plan, default is 90",
|
"notion.split_size_help": "Recommended: 90 for Free plan, 24990 for Plus plan, default is 90",
|
||||||
"notion.split_size_placeholder": "Enter block limit per page (default 90)",
|
"notion.split_size_placeholder": "Enter block limit per page (default 90)",
|
||||||
"notion.title": "Notion Configuration",
|
"notion.title": "Notion Configuration",
|
||||||
"obsidian": {
|
|
||||||
"folder": "Folder",
|
|
||||||
"folder_placeholder": "Please enter the folder name",
|
|
||||||
"tags": "Global Tags",
|
|
||||||
"tags_placeholder": "Please enter the tag name, separate multiple tags with commas",
|
|
||||||
"title": "Obsidian Configuration",
|
|
||||||
"vault": "Vault",
|
|
||||||
"vault_placeholder": "Please enter the vault name"
|
|
||||||
},
|
|
||||||
"title": "Data Settings",
|
"title": "Data Settings",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"autoSync": "Auto Backup",
|
"autoSync": "Auto Backup",
|
||||||
@ -850,6 +851,15 @@
|
|||||||
"token": "Yuque Token",
|
"token": "Yuque Token",
|
||||||
"token_placeholder": "Please enter the Yuque Token"
|
"token_placeholder": "Please enter the Yuque Token"
|
||||||
},
|
},
|
||||||
|
"obsidian": {
|
||||||
|
"title": "Obsidian Configuration",
|
||||||
|
"default_vault": "Default Obsidian Vault",
|
||||||
|
"default_vault_placeholder": "Please select the default Obsidian vault",
|
||||||
|
"default_vault_loading": "Loading Obsidian vault...",
|
||||||
|
"default_vault_no_vaults": "No Obsidian vaults found",
|
||||||
|
"default_vault_fetch_error": "Failed to fetch Obsidian vault",
|
||||||
|
"default_vault_export_failed": "Export failed"
|
||||||
|
},
|
||||||
"siyuan": {
|
"siyuan": {
|
||||||
"title": "Siyuan Note Configuration",
|
"title": "Siyuan Note Configuration",
|
||||||
"api_url": "Siyuan Note API URL",
|
"api_url": "Siyuan Note API URL",
|
||||||
|
|||||||
@ -180,13 +180,16 @@
|
|||||||
"topics.export.md": "Markdownとしてエクスポート",
|
"topics.export.md": "Markdownとしてエクスポート",
|
||||||
"topics.export.notion": "Notion にエクスポート",
|
"topics.export.notion": "Notion にエクスポート",
|
||||||
"topics.export.obsidian": "Obsidian にエクスポート",
|
"topics.export.obsidian": "Obsidian にエクスポート",
|
||||||
|
"topics.export.obsidian_vault": "保管庫",
|
||||||
|
"topics.export.obsidian_vault_placeholder": "保管庫名を選択してください",
|
||||||
|
"topics.export.obsidian_path": "パス",
|
||||||
|
"topics.export.obsidian_path_placeholder": "パスを選択してください",
|
||||||
"topics.export.obsidian_atributes": "ノートの属性を設定",
|
"topics.export.obsidian_atributes": "ノートの属性を設定",
|
||||||
"topics.export.obsidian_btn": "確定",
|
"topics.export.obsidian_btn": "確定",
|
||||||
"topics.export.obsidian_created": "作成日時",
|
"topics.export.obsidian_created": "作成日時",
|
||||||
"topics.export.obsidian_created_placeholder": "作成日時を選択してください",
|
"topics.export.obsidian_created_placeholder": "作成日時を選択してください",
|
||||||
"topics.export.obsidian_export_failed": "エクスポート失敗",
|
"topics.export.obsidian_export_failed": "エクスポート失敗",
|
||||||
"topics.export.obsidian_export_success": "エクスポート成功",
|
"topics.export.obsidian_export_success": "エクスポート成功",
|
||||||
"topics.export.obsidian_not_configured": "Obsidian 未設定",
|
|
||||||
"topics.export.obsidian_operate": "処理方法",
|
"topics.export.obsidian_operate": "処理方法",
|
||||||
"topics.export.obsidian_operate_append": "追加",
|
"topics.export.obsidian_operate_append": "追加",
|
||||||
"topics.export.obsidian_operate_new_or_overwrite": "新規作成(既に存在する場合は上書き)",
|
"topics.export.obsidian_operate_new_or_overwrite": "新規作成(既に存在する場合は上書き)",
|
||||||
@ -199,6 +202,13 @@
|
|||||||
"topics.export.obsidian_title": "タイトル",
|
"topics.export.obsidian_title": "タイトル",
|
||||||
"topics.export.obsidian_title_placeholder": "タイトルを入力してください",
|
"topics.export.obsidian_title_placeholder": "タイトルを入力してください",
|
||||||
"topics.export.obsidian_title_required": "タイトルは空白にできません",
|
"topics.export.obsidian_title_required": "タイトルは空白にできません",
|
||||||
|
"topics.export.obsidian_no_vaults": "Obsidianの保管庫が見つかりません",
|
||||||
|
"topics.export.obsidian_loading": "読み込み中...",
|
||||||
|
"topics.export.obsidian_fetch_error": "Obsidianの保管庫の取得に失敗しました",
|
||||||
|
"topics.export.obsidian_fetch_folders_error": "フォルダ構造の取得に失敗しました",
|
||||||
|
"topics.export.obsidian_no_vault_selected": "保管庫を選択してください",
|
||||||
|
"topics.export.obsidian_select_vault_first": "最初に保管庫を選択してください",
|
||||||
|
"topics.export.obsidian_root_directory": "ルートディレクトリ",
|
||||||
"topics.export.title": "エクスポート",
|
"topics.export.title": "エクスポート",
|
||||||
"topics.export.word": "Wordとしてエクスポート",
|
"topics.export.word": "Wordとしてエクスポート",
|
||||||
"topics.export.yuque": "語雀にエクスポート",
|
"topics.export.yuque": "語雀にエクスポート",
|
||||||
@ -796,15 +806,6 @@
|
|||||||
"notion.split_size_help": "Notion無料版ユーザーは90、有料版ユーザーは24990、デフォルトは90",
|
"notion.split_size_help": "Notion無料版ユーザーは90、有料版ユーザーは24990、デフォルトは90",
|
||||||
"notion.split_size_placeholder": "ページごとのブロック数制限を入力してください(デフォルト90)",
|
"notion.split_size_placeholder": "ページごとのブロック数制限を入力してください(デフォルト90)",
|
||||||
"notion.title": "Notion 設定",
|
"notion.title": "Notion 設定",
|
||||||
"obsidian": {
|
|
||||||
"folder": "フォルダー",
|
|
||||||
"folder_placeholder": "フォルダーの名前を入力してください",
|
|
||||||
"tags": "グローバルタグ",
|
|
||||||
"tags_placeholder": "タグの名前を入力してください。複数のタグは英語のコンマで区切ってください",
|
|
||||||
"title": "Obsidian の設定",
|
|
||||||
"vault": "ヴォールト(保管庫)",
|
|
||||||
"vault_placeholder": "保管庫の名前を入力してください"
|
|
||||||
},
|
|
||||||
"title": "データ設定",
|
"title": "データ設定",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"autoSync": "自動バックアップ",
|
"autoSync": "自動バックアップ",
|
||||||
@ -850,6 +851,15 @@
|
|||||||
"token": "Yuqueトークン",
|
"token": "Yuqueトークン",
|
||||||
"token_placeholder": "Yuqueトークンを入力してください"
|
"token_placeholder": "Yuqueトークンを入力してください"
|
||||||
},
|
},
|
||||||
|
"obsidian": {
|
||||||
|
"title": "Obsidian 設定",
|
||||||
|
"default_vault": "デフォルトの Obsidian 保管庫",
|
||||||
|
"default_vault_placeholder": "デフォルトの Obsidian 保管庫を選択してください",
|
||||||
|
"default_vault_loading": "Obsidian 保管庫を取得中...",
|
||||||
|
"default_vault_no_vaults": "Obsidian 保管庫が見つかりません",
|
||||||
|
"default_vault_fetch_error": "Obsidian 保管庫の取得に失敗しました",
|
||||||
|
"default_vault_export_failed": "エクスポートに失敗しました"
|
||||||
|
},
|
||||||
"siyuan": {
|
"siyuan": {
|
||||||
"title": "思源ノート設定",
|
"title": "思源ノート設定",
|
||||||
"api_url": "APIアドレス",
|
"api_url": "APIアドレス",
|
||||||
|
|||||||
@ -180,13 +180,16 @@
|
|||||||
"topics.export.md": "Экспорт как markdown",
|
"topics.export.md": "Экспорт как markdown",
|
||||||
"topics.export.notion": "Экспорт в Notion",
|
"topics.export.notion": "Экспорт в Notion",
|
||||||
"topics.export.obsidian": "Экспорт в Obsidian",
|
"topics.export.obsidian": "Экспорт в Obsidian",
|
||||||
|
"topics.export.obsidian_vault": "Хранилище",
|
||||||
|
"topics.export.obsidian_vault_placeholder": "Выберите имя хранилища",
|
||||||
|
"topics.export.obsidian_path": "Путь",
|
||||||
|
"topics.export.obsidian_path_placeholder": "Выберите путь",
|
||||||
"topics.export.obsidian_atributes": "Настроить атрибуты заметки",
|
"topics.export.obsidian_atributes": "Настроить атрибуты заметки",
|
||||||
"topics.export.obsidian_btn": "Подтвердить",
|
"topics.export.obsidian_btn": "Подтвердить",
|
||||||
"topics.export.obsidian_created": "Дата создания",
|
"topics.export.obsidian_created": "Дата создания",
|
||||||
"topics.export.obsidian_created_placeholder": "Пожалуйста, выберите дату создания",
|
"topics.export.obsidian_created_placeholder": "Пожалуйста, выберите дату создания",
|
||||||
"topics.export.obsidian_export_failed": "Экспорт не удалось",
|
"topics.export.obsidian_export_failed": "Экспорт не удалось",
|
||||||
"topics.export.obsidian_export_success": "Экспорт успешно завершен",
|
"topics.export.obsidian_export_success": "Экспорт успешно завершен",
|
||||||
"topics.export.obsidian_not_configured": "Obsidian не настроен",
|
|
||||||
"topics.export.obsidian_operate": "Метод обработки",
|
"topics.export.obsidian_operate": "Метод обработки",
|
||||||
"topics.export.obsidian_operate_append": "Добавить в конец",
|
"topics.export.obsidian_operate_append": "Добавить в конец",
|
||||||
"topics.export.obsidian_operate_new_or_overwrite": "Создать новый (перезаписать, если уже существует)",
|
"topics.export.obsidian_operate_new_or_overwrite": "Создать новый (перезаписать, если уже существует)",
|
||||||
@ -199,6 +202,13 @@
|
|||||||
"topics.export.obsidian_title": "Заголовок",
|
"topics.export.obsidian_title": "Заголовок",
|
||||||
"topics.export.obsidian_title_placeholder": "Пожалуйста, введите заголовок",
|
"topics.export.obsidian_title_placeholder": "Пожалуйста, введите заголовок",
|
||||||
"topics.export.obsidian_title_required": "Заголовок не может быть пустым",
|
"topics.export.obsidian_title_required": "Заголовок не может быть пустым",
|
||||||
|
"topics.export.obsidian_no_vaults": "Хранилища Obsidian не найдены",
|
||||||
|
"topics.export.obsidian_loading": "Загрузка...",
|
||||||
|
"topics.export.obsidian_fetch_error": "Не удалось получить хранилища Obsidian",
|
||||||
|
"topics.export.obsidian_fetch_folders_error": "Не удалось получить структуру папок",
|
||||||
|
"topics.export.obsidian_no_vault_selected": "Пожалуйста, сначала выберите хранилище",
|
||||||
|
"topics.export.obsidian_select_vault_first": "Пожалуйста, сначала выберите хранилище",
|
||||||
|
"topics.export.obsidian_root_directory": "Корневая директория",
|
||||||
"topics.export.title": "Экспорт",
|
"topics.export.title": "Экспорт",
|
||||||
"topics.export.word": "Экспорт как Word",
|
"topics.export.word": "Экспорт как Word",
|
||||||
"topics.export.yuque": "Экспорт в Yuque",
|
"topics.export.yuque": "Экспорт в Yuque",
|
||||||
@ -796,15 +806,6 @@
|
|||||||
"notion.split_size_help": "Рекомендуется 90 для пользователей бесплатной версии Notion, 24990 для пользователей премиум-версии, значение по умолчанию — 90",
|
"notion.split_size_help": "Рекомендуется 90 для пользователей бесплатной версии Notion, 24990 для пользователей премиум-версии, значение по умолчанию — 90",
|
||||||
"notion.split_size_placeholder": "Введите ограничение количества блоков на странице (по умолчанию 90)",
|
"notion.split_size_placeholder": "Введите ограничение количества блоков на странице (по умолчанию 90)",
|
||||||
"notion.title": "Настройки Notion",
|
"notion.title": "Настройки Notion",
|
||||||
"obsidian": {
|
|
||||||
"folder": "Папка",
|
|
||||||
"folder_placeholder": "Пожалуйста, введите имя папки",
|
|
||||||
"tags": "Глобальные Теги",
|
|
||||||
"tags_placeholder": "Пожалуйста, введите имена тегов. Разделяйте несколько тегов запятыми на английском языке. В Obsidian нельзя использовать только цифры.",
|
|
||||||
"title": "Конфигурация Obsidian",
|
|
||||||
"vault": "Хранилище",
|
|
||||||
"vault_placeholder": "Пожалуйста, введите имя хранилища"
|
|
||||||
},
|
|
||||||
"title": "Настройки данных",
|
"title": "Настройки данных",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"autoSync": "Автоматическое резервное копирование",
|
"autoSync": "Автоматическое резервное копирование",
|
||||||
@ -850,6 +851,15 @@
|
|||||||
"token": "Токен Yuque",
|
"token": "Токен Yuque",
|
||||||
"token_placeholder": "Введите токен Yuque"
|
"token_placeholder": "Введите токен Yuque"
|
||||||
},
|
},
|
||||||
|
"obsidian": {
|
||||||
|
"title": "Настройки Obsidian",
|
||||||
|
"default_vault": "Хранилище Obsidian по умолчанию",
|
||||||
|
"default_vault_placeholder": "Выберите хранилище Obsidian по умолчанию",
|
||||||
|
"default_vault_loading": "Получение хранилищ Obsidian...",
|
||||||
|
"default_vault_no_vaults": "Хранилища Obsidian не найдены",
|
||||||
|
"default_vault_fetch_error": "Не удалось получить хранилища Obsidian",
|
||||||
|
"default_vault_export_failed": "Ошибка экспорта"
|
||||||
|
},
|
||||||
"siyuan": {
|
"siyuan": {
|
||||||
"title": "Конфигурация SiYuan Note",
|
"title": "Конфигурация SiYuan Note",
|
||||||
"api_url": "API адрес",
|
"api_url": "API адрес",
|
||||||
|
|||||||
@ -180,13 +180,16 @@
|
|||||||
"topics.export.md": "导出为 Markdown",
|
"topics.export.md": "导出为 Markdown",
|
||||||
"topics.export.notion": "导出到 Notion",
|
"topics.export.notion": "导出到 Notion",
|
||||||
"topics.export.obsidian": "导出到 Obsidian",
|
"topics.export.obsidian": "导出到 Obsidian",
|
||||||
|
"topics.export.obsidian_vault": "保管库",
|
||||||
|
"topics.export.obsidian_vault_placeholder": "请选择保管库名称",
|
||||||
|
"topics.export.obsidian_path": "路径",
|
||||||
|
"topics.export.obsidian_path_placeholder": "请选择路径",
|
||||||
"topics.export.obsidian_atributes": "配置笔记属性",
|
"topics.export.obsidian_atributes": "配置笔记属性",
|
||||||
"topics.export.obsidian_btn": "确定",
|
"topics.export.obsidian_btn": "确定",
|
||||||
"topics.export.obsidian_created": "创建时间",
|
"topics.export.obsidian_created": "创建时间",
|
||||||
"topics.export.obsidian_created_placeholder": "请选择创建时间",
|
"topics.export.obsidian_created_placeholder": "请选择创建时间",
|
||||||
"topics.export.obsidian_export_failed": "导出失败",
|
"topics.export.obsidian_export_failed": "导出到Obsidian失败",
|
||||||
"topics.export.obsidian_export_success": "导出成功",
|
"topics.export.obsidian_export_success": "导出到Obsidian成功",
|
||||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
|
||||||
"topics.export.obsidian_operate": "处理方式",
|
"topics.export.obsidian_operate": "处理方式",
|
||||||
"topics.export.obsidian_operate_append": "追加",
|
"topics.export.obsidian_operate_append": "追加",
|
||||||
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆盖)",
|
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆盖)",
|
||||||
@ -199,6 +202,13 @@
|
|||||||
"topics.export.obsidian_title": "标题",
|
"topics.export.obsidian_title": "标题",
|
||||||
"topics.export.obsidian_title_placeholder": "请输入标题",
|
"topics.export.obsidian_title_placeholder": "请输入标题",
|
||||||
"topics.export.obsidian_title_required": "标题不能为空",
|
"topics.export.obsidian_title_required": "标题不能为空",
|
||||||
|
"topics.export.obsidian_no_vaults": "未找到Obsidian保管库",
|
||||||
|
"topics.export.obsidian_loading": "加载中...",
|
||||||
|
"topics.export.obsidian_fetch_error": "获取Obsidian保管库失败",
|
||||||
|
"topics.export.obsidian_fetch_folders_error": "获取文件夹结构失败",
|
||||||
|
"topics.export.obsidian_no_vault_selected": "请先选择一个保管库",
|
||||||
|
"topics.export.obsidian_select_vault_first": "请先选择保管库",
|
||||||
|
"topics.export.obsidian_root_directory": "根目录",
|
||||||
"topics.export.title": "导出",
|
"topics.export.title": "导出",
|
||||||
"topics.export.word": "导出为 Word",
|
"topics.export.word": "导出为 Word",
|
||||||
"topics.export.yuque": "导出到语雀",
|
"topics.export.yuque": "导出到语雀",
|
||||||
@ -796,15 +806,6 @@
|
|||||||
"notion.split_size_help": "Notion免费版用户建议设置为90,高级版用户建议设置为24990,默认值为90",
|
"notion.split_size_help": "Notion免费版用户建议设置为90,高级版用户建议设置为24990,默认值为90",
|
||||||
"notion.split_size_placeholder": "请输入每页块数限制(默认90)",
|
"notion.split_size_placeholder": "请输入每页块数限制(默认90)",
|
||||||
"notion.title": "Notion 配置",
|
"notion.title": "Notion 配置",
|
||||||
"obsidian": {
|
|
||||||
"folder": "文件夹",
|
|
||||||
"folder_placeholder": "请输入文件夹名称",
|
|
||||||
"tags": "全局标签",
|
|
||||||
"tags_placeholder": "请输入标签名称, 多个标签用英文逗号分隔",
|
|
||||||
"title": "Obsidian 配置",
|
|
||||||
"vault": "保管库",
|
|
||||||
"vault_placeholder": "请输入保管库名称"
|
|
||||||
},
|
|
||||||
"title": "数据设置",
|
"title": "数据设置",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"autoSync": "自动备份",
|
"autoSync": "自动备份",
|
||||||
@ -850,6 +851,15 @@
|
|||||||
"token": "语雀 Token",
|
"token": "语雀 Token",
|
||||||
"token_placeholder": "请输入语雀Token"
|
"token_placeholder": "请输入语雀Token"
|
||||||
},
|
},
|
||||||
|
"obsidian": {
|
||||||
|
"title": "Obsidian 配置",
|
||||||
|
"default_vault": "默认 Obsidian 仓库",
|
||||||
|
"default_vault_placeholder": "请选择默认 Obsidian 仓库",
|
||||||
|
"default_vault_loading": "正在获取 Obsidian 仓库...",
|
||||||
|
"default_vault_no_vaults": "未找到 Obsidian 仓库",
|
||||||
|
"default_vault_fetch_error": "获取 Obsidian 仓库失败",
|
||||||
|
"default_vault_export_failed": "导出失败"
|
||||||
|
},
|
||||||
"siyuan": {
|
"siyuan": {
|
||||||
"title": "思源笔记配置",
|
"title": "思源笔记配置",
|
||||||
"api_url": "API地址",
|
"api_url": "API地址",
|
||||||
|
|||||||
@ -180,13 +180,16 @@
|
|||||||
"topics.export.md": "匯出為 Markdown",
|
"topics.export.md": "匯出為 Markdown",
|
||||||
"topics.export.notion": "匯出到 Notion",
|
"topics.export.notion": "匯出到 Notion",
|
||||||
"topics.export.obsidian": "匯出到 Obsidian",
|
"topics.export.obsidian": "匯出到 Obsidian",
|
||||||
|
"topics.export.obsidian_vault": "保管庫",
|
||||||
|
"topics.export.obsidian_vault_placeholder": "請選擇保管庫名稱",
|
||||||
|
"topics.export.obsidian_path": "路徑",
|
||||||
|
"topics.export.obsidian_path_placeholder": "請選擇路徑",
|
||||||
"topics.export.obsidian_atributes": "配置筆記屬性",
|
"topics.export.obsidian_atributes": "配置筆記屬性",
|
||||||
"topics.export.obsidian_btn": "確定",
|
"topics.export.obsidian_btn": "確定",
|
||||||
"topics.export.obsidian_created": "建立時間",
|
"topics.export.obsidian_created": "建立時間",
|
||||||
"topics.export.obsidian_created_placeholder": "請選擇建立時間",
|
"topics.export.obsidian_created_placeholder": "請選擇建立時間",
|
||||||
"topics.export.obsidian_export_failed": "匯出失敗",
|
"topics.export.obsidian_export_failed": "匯出失敗",
|
||||||
"topics.export.obsidian_export_success": "匯出成功",
|
"topics.export.obsidian_export_success": "匯出成功",
|
||||||
"topics.export.obsidian_not_configured": "Obsidian 未配置",
|
|
||||||
"topics.export.obsidian_operate": "處理方式",
|
"topics.export.obsidian_operate": "處理方式",
|
||||||
"topics.export.obsidian_operate_append": "追加",
|
"topics.export.obsidian_operate_append": "追加",
|
||||||
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆蓋)",
|
"topics.export.obsidian_operate_new_or_overwrite": "新建(如果存在就覆蓋)",
|
||||||
@ -199,6 +202,13 @@
|
|||||||
"topics.export.obsidian_title": "標題",
|
"topics.export.obsidian_title": "標題",
|
||||||
"topics.export.obsidian_title_placeholder": "請輸入標題",
|
"topics.export.obsidian_title_placeholder": "請輸入標題",
|
||||||
"topics.export.obsidian_title_required": "標題不能為空",
|
"topics.export.obsidian_title_required": "標題不能為空",
|
||||||
|
"topics.export.obsidian_no_vaults": "未找到Obsidian保管庫",
|
||||||
|
"topics.export.obsidian_loading": "加載中...",
|
||||||
|
"topics.export.obsidian_fetch_error": "獲取Obsidian保管庫失敗",
|
||||||
|
"topics.export.obsidian_fetch_folders_error": "獲取文件夾結構失敗",
|
||||||
|
"topics.export.obsidian_no_vault_selected": "請先選擇一個保管庫",
|
||||||
|
"topics.export.obsidian_select_vault_first": "請先選擇保管庫",
|
||||||
|
"topics.export.obsidian_root_directory": "根目錄",
|
||||||
"topics.export.title": "匯出",
|
"topics.export.title": "匯出",
|
||||||
"topics.export.word": "匯出為 Word",
|
"topics.export.word": "匯出為 Word",
|
||||||
"topics.export.yuque": "匯出到語雀",
|
"topics.export.yuque": "匯出到語雀",
|
||||||
@ -796,15 +806,6 @@
|
|||||||
"notion.split_size_help": "Notion 免費版使用者建議設定為 90,進階版使用者建議設定為 24990,預設值為 90",
|
"notion.split_size_help": "Notion 免費版使用者建議設定為 90,進階版使用者建議設定為 24990,預設值為 90",
|
||||||
"notion.split_size_placeholder": "請輸入每頁塊數限制 (預設 90)",
|
"notion.split_size_placeholder": "請輸入每頁塊數限制 (預設 90)",
|
||||||
"notion.title": "Notion 設定",
|
"notion.title": "Notion 設定",
|
||||||
"obsidian": {
|
|
||||||
"folder": "資料夾",
|
|
||||||
"folder_placeholder": "請輸入資料夾名稱",
|
|
||||||
"tags": "全域標籤",
|
|
||||||
"tags_placeholder": "請輸入標籤名稱,多個標籤用英文逗號分隔。Obsidian 不可用純數字。",
|
|
||||||
"title": "Obsidian 設定",
|
|
||||||
"vault": "保險庫",
|
|
||||||
"vault_placeholder": "請輸入保險庫名稱"
|
|
||||||
},
|
|
||||||
"title": "資料設定",
|
"title": "資料設定",
|
||||||
"webdav": {
|
"webdav": {
|
||||||
"autoSync": "自動備份",
|
"autoSync": "自動備份",
|
||||||
@ -850,6 +851,15 @@
|
|||||||
"token": "語雀 Token",
|
"token": "語雀 Token",
|
||||||
"token_placeholder": "請輸入語雀 Token"
|
"token_placeholder": "請輸入語雀 Token"
|
||||||
},
|
},
|
||||||
|
"obsidian": {
|
||||||
|
"title": "Obsidian 設定",
|
||||||
|
"default_vault": "預設 Obsidian 倉庫",
|
||||||
|
"default_vault_placeholder": "請選擇預設 Obsidian 倉庫",
|
||||||
|
"default_vault_loading": "正在獲取 Obsidian 倉庫...",
|
||||||
|
"default_vault_no_vaults": "未找到 Obsidian 倉庫",
|
||||||
|
"default_vault_fetch_error": "獲取 Obsidian 倉庫失敗",
|
||||||
|
"default_vault_export_failed": "匯出失敗"
|
||||||
|
},
|
||||||
"siyuan": {
|
"siyuan": {
|
||||||
"title": "思源筆記配置",
|
"title": "思源筆記配置",
|
||||||
"api_url": "API地址",
|
"api_url": "API地址",
|
||||||
|
|||||||
@ -37,7 +37,7 @@ const DataSettings: FC = () => {
|
|||||||
const [appInfo, setAppInfo] = useState<AppInfo>()
|
const [appInfo, setAppInfo] = useState<AppInfo>()
|
||||||
const { size, removeAllFiles } = useKnowledgeFiles()
|
const { size, removeAllFiles } = useKnowledgeFiles()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const [menu, setMenu] = useState<string>('data')
|
const [menu, setMenu] = useState<string>('common')
|
||||||
|
|
||||||
//joplin icon needs to be updated into iconfont
|
//joplin icon needs to be updated into iconfont
|
||||||
const JoplinIcon = () => (
|
const JoplinIcon = () => (
|
||||||
@ -77,17 +77,17 @@ const DataSettings: FC = () => {
|
|||||||
title: 'settings.data.yuque.title',
|
title: 'settings.data.yuque.title',
|
||||||
icon: <YuqueOutlined style={{ fontSize: 16 }} />
|
icon: <YuqueOutlined style={{ fontSize: 16 }} />
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'obsidian',
|
|
||||||
title: 'settings.data.obsidian.title',
|
|
||||||
icon: <i className="iconfont icon-obsidian" />
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'joplin',
|
key: 'joplin',
|
||||||
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: 'obsidian',
|
||||||
|
title: 'settings.data.obsidian.title',
|
||||||
|
icon: <i className="iconfont icon-obsidian" />
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'siyuan',
|
key: 'siyuan',
|
||||||
title: 'settings.data.siyuan.title',
|
title: 'settings.data.siyuan.title',
|
||||||
@ -230,8 +230,8 @@ const DataSettings: FC = () => {
|
|||||||
{menu === 'markdown_export' && <MarkdownExportSettings />}
|
{menu === 'markdown_export' && <MarkdownExportSettings />}
|
||||||
{menu === 'notion' && <NotionSettings />}
|
{menu === 'notion' && <NotionSettings />}
|
||||||
{menu === 'yuque' && <YuqueSettings />}
|
{menu === 'yuque' && <YuqueSettings />}
|
||||||
{menu === 'obsidian' && <ObsidianSettings />}
|
|
||||||
{menu === 'joplin' && <JoplinSettings />}
|
{menu === 'joplin' && <JoplinSettings />}
|
||||||
|
{menu === 'obsidian' && <ObsidianSettings />}
|
||||||
{menu === 'siyuan' && <SiyuanSettings />}
|
{menu === 'siyuan' && <SiyuanSettings />}
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@ -1,95 +1,90 @@
|
|||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { RootState, useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setObsidianFolder, setObsidianTages, setObsidianValut } from '@renderer/store/settings'
|
import { setDefaultObsidianVault } from '@renderer/store/settings'
|
||||||
import Input from 'antd/es/input/Input'
|
import { Empty, Select, Spin } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
|
|
||||||
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
import { SettingDivider, SettingGroup, SettingRow, SettingRowTitle, SettingTitle } from '..'
|
||||||
|
|
||||||
|
const { Option } = Select
|
||||||
|
|
||||||
const ObsidianSettings: FC = () => {
|
const ObsidianSettings: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { defaultObsidianVault } = useSettings()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
// const obsidianApiKey = useSelector((state: RootState) => state.settings.obsidianApiKey)
|
const [vaults, setVaults] = useState<Array<{ path: string; name: string }>>([])
|
||||||
// const obsidianUrl = useSelector((state: RootState) => state.settings.obsidianUrl)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
const obsidianVault = useSelector((state: RootState) => state.settings.obsidianValut)
|
// 组件加载时获取Vault列表
|
||||||
const obsidianFolder = useSelector((state: RootState) => state.settings.obsidianFolder)
|
useEffect(() => {
|
||||||
const obsidianTags = useSelector((state: RootState) => state.settings.obsidianTages)
|
const fetchVaults = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
setError(null)
|
||||||
|
const vaultsData = await window.obsidian.getVaults()
|
||||||
|
|
||||||
const handleObsidianVaultChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
if (vaultsData.length === 0) {
|
||||||
dispatch(setObsidianValut(e.target.value))
|
setError(t('settings.data.obsidian.default_vault_no_vaults'))
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleObsidianFolderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
setVaults(vaultsData)
|
||||||
dispatch(setObsidianFolder(e.target.value))
|
|
||||||
|
// 如果没有设置默认vault,则选择第一个
|
||||||
|
if (!defaultObsidianVault && vaultsData.length > 0) {
|
||||||
|
dispatch(setDefaultObsidianVault(vaultsData[0].name))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取Obsidian Vault失败:', error)
|
||||||
|
setError(t('settings.data.obsidian.default_vault_fetch_error'))
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleObsidianVaultBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
fetchVaults()
|
||||||
dispatch(setObsidianValut(e.target.value))
|
}, [dispatch, defaultObsidianVault, t])
|
||||||
}
|
|
||||||
|
|
||||||
const handleObsidianFolderBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
const handleChange = (value: string) => {
|
||||||
dispatch(setObsidianFolder(e.target.value))
|
dispatch(setDefaultObsidianVault(value))
|
||||||
}
|
|
||||||
|
|
||||||
const handleObsidianTagsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
dispatch(setObsidianTages(e.target.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleObsidianTagsBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
||||||
dispatch(setObsidianTages(e.target.value))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingGroup theme={theme}>
|
<SettingGroup>
|
||||||
<SettingTitle>{t('settings.data.obsidian.title')}</SettingTitle>
|
<SettingTitle>{t('settings.data.obsidian.title')}</SettingTitle>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitle>{t('settings.data.obsidian.vault')}</SettingRowTitle>
|
<SettingRowTitle>{t('settings.data.obsidian.default_vault')}</SettingRowTitle>
|
||||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
<HStack gap="5px">
|
||||||
<Input
|
<Spin spinning={loading} size="small">
|
||||||
type="text"
|
{vaults.length > 0 ? (
|
||||||
value={obsidianVault || ''}
|
<Select
|
||||||
onChange={handleObsidianVaultChange}
|
value={defaultObsidianVault || undefined}
|
||||||
onBlur={handleObsidianVaultBlur}
|
onChange={handleChange}
|
||||||
style={{ width: 315 }}
|
placeholder={t('settings.data.obsidian.default_vault_placeholder')}
|
||||||
placeholder={t('settings.data.obsidian.vault_placeholder')}
|
style={{ width: 300 }}>
|
||||||
/>
|
{vaults.map((vault) => (
|
||||||
</HStack>
|
<Option key={vault.name} value={vault.name}>
|
||||||
</SettingRow>
|
{vault.name}
|
||||||
<SettingDivider />
|
</Option>
|
||||||
<SettingRow>
|
))}
|
||||||
<SettingRowTitle style={{ display: 'flex', alignItems: 'center' }}>
|
</Select>
|
||||||
<span>{t('settings.data.obsidian.folder')}</span>
|
) : (
|
||||||
</SettingRowTitle>
|
<Empty
|
||||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
description={
|
||||||
<Input
|
loading
|
||||||
value={obsidianFolder || ''}
|
? t('settings.data.obsidian.default_vault_loading')
|
||||||
onChange={handleObsidianFolderChange}
|
: error || t('settings.data.obsidian.default_vault_no_vaults')
|
||||||
onBlur={handleObsidianFolderBlur}
|
}
|
||||||
style={{ width: 315 }}
|
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||||
placeholder={t('settings.data.obsidian.folder_placeholder')}
|
|
||||||
/>
|
|
||||||
</HStack>
|
|
||||||
</SettingRow>
|
|
||||||
<SettingDivider />
|
|
||||||
<SettingRow>
|
|
||||||
<SettingRowTitle style={{ display: 'flex', alignItems: 'center' }}>
|
|
||||||
<span>{t('settings.data.obsidian.tags')}</span>
|
|
||||||
</SettingRowTitle>
|
|
||||||
<HStack alignItems="center" gap="5px" style={{ width: 315 }}>
|
|
||||||
<Input
|
|
||||||
value={obsidianTags || ''}
|
|
||||||
onChange={handleObsidianTagsChange}
|
|
||||||
onBlur={handleObsidianTagsBlur}
|
|
||||||
style={{ width: 315 }}
|
|
||||||
placeholder={t('settings.data.obsidian.tags_placeholder')}
|
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
</Spin>
|
||||||
</HStack>
|
</HStack>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
</SettingGroup>
|
</SettingGroup>
|
||||||
|
|||||||
@ -90,12 +90,9 @@ export interface SettingsState {
|
|||||||
yuqueToken: string | null
|
yuqueToken: string | null
|
||||||
yuqueUrl: string | null
|
yuqueUrl: string | null
|
||||||
yuqueRepoId: string | null
|
yuqueRepoId: string | null
|
||||||
//obsidian settings obsidianVault, obisidanFolder
|
|
||||||
obsidianValut: string | null
|
|
||||||
obsidianFolder: string | null
|
|
||||||
obsidianTages: string | null
|
|
||||||
joplinToken: string | null
|
joplinToken: string | null
|
||||||
joplinUrl: string | null
|
joplinUrl: string | null
|
||||||
|
defaultObsidianVault: string | null
|
||||||
// 思源笔记配置
|
// 思源笔记配置
|
||||||
siyuanApiUrl: string | null
|
siyuanApiUrl: string | null
|
||||||
siyuanToken: string | null
|
siyuanToken: string | null
|
||||||
@ -172,11 +169,9 @@ const initialState: SettingsState = {
|
|||||||
yuqueToken: '',
|
yuqueToken: '',
|
||||||
yuqueUrl: '',
|
yuqueUrl: '',
|
||||||
yuqueRepoId: '',
|
yuqueRepoId: '',
|
||||||
obsidianValut: '',
|
|
||||||
obsidianFolder: '',
|
|
||||||
obsidianTages: '',
|
|
||||||
joplinToken: '',
|
joplinToken: '',
|
||||||
joplinUrl: '',
|
joplinUrl: '',
|
||||||
|
defaultObsidianVault: null,
|
||||||
// 思源笔记配置初始值
|
// 思源笔记配置初始值
|
||||||
siyuanApiUrl: null,
|
siyuanApiUrl: null,
|
||||||
siyuanToken: null,
|
siyuanToken: null,
|
||||||
@ -386,15 +381,6 @@ const settingsSlice = createSlice({
|
|||||||
setYuqueUrl: (state, action: PayloadAction<string>) => {
|
setYuqueUrl: (state, action: PayloadAction<string>) => {
|
||||||
state.yuqueUrl = action.payload
|
state.yuqueUrl = action.payload
|
||||||
},
|
},
|
||||||
setObsidianValut: (state, action: PayloadAction<string>) => {
|
|
||||||
state.obsidianValut = action.payload
|
|
||||||
},
|
|
||||||
setObsidianFolder: (state, action: PayloadAction<string>) => {
|
|
||||||
state.obsidianFolder = action.payload
|
|
||||||
},
|
|
||||||
setObsidianTages: (state, action: PayloadAction<string>) => {
|
|
||||||
state.obsidianTages = action.payload
|
|
||||||
},
|
|
||||||
setJoplinToken: (state, action: PayloadAction<string>) => {
|
setJoplinToken: (state, action: PayloadAction<string>) => {
|
||||||
state.joplinToken = action.payload
|
state.joplinToken = action.payload
|
||||||
},
|
},
|
||||||
@ -415,6 +401,9 @@ const settingsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => {
|
setMessageNavigation: (state, action: PayloadAction<'none' | 'buttons' | 'anchor'>) => {
|
||||||
state.messageNavigation = action.payload
|
state.messageNavigation = action.payload
|
||||||
|
},
|
||||||
|
setDefaultObsidianVault: (state, action: PayloadAction<string>) => {
|
||||||
|
state.defaultObsidianVault = action.payload
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -484,16 +473,14 @@ export const {
|
|||||||
setYuqueToken,
|
setYuqueToken,
|
||||||
setYuqueRepoId,
|
setYuqueRepoId,
|
||||||
setYuqueUrl,
|
setYuqueUrl,
|
||||||
setObsidianValut,
|
|
||||||
setObsidianFolder,
|
|
||||||
setObsidianTages,
|
|
||||||
setJoplinToken,
|
setJoplinToken,
|
||||||
setJoplinUrl,
|
setJoplinUrl,
|
||||||
|
setMessageNavigation,
|
||||||
|
setDefaultObsidianVault,
|
||||||
setSiyuanApiUrl,
|
setSiyuanApiUrl,
|
||||||
setSiyuanToken,
|
setSiyuanToken,
|
||||||
setSiyuanBoxId,
|
setSiyuanBoxId,
|
||||||
setSiyuanRootPath,
|
setSiyuanRootPath
|
||||||
setMessageNavigation
|
|
||||||
} = settingsSlice.actions
|
} = settingsSlice.actions
|
||||||
|
|
||||||
export default settingsSlice.reducer
|
export default settingsSlice.reducer
|
||||||
|
|||||||
9
src/renderer/src/types/electron.d.ts
vendored
Normal file
9
src/renderer/src/types/electron.d.ts
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
interface ObsidianAPI {
|
||||||
|
getVaults: () => Promise<Array<{ path: string; name: string }>>
|
||||||
|
getFiles: (vaultName: string) => Promise<Array<{ path: string; type: 'folder' | 'markdown'; name: string }>>
|
||||||
|
getFolders: (vaultName: string) => Promise<Array<{ path: string; type: 'folder' | 'markdown'; name: string }>>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
obsidian: ObsidianAPI
|
||||||
|
}
|
||||||
@ -326,31 +326,49 @@ export const exportMarkdownToYuque = async (title: string, content: string) => {
|
|||||||
* @param attributes.source 来源
|
* @param attributes.source 来源
|
||||||
* @param attributes.tags 标签
|
* @param attributes.tags 标签
|
||||||
* @param attributes.processingMethod 处理方式
|
* @param attributes.processingMethod 处理方式
|
||||||
|
* @param attributes.folder 选择的文件夹路径或文件路径
|
||||||
|
* @param attributes.vault 选择的Vault名称
|
||||||
*/
|
*/
|
||||||
export const exportMarkdownToObsidian = async (attributes: any) => {
|
export const exportMarkdownToObsidian = async (attributes: any) => {
|
||||||
try {
|
try {
|
||||||
const obsidianValut = store.getState().settings.obsidianValut
|
// 从参数获取Vault名称
|
||||||
const obsidianFolder = store.getState().settings.obsidianFolder
|
const obsidianValut = attributes.vault
|
||||||
|
let obsidianFolder = attributes.folder || ''
|
||||||
|
let isMarkdownFile = false
|
||||||
|
|
||||||
if (!obsidianValut || !obsidianFolder) {
|
if (!obsidianValut) {
|
||||||
window.message.error(i18n.t('chat.topics.export.obsidian_not_configured'))
|
window.message.error(i18n.t('chat.topics.export.obsidian_not_configured'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let path = ''
|
|
||||||
|
|
||||||
if (!attributes.title) {
|
if (!attributes.title) {
|
||||||
window.message.error(i18n.t('chat.topics.export.obsidian_title_required'))
|
window.message.error(i18n.t('chat.topics.export.obsidian_title_required'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//构建保存路径添加以 / 结尾
|
// 检查是否选择了.md文件
|
||||||
if (!obsidianFolder.endsWith('/')) {
|
if (obsidianFolder && obsidianFolder.endsWith('.md')) {
|
||||||
path = obsidianFolder + '/'
|
isMarkdownFile = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filePath = ''
|
||||||
|
|
||||||
|
// 如果是.md文件,直接使用该文件路径
|
||||||
|
if (isMarkdownFile) {
|
||||||
|
filePath = obsidianFolder
|
||||||
|
} else {
|
||||||
|
// 否则构建路径
|
||||||
|
//构建保存路径添加以 / 结尾
|
||||||
|
if (obsidianFolder && !obsidianFolder.endsWith('/')) {
|
||||||
|
obsidianFolder = obsidianFolder + '/'
|
||||||
|
}
|
||||||
|
|
||||||
//构建文件名
|
//构建文件名
|
||||||
const fileName = transformObsidianFileName(attributes.title)
|
const fileName = transformObsidianFileName(attributes.title)
|
||||||
|
filePath = obsidianFolder + fileName + '.md'
|
||||||
|
}
|
||||||
|
|
||||||
let obsidianUrl = `obsidian://new?file=${encodeURIComponent(path + fileName)}&vault=${encodeURIComponent(obsidianValut)}&clipboard`
|
let obsidianUrl = `obsidian://new?file=${encodeURIComponent(filePath)}&vault=${encodeURIComponent(obsidianValut)}&clipboard`
|
||||||
|
|
||||||
if (attributes.processingMethod === '3') {
|
if (attributes.processingMethod === '3') {
|
||||||
obsidianUrl += '&overwrite=true'
|
obsidianUrl += '&overwrite=true'
|
||||||
@ -359,6 +377,7 @@ export const exportMarkdownToObsidian = async (attributes: any) => {
|
|||||||
} else if (attributes.processingMethod === '1') {
|
} else if (attributes.processingMethod === '1') {
|
||||||
obsidianUrl += '&append=true'
|
obsidianUrl += '&append=true'
|
||||||
}
|
}
|
||||||
|
|
||||||
window.open(obsidianUrl)
|
window.open(obsidianUrl)
|
||||||
window.message.success(i18n.t('chat.topics.export.obsidian_export_success'))
|
window.message.success(i18n.t('chat.topics.export.obsidian_export_success'))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user