feat: Add full suite of Evernote-compatible keyboard shortcuts and visual cheat sheet modal
This commit is contained in:
1 parent
3de36ff39b
commit
8907b4c494
2 files changed
+286
No files matched your search
@@ -105,6 +105,7 @@ export default function EvercloneWorkspace() {
|
||||
const [shareToast, setShareToast] = useState<string | null>(null);
|
||||
const [historyStack, setHistoryStack] = useState<string[]>([]);
|
||||
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
||||
const [shortcutsModalOpen, setShortcutsModalOpen] = useState(false);
|
||||
|
||||
const activeFilterCount =
|
||||
(filterNotebookId ? 1 : 0) +
|
||||
|
||||
@@ -0,0 +1,285 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
Keyboard,
|
||||
X,
|
||||
FileText,
|
||||
Search,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Pin,
|
||||
Star,
|
||||
Trash2,
|
||||
Tag,
|
||||
Link2,
|
||||
PanelLeft,
|
||||
Bold,
|
||||
Italic,
|
||||
Underline,
|
||||
List,
|
||||
ListOrdered,
|
||||
CheckSquare,
|
||||
Code,
|
||||
Highlighter,
|
||||
Save,
|
||||
} from 'lucide-react';
|
||||
|
||||
interface KeyboardShortcutsModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface ShortcutItem {
|
||||
keys: string[];
|
||||
description: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function KeyboardShortcutsModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
}: KeyboardShortcutsModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const navigationShortcuts: ShortcutItem[] = [
|
||||
{
|
||||
keys: ['Alt', 'N'],
|
||||
description: 'Create New Note',
|
||||
icon: <FileText className="w-3.5 h-3.5 text-evernote-green" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'K'],
|
||||
description: 'Focus Search / Link',
|
||||
icon: <Search className="w-3.5 h-3.5 text-blue-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['/'],
|
||||
description: 'Jump to Search (when not editing)',
|
||||
icon: <Search className="w-3.5 h-3.5 text-blue-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Alt', '←'],
|
||||
description: 'Previous Visited Note (Back)',
|
||||
icon: <ArrowLeft className="w-3.5 h-3.5 text-amber-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Alt', '→'],
|
||||
description: 'Next Visited Note (Forward)',
|
||||
icon: <ArrowRight className="w-3.5 h-3.5 text-amber-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', '\\'],
|
||||
description: 'Toggle Left Sidebar',
|
||||
icon: <PanelLeft className="w-3.5 h-3.5 text-gray-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Alt', 'L'],
|
||||
description: 'Copy Internal Note Link',
|
||||
icon: <Link2 className="w-3.5 h-3.5 text-evernote-green" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'P'],
|
||||
description: 'Pin / Unpin Note',
|
||||
icon: <Pin className="w-3.5 h-3.5 text-evernote-green" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'S'],
|
||||
description: 'Add / Remove Shortcut (Star)',
|
||||
icon: <Star className="w-3.5 h-3.5 text-amber-400" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'T'],
|
||||
description: 'Focus Add Tag Input',
|
||||
icon: <Tag className="w-3.5 h-3.5 text-emerald-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'Del'],
|
||||
description: 'Move Note to Trash',
|
||||
icon: <Trash2 className="w-3.5 h-3.5 text-red-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['?'],
|
||||
description: 'Show Shortcuts Cheat Sheet',
|
||||
icon: <Keyboard className="w-3.5 h-3.5 text-gray-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Esc'],
|
||||
description: 'Close Modals / Deselect',
|
||||
},
|
||||
];
|
||||
|
||||
const editorShortcuts: ShortcutItem[] = [
|
||||
{
|
||||
keys: ['Ctrl', 'K'],
|
||||
description: 'Insert Link / Internal Note Link',
|
||||
icon: <Link2 className="w-3.5 h-3.5 text-evernote-green" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'S'],
|
||||
description: 'Save Edits Immediately',
|
||||
icon: <Save className="w-3.5 h-3.5 text-emerald-600" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'B'],
|
||||
description: 'Bold Text',
|
||||
icon: <Bold className="w-3.5 h-3.5" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'I'],
|
||||
description: 'Italic Text',
|
||||
icon: <Italic className="w-3.5 h-3.5" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'U'],
|
||||
description: 'Underline Text',
|
||||
icon: <Underline className="w-3.5 h-3.5" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'X'],
|
||||
description: 'Strikethrough',
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'H'],
|
||||
description: 'Highlight Text',
|
||||
icon: <Highlighter className="w-3.5 h-3.5 text-amber-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', '7'],
|
||||
description: 'Bulleted List',
|
||||
icon: <List className="w-3.5 h-3.5" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', '8'],
|
||||
description: 'Numbered List',
|
||||
icon: <ListOrdered className="w-3.5 h-3.5" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', '9'],
|
||||
description: 'Task Checklist',
|
||||
icon: <CheckSquare className="w-3.5 h-3.5 text-evernote-green" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Shift', 'C'],
|
||||
description: 'Code Block',
|
||||
icon: <Code className="w-3.5 h-3.5 text-blue-500" />,
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Alt', '1-3'],
|
||||
description: 'Heading 1, 2, 3',
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Z'],
|
||||
description: 'Undo Edit',
|
||||
},
|
||||
{
|
||||
keys: ['Ctrl', 'Y'],
|
||||
description: 'Redo Edit',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4 animate-fade-in">
|
||||
<div className="bg-white rounded-2xl shadow-2xl border border-gray-200 w-full max-w-2xl max-h-[85vh] flex flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between bg-gradient-to-r from-emerald-50 to-teal-50">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="w-8 h-8 rounded-lg bg-evernote-green/15 text-evernote-green flex items-center justify-center">
|
||||
<Keyboard className="w-4 h-4" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-bold text-gray-900 text-sm">
|
||||
Keyboard Shortcuts
|
||||
</h3>
|
||||
<p className="text-[11px] text-gray-500">
|
||||
Evernote-compatible hotkeys for fast navigation and editing
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="p-1 text-gray-400 hover:text-gray-700 rounded-lg hover:bg-white transition"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content Body: 2 Columns */}
|
||||
<div className="flex-1 overflow-y-auto p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Column 1: Navigation & Library */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-xs font-bold uppercase tracking-wider text-gray-500 pb-1 border-b border-gray-100 flex items-center gap-1.5">
|
||||
<span>Navigation & Library</span>
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{navigationShortcuts.map((sc, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center justify-between gap-2 text-xs py-1"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-gray-700 min-w-0">
|
||||
{sc.icon && <span className="shrink-0">{sc.icon}</span>}
|
||||
<span className="truncate">{sc.description}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{sc.keys.map((k, ki) => (
|
||||
<kbd
|
||||
key={ki}
|
||||
className="px-2 py-0.5 text-[11px] font-semibold bg-gray-100 text-gray-800 border border-gray-300 rounded shadow-2xs font-mono"
|
||||
>
|
||||
{k}
|
||||
</kbd>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Column 2: Editor & Formatting */}
|
||||
<div className="space-y-3">
|
||||
<h4 className="text-xs font-bold uppercase tracking-wider text-gray-500 pb-1 border-b border-gray-100 flex items-center gap-1.5">
|
||||
<span>Editor & Formatting</span>
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{editorShortcuts.map((sc, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex items-center justify-between gap-2 text-xs py-1"
|
||||
>
|
||||
<div className="flex items-center gap-2 text-gray-700 min-w-0">
|
||||
{sc.icon && <span className="shrink-0">{sc.icon}</span>}
|
||||
<span className="truncate">{sc.description}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 shrink-0">
|
||||
{sc.keys.map((k, ki) => (
|
||||
<kbd
|
||||
key={ki}
|
||||
className="px-2 py-0.5 text-[11px] font-semibold bg-gray-100 text-gray-800 border border-gray-300 rounded shadow-2xs font-mono"
|
||||
>
|
||||
{k}
|
||||
</kbd>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="px-6 py-3 bg-gray-50 border-t border-gray-200 flex items-center justify-between text-xs text-gray-500">
|
||||
<span>Tip: On Mac, use <kbd className="px-1.5 py-0.2 bg-gray-200 rounded font-mono text-[10px]">Cmd</kbd> instead of <kbd className="px-1.5 py-0.2 bg-gray-200 rounded font-mono text-[10px]">Ctrl</kbd></span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="px-4 py-1.5 bg-white border border-gray-300 hover:bg-gray-100 text-gray-700 rounded-lg font-semibold text-xs transition"
|
||||
>
|
||||
Close (Esc)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user