From f06fc640b6b26e707a52efd4ea4dc71d126f11d1 Mon Sep 17 00:00:00 2001 From: drelich Date: Tue, 17 Mar 2026 20:39:33 +0100 Subject: [PATCH] feat: Add customizable fonts for editor and preview - Added Google Fonts: Source Code Pro, Roboto Mono, Inconsolata (editor) and Merriweather, Crimson Pro, Roboto Serif, Average (preview) - Font face and size selectors in Categories sidebar with polished UI - Editor font applied to markdown textarea - Preview font applied to preview mode and PDF export - Code blocks always render in monospace - Settings persist in localStorage - Fixed textarea height recalculation when switching from preview to edit --- index.html | 6 ++ src/App.tsx | 52 +++++++++++++ src/components/CategoriesSidebar.tsx | 105 ++++++++++++++++++++++++++- src/components/NoteEditor.tsx | 41 ++++++++--- 4 files changed, 192 insertions(+), 12 deletions(-) diff --git a/index.html b/index.html index ff93803..b9eb29e 100644 --- a/index.html +++ b/index.html @@ -5,6 +5,12 @@ Tauri + React + Typescript + + + + + + diff --git a/src/App.tsx b/src/App.tsx index 5e1d040..0d18a69 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,16 +22,36 @@ function App() { const [theme, setTheme] = useState<'light' | 'dark' | 'system'>('system'); const [effectiveTheme, setEffectiveTheme] = useState<'light' | 'dark'>('light'); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + const [editorFont, setEditorFont] = useState('Source Code Pro'); + const [editorFontSize, setEditorFontSize] = useState(14); + const [previewFont, setPreviewFont] = useState('Merriweather'); + const [previewFontSize, setPreviewFontSize] = useState(16); useEffect(() => { const savedServer = localStorage.getItem('serverURL'); const savedUsername = localStorage.getItem('username'); const savedPassword = localStorage.getItem('password'); const savedTheme = localStorage.getItem('theme') as 'light' | 'dark' | 'system' | null; + const savedEditorFont = localStorage.getItem('editorFont'); + const savedPreviewFont = localStorage.getItem('previewFont'); if (savedTheme) { setTheme(savedTheme); } + if (savedEditorFont) { + setEditorFont(savedEditorFont); + } + if (savedPreviewFont) { + setPreviewFont(savedPreviewFont); + } + const savedEditorFontSize = localStorage.getItem('editorFontSize'); + const savedPreviewFontSize = localStorage.getItem('previewFontSize'); + if (savedEditorFontSize) { + setEditorFontSize(parseInt(savedEditorFontSize, 10)); + } + if (savedPreviewFontSize) { + setPreviewFontSize(parseInt(savedPreviewFontSize, 10)); + } if (savedServer && savedUsername && savedPassword) { const apiInstance = new NextcloudAPI({ @@ -117,6 +137,26 @@ function App() { localStorage.setItem('theme', newTheme); }; + const handleEditorFontChange = (font: string) => { + setEditorFont(font); + localStorage.setItem('editorFont', font); + }; + + const handlePreviewFontChange = (font: string) => { + setPreviewFont(font); + localStorage.setItem('previewFont', font); + }; + + const handleEditorFontSizeChange = (size: number) => { + setEditorFontSize(size); + localStorage.setItem('editorFontSize', size.toString()); + }; + + const handlePreviewFontSizeChange = (size: number) => { + setPreviewFontSize(size); + localStorage.setItem('previewFontSize', size.toString()); + }; + const handleCreateNote = async () => { if (!api) return; try { @@ -207,6 +247,14 @@ function App() { onLogout={handleLogout} theme={theme} onThemeChange={handleThemeChange} + editorFont={editorFont} + onEditorFontChange={handleEditorFontChange} + editorFontSize={editorFontSize} + onEditorFontSizeChange={handleEditorFontSizeChange} + previewFont={previewFont} + onPreviewFontChange={handlePreviewFontChange} + previewFontSize={previewFontSize} + onPreviewFontSizeChange={handlePreviewFontSizeChange} /> setIsFocusMode(!isFocusMode)} + editorFont={editorFont} + editorFontSize={editorFontSize} + previewFont={previewFont} + previewFontSize={previewFontSize} /> ); diff --git a/src/components/CategoriesSidebar.tsx b/src/components/CategoriesSidebar.tsx index b6db55c..9ef03b7 100644 --- a/src/components/CategoriesSidebar.tsx +++ b/src/components/CategoriesSidebar.tsx @@ -1,5 +1,20 @@ import { useState, useEffect, useRef } from 'react'; +const EDITOR_FONTS = [ + { name: 'Source Code Pro', value: 'Source Code Pro' }, + { name: 'Roboto Mono', value: 'Roboto Mono' }, + { name: 'Inconsolata', value: 'Inconsolata' }, + { name: 'System Mono', value: 'ui-monospace, monospace' }, +]; + +const PREVIEW_FONTS = [ + { name: 'Merriweather', value: 'Merriweather' }, + { name: 'Crimson Pro', value: 'Crimson Pro' }, + { name: 'Roboto Serif', value: 'Roboto Serif' }, + { name: 'Average', value: 'Average' }, + { name: 'System Serif', value: 'ui-serif, Georgia, serif' }, +]; + interface CategoriesSidebarProps { categories: string[]; selectedCategory: string; @@ -11,6 +26,14 @@ interface CategoriesSidebarProps { onLogout: () => void; theme: 'light' | 'dark' | 'system'; onThemeChange: (theme: 'light' | 'dark' | 'system') => void; + editorFont: string; + onEditorFontChange: (font: string) => void; + editorFontSize: number; + onEditorFontSizeChange: (size: number) => void; + previewFont: string; + onPreviewFontChange: (font: string) => void; + previewFontSize: number; + onPreviewFontSizeChange: (size: number) => void; } export function CategoriesSidebar({ @@ -24,6 +47,14 @@ export function CategoriesSidebar({ onLogout, theme, onThemeChange, + editorFont, + onEditorFontChange, + editorFontSize, + onEditorFontSizeChange, + previewFont, + onPreviewFontChange, + previewFontSize, + onPreviewFontSizeChange, }: CategoriesSidebarProps) { const [isCreating, setIsCreating] = useState(false); const [newCategoryName, setNewCategoryName] = useState(''); @@ -174,7 +205,7 @@ export function CategoriesSidebar({ {/* Theme Toggle */} -
+
Theme
+ + {/* Font Settings */} +
+ Fonts + + {/* Editor Font */} +
+
+ + + + Editor +
+
+ + +
+
+ + {/* Preview Font */} +
+
+ + + + + Preview +
+
+ + +
+
+
); diff --git a/src/components/NoteEditor.tsx b/src/components/NoteEditor.tsx index f158c97..a6a5b6f 100644 --- a/src/components/NoteEditor.tsx +++ b/src/components/NoteEditor.tsx @@ -13,10 +13,14 @@ interface NoteEditorProps { categories: string[]; isFocusMode?: boolean; onToggleFocusMode?: () => void; + editorFont?: string; + editorFontSize?: number; + previewFont?: string; + previewFontSize?: number; } -export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, categories, isFocusMode, onToggleFocusMode }: NoteEditorProps) { +export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, categories, isFocusMode, onToggleFocusMode, editorFont = 'Source Code Pro', editorFontSize = 14, previewFont = 'Merriweather', previewFontSize = 16 }: NoteEditorProps) { const [localTitle, setLocalTitle] = useState(''); const [localContent, setLocalContent] = useState(''); const [localCategory, setLocalCategory] = useState(''); @@ -45,13 +49,18 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, cat return () => document.removeEventListener('keydown', handleKeyDown); }, [isFocusMode, onToggleFocusMode]); - // Auto-resize textarea when content changes + // Auto-resize textarea when content changes, switching from preview to edit, or font size changes useEffect(() => { - if (textareaRef.current) { - textareaRef.current.style.height = 'auto'; - textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + if (textareaRef.current && !isPreviewMode) { + // Use setTimeout to ensure DOM has updated + setTimeout(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = textareaRef.current.scrollHeight + 'px'; + } + }, 0); } - }, [localContent]); + }, [localContent, isPreviewMode, editorFontSize]); useEffect(() => { const loadNewNote = () => { @@ -136,7 +145,7 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, cat try { const container = document.createElement('div'); - container.style.fontFamily = 'Arial, sans-serif'; + container.style.fontFamily = `"${previewFont}", Georgia, serif`; container.style.fontSize = '12px'; container.style.lineHeight = '1.6'; container.style.color = '#000000'; @@ -149,6 +158,7 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, cat titleElement.style.fontWeight = 'bold'; titleElement.style.color = '#000000'; titleElement.style.textAlign = 'center'; + titleElement.style.fontFamily = `"${previewFont}", Georgia, serif`; container.appendChild(titleElement); const contentElement = document.createElement('div'); @@ -158,6 +168,15 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, cat contentElement.style.lineHeight = '1.6'; contentElement.style.color = '#000000'; container.appendChild(contentElement); + + // Apply monospace font to code elements + const style = document.createElement('style'); + style.textContent = ` + code, pre { font-family: "Source Code Pro", ui-monospace, monospace !important; } + pre { background: #f5f5f5; padding: 10px; border-radius: 4px; overflow-x: auto; } + code { background: #f0f0f0; padding: 2px 4px; border-radius: 2px; } + `; + container.appendChild(style); // Create PDF using jsPDF's html() method (like dompdf) const pdf = new jsPDF({ @@ -561,8 +580,8 @@ export function NoteEditor({ note, onUpdateNote, fontSize, onUnsavedChanges, cat
{isPreviewMode ? (