Skip to content
Merged
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
376 changes: 68 additions & 308 deletions src/components/features/settings/Settings.tsx

Large diffs are not rendered by default.

123 changes: 52 additions & 71 deletions src/components/features/trash/TrashView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export function TrashView() {
const entries = await invoke<TrashEntry[]>('list_trash', {
workspacePath,
});
// Sort by most recently trashed first
entries.sort(
(a, b) =>
new Date(b.trashed_at).getTime() - new Date(a.trashed_at).getTime()
Expand Down Expand Up @@ -91,113 +90,95 @@ export function TrashView() {
};

return (
<div className="flex-1 flex justify-center h-full bg-[var(--bg-primary)] overflow-hidden">
<div className="w-full max-w-3xl flex flex-col h-full border-x border-[var(--border-primary)] shadow-sm">
<div className="flex-1 h-full bg-[var(--bg-primary)] overflow-y-auto no-scrollbar">
<div className="max-w-xl mx-auto px-8 py-10">
{/* Header */}
<div className="shrink-0 p-8 pb-6 border-b border-[var(--border-primary)]">
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-[var(--text-primary)]">
Trash
</h1>
<p className="mt-1 text-sm text-[var(--text-secondary)]">
{items.length === 0
? 'No items in trash'
: `${items.length} item${items.length !== 1 ? 's' : ''} in trash`}
</p>
</div>
{items.length > 0 && (
<button
onClick={handleEmptyTrash}
className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-xs font-medium transition-colors hover:bg-[var(--bg-hover)] border border-[var(--border-primary)]"
style={{ color: 'var(--text-secondary)' }}
title="Permanently delete all items"
>
<Trash2 size={13} />
Empty Trash
</button>
)}
<div className="flex items-center justify-between">
<div>
<h1 className="text-lg font-semibold text-[var(--text-primary)]">
Trash
</h1>
<p className="mt-1 text-xs text-[var(--text-tertiary)]">
{items.length === 0
? 'No items'
: `${items.length} item${items.length !== 1 ? 's' : ''}`}
</p>
</div>
{items.length > 0 && (
<button
onClick={handleEmptyTrash}
className="flex items-center gap-1.5 px-2.5 py-1 rounded text-[11px] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] hover:bg-[var(--bg-hover)] transition-colors"
title="Permanently delete all items"
>
<Trash2 size={12} />
Empty
</button>
)}
</div>

{/* Content */}
<div className="flex-1 overflow-y-auto no-scrollbar">
<div className="mt-6">
{loading ? (
<div className="flex items-center justify-center h-40">
<p className="text-sm text-[var(--text-tertiary)]">Loading...</p>
</div>
<p className="text-xs text-[var(--text-tertiary)] py-8 text-center">
Loading...
</p>
) : items.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full gap-4 opacity-40">
<div className="flex flex-col items-center py-16 opacity-30">
<Trash2
size={48}
size={32}
strokeWidth={1}
style={{ color: 'var(--text-tertiary)' }}
className="text-[var(--text-tertiary)]"
/>
<p
className="text-sm font-medium"
style={{ color: 'var(--text-tertiary)' }}
>
<p className="text-xs text-[var(--text-tertiary)] mt-3">
Trash is empty
</p>
</div>
) : (
<div>
<div className="space-y-0.5">
{items.map((entry) => (
<div
key={entry.id}
className="flex items-center gap-4 px-8 py-3.5 border-b transition-colors hover:bg-[var(--bg-hover)] group"
style={{ borderColor: 'var(--border-primary)' }}
className="flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-[var(--bg-hover)] transition-colors group"
>
{/* Icon */}
<div
className="p-2 rounded-lg shrink-0"
style={{ backgroundColor: 'var(--bg-tertiary)' }}
>
{entry.entry_type === 'folder' ? (
<Folder
size={18}
style={{ color: 'var(--text-secondary)' }}
/>
) : (
<FileText
size={18}
style={{ color: 'var(--text-secondary)' }}
/>
)}
</div>
{entry.entry_type === 'folder' ? (
<Folder
size={15}
className="shrink-0 text-[var(--text-tertiary)]"
/>
) : (
<FileText
size={15}
className="shrink-0 text-[var(--text-tertiary)]"
/>
)}

{/* Info */}
<div className="flex-1 min-w-0">
<div className="text-sm font-medium truncate text-[var(--text-primary)]">
<div className="text-sm truncate text-[var(--text-primary)]">
{entry.original_name}
</div>
<div className="text-xs text-[var(--text-tertiary)] truncate mt-0.5">
<div className="text-[11px] text-[var(--text-tertiary)] truncate">
{entry.relative_path}
</div>
</div>

{/* Time */}
<span className="text-[11px] text-[var(--text-tertiary)] shrink-0 tabular-nums">
<span className="text-[10px] text-[var(--text-tertiary)] shrink-0 tabular-nums">
{timeAgo(entry.trashed_at)}
</span>

{/* Actions */}
<div className="flex items-center gap-1 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="flex items-center gap-0.5 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity">
<button
onClick={() => handleRestore(entry)}
className="p-1.5 rounded-md transition-colors hover:bg-[var(--bg-active)]"
style={{ color: 'var(--text-secondary)' }}
title="Restore to original location"
className="p-1 rounded hover:bg-[var(--bg-active)] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors"
title="Restore"
>
<RotateCcw size={14} />
<RotateCcw size={13} />
</button>
<button
onClick={() => handleDelete(entry)}
className="p-1.5 rounded-md transition-colors hover:bg-[var(--bg-active)]"
style={{ color: 'var(--text-tertiary)' }}
className="p-1 rounded hover:bg-[var(--bg-active)] text-[var(--text-tertiary)] hover:text-[var(--text-secondary)] transition-colors"
title="Delete permanently"
>
<X size={14} />
<X size={13} />
</button>
</div>
</div>
Expand Down
2 changes: 0 additions & 2 deletions src/components/layout/editor/CodeMirrorEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { markdown, markdownLanguage } from '@codemirror/lang-markdown';
import { languages } from '@codemirror/language-data';
import { EditorView, type ViewUpdate } from '@codemirror/view';
import { cinderTheme } from '../../../theme/cinderTheme';
import { markdownStylingPlugin } from './markdownStylingPlugin';

import { search } from '@codemirror/search';

Expand All @@ -38,7 +37,6 @@ const extensions = [
codeLanguages: languages,
}),
EditorView.lineWrapping,
markdownStylingPlugin,
search({ top: true, caseSensitive: true }),
];

Expand Down
31 changes: 16 additions & 15 deletions src/components/layout/editor/EditorHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useRef, useEffect, type MutableRefObject } from 'react';
import { Undo2, Redo2, Eye, Edit2, MoreVertical } from 'lucide-react';
import { Undo2, Redo2, Eye, Edit2 } from 'lucide-react';
import { useAppStore } from '../../../store/useAppStore';
import { undo, redo } from '@codemirror/commands';
import type { EditorView } from '@codemirror/view';
Expand Down Expand Up @@ -61,7 +61,7 @@ export function EditorHeader({
className="bg-transparent outline-none font-bold px-1 py-0.5 rounded border border-[var(--editor-header-accent)]"
style={{
color: 'var(--text-primary)',
caretColor: '#f48c25',
caretColor: 'var(--editor-header-accent)',
width: `${Math.max(3, 'Untitled'.length + 1)}ch`,
minWidth: '20px',
}}
Expand Down Expand Up @@ -104,7 +104,11 @@ export function EditorHeader({
Untitled
</span>
)}
<span style={{ color: '#f48c25', opacity: 0.5 }}>&gt;</span>
<span
style={{ color: 'var(--editor-header-accent)', opacity: 0.5 }}
>
&gt;
</span>
</div>
)}
{!isBlankTab && breadcrumb.length > 0 && (
Expand All @@ -117,7 +121,14 @@ export function EditorHeader({
return (
<div key={item.id} className="flex items-center gap-2.5">
{index > 0 && (
<span style={{ color: '#f48c25', opacity: 0.5 }}>&gt;</span>
<span
style={{
color: 'var(--editor-header-accent)',
opacity: 0.5,
}}
>
&gt;
</span>
)}
{renamingFileId === item.id && renameSource === 'editor' ? (
<input
Expand All @@ -127,7 +138,7 @@ export function EditorHeader({
className="bg-transparent outline-none font-bold px-1 py-0.5 rounded border border-[var(--editor-header-accent)]"
style={{
color: 'var(--text-primary)',
caretColor: '#f48c25',
caretColor: 'var(--editor-header-accent)',
width: `${Math.max(3, item.name.replace(/\.md$/, '').length + 1)}ch`,
minWidth: '20px',
}}
Expand Down Expand Up @@ -251,16 +262,6 @@ export function EditorHeader({
>
{isPreview ? <Edit2 size={16} /> : <Eye size={16} />}
</button>

{/* Updated More Button - Subtle style matching theme */}
<button
className="flex items-center justify-center p-1.5 rounded-md transition-all duration-200 hover:bg-[var(--bg-hover)] active:scale-95"
style={{
color: 'var(--text-secondary)',
}}
>
<MoreVertical size={16} />
</button>
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/editor/EditorStatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export function EditorStatusBar({
<div
className="w-1.5 h-1.5 rounded-full transition-all duration-300"
style={{
backgroundColor: isAutoSave ? '#22c55e' : '#f59e0b',
backgroundColor: isAutoSave ? '#22c55e' : '#a1a1aa',
boxShadow: isAutoSave
? '0 0 4px rgba(34, 197, 94, 0.3)'
: '0 0 4px rgba(245, 158, 11, 0.3)',
: '0 0 4px rgba(161, 161, 170, 0.3)',
}}
/>
</div>
Expand Down
12 changes: 11 additions & 1 deletion src/components/layout/editor/EditorTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
} from 'lucide-react';
import { useAppStore } from '../../../store/useAppStore';
import { showTabContextMenu } from '../../../util/contextMenu';
import { isMac } from '../../../util/tauri';

export function EditorTabs() {
const {
Expand Down Expand Up @@ -47,6 +48,15 @@ export function EditorTabs() {
height: '40px',
}}
>
{/* macOS traffic lights spacer when sidebar is collapsed */}
{isMac() && (
<div
data-tauri-drag-region
className="shrink-0 h-full transition-[width] duration-200 ease-in-out"
style={{ width: isExplorerCollapsed ? '84px' : '0px' }}
/>
)}

{/* Tabs List - Takes available space */}
<div className="flex-1 flex overflow-x-auto no-scrollbar h-full">
{openFiles.map((fileId) => {
Expand Down Expand Up @@ -88,7 +98,7 @@ export function EditorTabs() {
togglePin,
});
}}
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`}
className={`animate-tab-enter 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`}
style={{
borderColor: 'var(--border-primary)',
backgroundColor: isActive
Expand Down
30 changes: 28 additions & 2 deletions src/components/layout/editor/MarkdownPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import remarkMath from 'remark-math';
import remarkBreaks from 'remark-breaks';
import rehypeKatex from 'rehype-katex';
import rehypeHighlight from 'rehype-highlight';
import rehypeSanitize from 'rehype-sanitize';
import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
import { processMarkdown } from '../../../util/markdownpipeline';
import { isValidElement } from 'react';

Expand All @@ -13,6 +13,28 @@ import 'katex/dist/katex.min.css';
import 'highlight.js/styles/github-dark.css';
import '../../../theme/markdown.css';

/**
* Custom sanitization schema that preserves math-related classes through
* the sanitizer so rehype-katex (which runs AFTER sanitize) can find and
* render them.
*
* Per the official rehype-katex docs, the correct order is:
* rehypeSanitize -> rehypeKatex
* NOT:
* rehypeKatex -> rehypeSanitize (which strips KaTeX's HTML output)
*
* @see https://github.com/remarkjs/remark-math/blob/main/packages/rehype-katex/readme.md
*/
const mathSanitizeSchema = {
...defaultSchema,
attributes: {
...defaultSchema.attributes,
// Allow math-related classes on code elements so rehype-katex can process them.
// The `language-*` regex is allowed by default for syntax highlighting.
code: [['className', /^language-./, 'math-inline', 'math-display']],
},
};

interface MarkdownPreviewProps {
content: string;
}
Expand All @@ -25,7 +47,11 @@ export function MarkdownPreview({ content }: MarkdownPreviewProps) {
<div className="markdown-preview px-8 sm:px-12 py-10">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath, remarkBreaks]}
rehypePlugins={[rehypeKatex, rehypeHighlight, rehypeSanitize]}
rehypePlugins={[
rehypeHighlight,
[rehypeSanitize, mathSanitizeSchema],
rehypeKatex,
]}
components={{
pre({ children }) {
const childArray = Array.isArray(children)
Expand Down
15 changes: 10 additions & 5 deletions src/components/layout/explorer/FileTreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -493,11 +493,16 @@ export function FileTreeItem({
</div>

{/* Children */}
{node.type === 'folder' && isOpen && node.children && (
<div>
{node.children.map((child) => (
<FileTreeItem key={child.id} node={child} depth={depth + 1} />
))}
{node.type === 'folder' && node.children && (
<div
className="grid transition-[grid-template-rows] duration-200 ease-in-out"
style={{ gridTemplateRows: isOpen ? '1fr' : '0fr' }}
>
<div className="overflow-hidden">
{node.children.map((child) => (
<FileTreeItem key={child.id} node={child} depth={depth + 1} />
))}
</div>
</div>
)}
</div>
Expand Down
16 changes: 16 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,19 @@ body {
background-color: var(--vsc-indent-guide) !important;
opacity: 1.5;
}

/* Tab animations */
@keyframes tab-enter {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

.animate-tab-enter {
animation: tab-enter 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
Loading
Loading