Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion docs/changelogs/cinder-v0.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,4 +107,3 @@ Allow dragging `.md` files from Finder/Explorer into the workspace to import the
### 10. Note Pinning

Allow users to pin frequently accessed notes to the top of the explorer or tabs.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,4 @@
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}
}
310 changes: 187 additions & 123 deletions src/components/features/settings/Settings.tsx
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is settings file modified for note pinning ?

Large diffs are not rendered by default.

18 changes: 14 additions & 4 deletions src/components/layout/MainLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {
setExplorerCollapsed,
sidebarWidth,
setSidebarWidth,
sidebarPosition,
} = useAppStore();

const isResizingRef = useRef(false);
Expand All @@ -32,8 +33,12 @@ export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {

// Calculate width relative to sidebar start
const sidebarRect = sidebarRef.current.getBoundingClientRect();
const sidebarLeft = sidebarRect.left;
const newWidthPx = e.clientX - sidebarLeft;
const sidebarLeft =
sidebarPosition === 'right' ? sidebarRect.right : sidebarRect.left;
const newWidthPx =
sidebarPosition === 'right'
? sidebarRect.right - e.clientX
: e.clientX - sidebarLeft;

const windowWidth = window.innerWidth;
const computedWidth = (newWidthPx / windowWidth) * 100;
Expand Down Expand Up @@ -63,7 +68,7 @@ export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
},
[setSidebarWidth, setExplorerCollapsed]
[setSidebarWidth, setExplorerCollapsed, sidebarPosition]
);

const toggleSidebar = () => {
Expand All @@ -79,7 +84,12 @@ export function MainLayout({ sidebarContent, editorContent }: MainLayoutProps) {
}}
>
{/* Main Content Area */}
<div className="flex-1 flex min-h-0 relative">
<div
className="flex-1 flex min-h-0 relative"
style={{
flexDirection: sidebarPosition === 'right' ? 'row-reverse' : 'row',
}}
>
{/* <ActivityBar /> */}

{/* Sidebar Area */}
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/editor/EditorPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { useAppStore } from '../../../store/useAppStore';
import type { EditorView } from '@codemirror/view';

