feat: added dataPath for projects
This commit is contained in:
@@ -108,6 +108,8 @@ export const SettingsView: React.FC = () => {
|
||||
// Project settings
|
||||
const [projectName, setProjectName] = useState('');
|
||||
const [projectDescription, setProjectDescription] = useState('');
|
||||
const [projectDataPath, setProjectDataPath] = useState('');
|
||||
const [defaultProjectPath, setDefaultProjectPath] = useState('');
|
||||
|
||||
// Post categories management
|
||||
const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES);
|
||||
@@ -135,6 +137,12 @@ export const SettingsView: React.FC = () => {
|
||||
if (activeProject) {
|
||||
setProjectName(activeProject.name);
|
||||
setProjectDescription(activeProject.description || '');
|
||||
setProjectDataPath(activeProject.dataPath || '');
|
||||
|
||||
// Load the default path for reference
|
||||
window.electronAPI?.app.getDefaultProjectPath(activeProject.id).then(path => {
|
||||
setDefaultProjectPath(path);
|
||||
});
|
||||
}
|
||||
}, [activeProject]);
|
||||
|
||||
@@ -285,10 +293,18 @@ export const SettingsView: React.FC = () => {
|
||||
const updated = await window.electronAPI?.projects.update(activeProject.id, {
|
||||
name: projectName.trim() || activeProject.name,
|
||||
description: projectDescription.trim(),
|
||||
dataPath: projectDataPath.trim() || undefined,
|
||||
});
|
||||
if (updated) {
|
||||
setActiveProject(updated as any);
|
||||
useAppStore.getState().updateProject(activeProject.id, updated as any);
|
||||
|
||||
// Also update project.json to keep dataPath in sync
|
||||
await window.electronAPI?.meta.updateProjectMetadata({
|
||||
name: projectName.trim() || activeProject.name,
|
||||
description: projectDescription.trim(),
|
||||
dataPath: projectDataPath.trim() || undefined,
|
||||
} as any);
|
||||
}
|
||||
showToast.success('Project settings saved');
|
||||
} catch (error) {
|
||||
@@ -297,8 +313,19 @@ export const SettingsView: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleBrowseDataPath = async () => {
|
||||
const selected = await window.electronAPI?.app.selectFolder('Select Project Data Folder');
|
||||
if (selected) {
|
||||
setProjectDataPath(selected);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetDataPath = () => {
|
||||
setProjectDataPath('');
|
||||
};
|
||||
|
||||
// Keywords for each section for search filtering
|
||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site'];
|
||||
const projectKeywords = ['project', 'name', 'description', 'blog', 'site', 'path', 'folder', 'location', 'data'];
|
||||
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
|
||||
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
|
||||
const aiKeywords = ['ai', 'assistant', 'chat', 'model', 'prompt', 'system', 'api', 'key', 'claude', 'gpt', 'opencode'];
|
||||
@@ -341,6 +368,30 @@ export const SettingsView: React.FC = () => {
|
||||
/>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow
|
||||
id="project-datapath"
|
||||
label="Project Data Path"
|
||||
description={`Custom folder for storing posts, media, and metadata. Leave empty to use the default location: ${defaultProjectPath}`}
|
||||
>
|
||||
<div className="setting-input-group">
|
||||
<input
|
||||
id="project-datapath"
|
||||
type="text"
|
||||
placeholder={defaultProjectPath || 'Default location'}
|
||||
value={projectDataPath}
|
||||
onChange={(e) => setProjectDataPath(e.target.value)}
|
||||
/>
|
||||
<button className="secondary" onClick={handleBrowseDataPath} title="Browse...">
|
||||
Browse
|
||||
</button>
|
||||
{projectDataPath && (
|
||||
<button className="secondary" onClick={handleResetDataPath} title="Reset to default">
|
||||
Reset
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</SettingRow>
|
||||
|
||||
<div className="setting-actions">
|
||||
<button className="primary" onClick={handleSaveProject}>
|
||||
Save Project Settings
|
||||
|
||||
@@ -604,7 +604,7 @@ const MediaList: React.FC = () => {
|
||||
{item.mimeType.startsWith('image/') ? (
|
||||
<div className="media-thumbnail">
|
||||
<img
|
||||
src={`bds-media://${item.id}`}
|
||||
src={`bds-thumb://${item.id}`}
|
||||
alt={item.alt || item.originalName}
|
||||
onError={(e) => {
|
||||
const target = e.target as HTMLImageElement;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: file: blob: bds-media:; worker-src 'self' blob:; font-src 'self' data:;" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; style-src 'self' 'unsafe-inline'; img-src 'self' data: file: blob: bds-media: bds-thumb:; worker-src 'self' blob:; font-src 'self' data:;" />
|
||||
<title>Blogging Desktop Server</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface ProjectData {
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
dataPath?: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -322,7 +323,10 @@ export const useAppStore = create<AppState>()(
|
||||
hasMorePosts: hasMore,
|
||||
};
|
||||
}),
|
||||
addPost: (post) => set((state) => ({ posts: [post, ...state.posts], totalPosts: state.totalPosts + 1 })),
|
||||
addPost: (post) => set((state) => {
|
||||
if (state.posts.some(p => p.id === post.id)) return state;
|
||||
return { posts: [post, ...state.posts], totalPosts: state.totalPosts + 1 };
|
||||
}),
|
||||
updatePost: (id, updatedPost) => set((state) => ({
|
||||
posts: state.posts.map((p) => (p.id === id ? { ...p, ...updatedPost } : p)),
|
||||
})),
|
||||
|
||||
6
src/renderer/types/electron.d.ts
vendored
6
src/renderer/types/electron.d.ts
vendored
@@ -3,6 +3,7 @@
|
||||
export interface ProjectMetadata {
|
||||
name: string;
|
||||
description?: string;
|
||||
dataPath?: string;
|
||||
}
|
||||
|
||||
export interface ProjectData {
|
||||
@@ -10,6 +11,7 @@ export interface ProjectData {
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
dataPath?: string;
|
||||
isActive: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -312,6 +314,8 @@ export interface ElectronAPI {
|
||||
getDataPaths: () => Promise<{ database: string; posts: string; media: string }>;
|
||||
openFolder: (folderPath: string) => Promise<string>;
|
||||
showItemInFolder: (itemPath: string) => Promise<void>;
|
||||
selectFolder: (title?: string) => Promise<string | null>;
|
||||
getDefaultProjectPath: (projectId: string) => Promise<string>;
|
||||
};
|
||||
meta: {
|
||||
getTags: () => Promise<string[]>;
|
||||
@@ -323,7 +327,7 @@ export interface ElectronAPI {
|
||||
syncOnStartup: () => Promise<{ tags: string[]; categories: string[]; projectMetadata: ProjectMetadata | null }>;
|
||||
getProjectMetadata: () => Promise<ProjectMetadata | null>;
|
||||
setProjectMetadata: (metadata: { name: string; description?: string }) => Promise<ProjectMetadata | null>;
|
||||
updateProjectMetadata: (updates: { name?: string; description?: string }) => Promise<ProjectMetadata | null>;
|
||||
updateProjectMetadata: (updates: { name?: string; description?: string; dataPath?: string }) => Promise<ProjectMetadata | null>;
|
||||
};
|
||||
tags: {
|
||||
getAll: () => Promise<TagData[]>;
|
||||
|
||||
Reference in New Issue
Block a user