feat: project settings

This commit is contained in:
2026-02-11 06:41:43 +01:00
parent 948873a971
commit 503d895588
4 changed files with 104 additions and 9 deletions

View File

@@ -261,7 +261,9 @@ to easy template creation.
For the styling I want the system to be based on css templates, so that the look can be easily swapped For the styling I want the system to be based on css templates, so that the look can be easily swapped
to the wish of the user. There should be a selection of light and dark themes bundled with the application, to the wish of the user. There should be a selection of light and dark themes bundled with the application,
so that starting is simple. New css templates must be easily integrateable into the application, so that starting is simple. New css templates must be easily integrateable into the application,
maybe even with easy importing from a central repository site or something like that. maybe even with easy importing from a central repository site or something like that. I think Pico CSS is a
good choice, since it sticks to semantic HTML and auto-adapts to light/dark mode settings of users.
I want to work minimalist, but still allow some style influence into the site based on user prefs.
Check the site https://hugo.rfc1437.de/ for its structure, this is the structure of blog I want to be Check the site https://hugo.rfc1437.de/ for its structure, this is the structure of blog I want to be
capable of building with this tooling. So we need templates for overview pages and ways to manage menues capable of building with this tooling. So we need templates for overview pages and ways to manage menues

View File

@@ -185,7 +185,8 @@
} }
.setting-control input[type="text"], .setting-control input[type="text"],
.setting-control input[type="password"] { .setting-control input[type="password"],
.setting-control textarea {
width: 100%; width: 100%;
max-width: 400px; max-width: 400px;
padding: 6px 10px; padding: 6px 10px;
@@ -195,13 +196,21 @@
color: var(--vscode-input-foreground); color: var(--vscode-input-foreground);
border-radius: 4px; border-radius: 4px;
outline: none; outline: none;
font-family: inherit;
} }
.setting-control input:focus { .setting-control textarea {
resize: vertical;
min-height: 60px;
}
.setting-control input:focus,
.setting-control textarea:focus {
border-color: var(--vscode-focusBorder); border-color: var(--vscode-focusBorder);
} }
.setting-control input::placeholder { .setting-control input::placeholder,
.setting-control textarea::placeholder {
color: var(--vscode-input-placeholderForeground); color: var(--vscode-input-placeholderForeground);
} }

View File