export function EditorPane() {
const [isPreview, setIsPreview] = useState(false);
const { activeFileId } = useAppStore();
const { activeFileId, defaultView } = useAppStore();
const [isPreview, setIsPreview] = useState(defaultView === 'preview');
const editorViewRef = useRef<EditorView | null>(null);
const [cursorLine, setCursorLine] = useState(1);
const [cursorCol, setCursorCol] = useState(1);
Expand Down
13 changes: 8 additions & 5 deletions src/components/layout/editor/EditorStatusBar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { EditorStatusBarItem } from './EditorStatusBarItem';
import { useAppStore } from '../../../store/useAppStore';
import { getTranslation } from '../../../utils/i18n';

interface EditorStatusBarProps {
isPreview: boolean;
Expand All @@ -12,7 +13,8 @@ export function EditorStatusBar({
cursorLine,
cursorCol,
}: EditorStatusBarProps) {
const { activeFileContent, isAutoSave } = useAppStore();
const { activeFileContent, isAutoSave, language } = useAppStore();
const t = (key: string) => getTranslation(language, key);

// Calculate lines and words
const lines = activeFileContent ? activeFileContent.split('\n').length : 0;
Expand All @@ -24,10 +26,11 @@ export function EditorStatusBar({
: 0;

// Determine mode
const mode = isPreview ? 'Preview' : 'Editing';
// Determine mode
const mode = isPreview ? t('preview') : t('editing');

// Save mode text
const saveMode = isAutoSave ? 'Auto Save' : 'Manual';
const saveMode = isAutoSave ? t('autoSave') : t('manual');

return (
<div
Expand Down Expand Up @@ -85,15 +88,15 @@ export function EditorStatusBar({
style={{ color: 'var(--text-secondary)' }}
className="font-normal opacity-50"
>
{lines} lines
{lines} {t('lines')}
</span>
</EditorStatusBarItem>
<EditorStatusBarItem>
<span
style={{ color: 'var(--text-secondary)' }}
className="font-normal opacity-50"
>
{words} words
{words} {t('words')}
</span>
</EditorStatusBarItem>
</div>
Expand Down
28 changes: 26 additions & 2 deletions src/components/layout/editor/EditorTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Minimize2,
Info,
Settings,
Pin,
} from 'lucide-react';
import { useAppStore } from '../../../store/useAppStore';
import { showTabContextMenu } from '../../../util/contextMenu';
Expand All @@ -32,8 +33,18 @@ export function EditorTabs() {
createFolder,
closeOtherFiles,
closeAllFiles,
pinnedFiles,
togglePinFile,
} = useAppStore();

const sortedFiles = [...openFiles].sort((a, b) => {
const aPinned = pinnedFiles.includes(a);
const bPinned = pinnedFiles.includes(b);
if (aPinned && !bPinned) return -1;
if (!aPinned && bPinned) return 1;
return 0;
});

return (
<div
className="flex items-center shrink-0 border-b relative"
Expand All @@ -45,9 +56,10 @@ export function EditorTabs() {
>
{/* Tabs List - Takes available space */}
<div className="flex-1 flex overflow-x-auto no-scrollbar h-full">
{openFiles.map((fileId) => {
{sortedFiles.map((fileId) => {
const file = findFile(fileId);
const isActive = activeFileId === fileId;
const isPinned = pinnedFiles.includes(fileId);
const isBlankTab = fileId.startsWith('new-tab-');
const isWelcomeTab = fileId === 'welcome';

Expand All @@ -56,7 +68,10 @@ export function EditorTabs() {
else if (isBlankTab) tabName = 'Untitled';
else if (fileId === 'cinder-settings') tabName = 'Settings';
else if (fileId === 'cinder-info') tabName = 'About';
else tabName = file?.name.replace(/\.md$/, '') || 'Unknown';
else if (file) tabName = file.name.replace(/\.md$/, '');
else
tabName =
fileId.split(/[/\\]/).pop()?.replace(/\.md$/, '') || 'Unknown';

return (
<div
Expand All @@ -78,6 +93,8 @@ export function EditorTabs() {
closeOtherFiles,
closeAllFiles,
findFile,
togglePinFile,
isPinned: (id) => pinnedFiles.includes(id),
});
}}
className={`group flex items-center min-w-[140px] max-w-[220px] h-full px-4 border-r cursor-pointer text-[12px] font-medium select-none transition-all relative shrink-0`}
Expand Down Expand Up @@ -127,6 +144,13 @@ export function EditorTabs() {
{tabName}
</span>

{isPinned && (
<Pin
size={12}
className={`ml-1 shrink-0 ${isActive ? 'text-[var(--editor-header-accent)]' : 'opacity-40'}`}
/>
)}

<button
onClick={(e) => {
e.stopPropagation();
Expand Down
45 changes: 40 additions & 5 deletions src/components/layout/explorer/FileExplorer.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, useMemo } from 'react';
import { useAppStore } from '../../../store/useAppStore';
import { getTranslation } from '../../../utils/i18n';
import type { FileNode } from '../../../types/fileSystem';
import { FileTreeItem } from './FileTreeItem';
import { showExplorerContextMenu } from '../../../util/contextMenu';
Expand Down Expand Up @@ -50,9 +51,20 @@ export function FileExplorer() {
closeOtherFiles,
closeAllFiles,
findFile,
language,
pinnedFiles,
} = useAppStore();
const t = (key: string) => getTranslation(language, key);
const [searchQuery, setSearchQuery] = useState('');

const pinnedNodes = useMemo(() => {
const nodes = pinnedFiles
.map((id) => findFile(id))
.filter(Boolean) as FileNode[];
if (!searchQuery.trim()) return nodes;
return filterNodes(nodes, searchQuery.trim());
}, [pinnedFiles, searchQuery, findFile]);

// Extract folder name from workspace path
const workspaceName = workspacePath
? workspacePath.split('/').pop() ||
Expand Down Expand Up @@ -113,7 +125,7 @@ export function FileExplorer() {
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search..."
placeholder={t('searchPlaceholder')}
className="flex-1 bg-transparent border-none outline-none text-[12px] placeholder:text-[var(--text-tertiary)] min-w-0"
style={{ color: 'var(--text-primary)' }}
/>
Expand All @@ -123,7 +135,7 @@ export function FileExplorer() {
onClick={() => createFile()}
className="h-[28px] w-[28px] flex items-center justify-center rounded-md transition-colors hover:bg-[var(--bg-hover)]"
style={{ color: 'var(--text-secondary)' }}
title="New Note"
title={t('newNote')}
>
<SquarePen size={15} strokeWidth={2.5} />
</button>
Expand Down Expand Up @@ -160,12 +172,35 @@ export function FileExplorer() {
}
}}
>
{filteredFiles.length === 0 ? (
{filteredFiles.length === 0 && pinnedNodes.length === 0 ? (
<div className="px-4 py-4 text-center text-[12px] opacity-50 select-none">
No matches found
{t('noMatches')}
</div>
) : (
<div>
<div className="pb-2">
{pinnedNodes.length > 0 && (
<div className="mb-2">
<div
className="px-4 py-1 text-[10px] font-bold opacity-50 uppercase tracking-wider select-none mb-0.5 mt-1"
style={{ color: 'var(--text-primary)' }}
>
Pinned
</div>
{pinnedNodes.map((node) => (
<FileTreeItem key={`pinned-${node.id}`} node={node} />
))}
</div>
)}

{pinnedNodes.length > 0 && filteredFiles.length > 0 && (
<div
className="px-4 py-1 text-[10px] font-bold opacity-50 uppercase tracking-wider select-none mb-0.5 mt-2"
style={{ color: 'var(--text-primary)' }}
>
Files
</div>
)}

{filteredFiles.map((node) => (
<FileTreeItem key={node.id} node={node} />
))}
Expand Down
22 changes: 22 additions & 0 deletions src/components/layout/explorer/FileTreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ export function FileTreeItem({ node, depth = 0 }: FileTreeItemProps) {
closeAllFiles,
createFile,
findFile,
pinnedFiles,
togglePinFile,
} = useAppStore();

// Derived state from store
Expand Down Expand Up @@ -230,6 +232,8 @@ export function FileTreeItem({ node, depth = 0 }: FileTreeItemProps) {
closeOtherFiles,
closeAllFiles,
findFile,
togglePinFile,
isPinned: (id: string) => pinnedFiles.includes(id),
};
if (node.type === 'folder') {
showFolderContextMenu(node, actions);
Expand Down Expand Up @@ -335,6 +339,24 @@ export function FileTreeItem({ node, depth = 0 }: FileTreeItemProps) {
{node.name.replace(/\.md$/, '')}
</span>
)}

{node.type === 'file' && pinnedFiles.includes(node.id) && (
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={`ml-auto mr-2 shrink-0 ${isActive ? 'text-[var(--editor-header-accent)]' : 'opacity-40'}`}
>
<path d="M12 17v5" />
<path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z" />
</svg>
)}
</div>
</div>

Expand Down
Loading
Loading