@@ -4,7 +4,7 @@ import { showToast } from '../Toast';
import './SettingsView.css'; import './SettingsView.css';
// Export category IDs for sidebar navigation // Export category IDs for sidebar navigation
export type SettingsCategory = 'editor' | 'content' | 'sync' | 'publishing' | 'data'; export type SettingsCategory = 'project' | 'editor' | 'content' | 'sync' | 'publishing' | 'data';
// Scroll to a settings section by category ID // Scroll to a settings section by category ID
export const scrollToSettingsSection = (category: SettingsCategory) => { export const scrollToSettingsSection = (category: SettingsCategory) => {
@@ -102,14 +102,18 @@ const SettingSection: React.FC<{
}; };
export const SettingsView: React.FC = () => { export const SettingsView: React.FC = () => {
const { preferredEditorMode, setPreferredEditorMode, syncConfigured } = useAppStore(); const { preferredEditorMode, setPreferredEditorMode, syncConfigured, activeProject, setActiveProject } = useAppStore();
const [searchQuery, setSearchQuery] = useState(''); const [searchQuery, setSearchQuery] = useState('');
const [credentials, setCredentials] = useState<Credentials>(defaultCredentials); const [credentials, setCredentials] = useState<Credentials>(defaultCredentials);
const [showSecrets, setShowSecrets] = useState(false); const [showSecrets, setShowSecrets] = useState(false);
const [dropboxConfigured, setDropboxConfigured] = useState(false); const [dropboxConfigured, setDropboxConfigured] = useState(false);
const [dropboxLastSync, setDropboxLastSync] = useState<string | null>(null); const [dropboxLastSync, setDropboxLastSync] = useState<string | null>(null);
const contentRef = useRef<HTMLDivElement>(null); const contentRef = useRef<HTMLDivElement>(null);
// Project settings
const [projectName, setProjectName] = useState('');
const [projectDescription, setProjectDescription] = useState('');
// Post categories management // Post categories management
const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES); const [postCategories, setPostCategories] = useState<string[]>(DEFAULT_POST_CATEGORIES);
const [newCategoryInput, setNewCategoryInput] = useState(''); const [newCategoryInput, setNewCategoryInput] = useState('');
@@ -128,6 +132,14 @@ export const SettingsView: React.FC = () => {
); );
}, [searchQuery]); }, [searchQuery]);
// Sync project fields from active project
useEffect(() => {
if (activeProject) {
setProjectName(activeProject.name);
setProjectDescription(activeProject.description || '');
}
}, [activeProject]);
// Load saved credentials and categories // Load saved credentials and categories
useEffect(() => { useEffect(() => {
const loadSettings = async () => { const loadSettings = async () => {
@@ -284,13 +296,76 @@ export const SettingsView: React.FC = () => {
} }
}; };
// Save project settings
const handleSaveProject = async () => {
if (!activeProject) return;
try {
const updated = await window.electronAPI?.projects.update(activeProject.id, {
name: projectName.trim() || activeProject.name,
description: projectDescription.trim(),
});
if (updated) {
setActiveProject(updated as any);
useAppStore.getState().updateProject(activeProject.id, updated as any);
}
showToast.success('Project settings saved');
} catch (error) {
console.error('Failed to save project settings:', error);
showToast.error('Failed to save project settings');
}
};
// Keywords for each section for search filtering // Keywords for each section for search filtering
const projectKeywords = ['project', 'name', 'description', 'blog', 'site'];
const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual']; const editorKeywords = ['editor', 'mode', 'wysiwyg', 'markdown', 'preview', 'visual'];
const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page']; const contentKeywords = ['content', 'categories', 'post', 'article', 'picture', 'aside', 'page'];
const syncKeywords = ['sync', 'turso', 'libsql', 'cloud', 'database', 'dropbox', 'file', 'backup', 'token', 'remote']; const syncKeywords = ['sync', 'turso', 'libsql', 'cloud', 'database', 'dropbox', 'file', 'backup', 'token', 'remote'];
const publishingKeywords = ['publishing', 'ftp', 'ssh', 'deploy', 'server', 'host', 'upload']; const publishingKeywords = ['publishing', 'ftp', 'ssh', 'deploy', 'server', 'host', 'upload'];
const dataKeywords = ['data', 'database', 'rebuild', 'maintenance', 'posts', 'media', 'links', 'folder', 'filesystem']; const dataKeywords = ['data', 'database', 'rebuild', 'maintenance', 'posts', 'media', 'links', 'folder', 'filesystem'];
const renderProjectSettings = () => (
<SettingSection
id="settings-section-project"
title="Project"
description="General settings for the active blog project."
hidden={!sectionHasMatches(projectKeywords)}
>
<SettingRow
id="project-name"
label="Project Name"
description="The display name of your blog project."
>
<input
id="project-name"
type="text"
placeholder="My Blog"
value={projectName}
onChange={(e) => setProjectName(e.target.value)}
/>
</SettingRow>
<SettingRow
id="project-description"
label="Description"
description="A short description of your blog. This can be used in templates and metadata."
>
<textarea
id="project-description"
placeholder="A blog about..."
value={projectDescription}
onChange={(e) => setProjectDescription(e.target.value)}
rows={3}
/>
</SettingRow>
<div className="setting-actions">
<button className="primary" onClick={handleSaveProject}>
Save Project Settings
</button>
</div>
</SettingSection>
);
const renderEditorSettings = () => ( const renderEditorSettings = () => (
<SettingSection <SettingSection
id="settings-section-editor" id="settings-section-editor"
@@ -781,7 +856,8 @@ export const SettingsView: React.FC = () => {
); );
// Check if any results match the search // Check if any results match the search
const hasAnyMatches = !searchQuery || const hasAnyMatches = !searchQuery ||
sectionHasMatches(projectKeywords) ||
sectionHasMatches(editorKeywords) || sectionHasMatches(editorKeywords) ||
sectionHasMatches(contentKeywords) || sectionHasMatches(contentKeywords) ||
sectionHasMatches(syncKeywords) || sectionHasMatches(syncKeywords) ||
@@ -816,6 +892,7 @@ export const SettingsView: React.FC = () => {
<div className="settings-content" ref={contentRef}> <div className="settings-content" ref={contentRef}>
{hasAnyMatches ? ( {hasAnyMatches ? (
<> <>
{renderProjectSettings()}
{renderEditorSettings()} {renderEditorSettings()}
{renderContentSettings()} {renderContentSettings()}
{renderSyncSettings()} {renderSyncSettings()}

View File

@@ -634,7 +634,14 @@ const SettingsNav: React.FC = () => {
</div> </div>
<div className="settings-nav-list"> <div className="settings-nav-list">
<button <button
className={`settings-nav-entry ${activeSection === 'project' ? 'active' : ''}`}
onClick={() => handleNavClick('project')}
>
<span className="settings-nav-entry-icon">📁</span>
<span>Project</span>
</button>
<button
className={`settings-nav-entry ${activeSection === 'editor' ? 'active' : ''}`} className={`settings-nav-entry ${activeSection === 'editor' ? 'active' : ''}`}
onClick={() => handleNavClick('editor')} onClick={() => handleNavClick('editor')}
> >