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 [shareToast, setShareToast] = useState<string | null>(null);
|
||||||
const [historyStack, setHistoryStack] = useState<string[]>([]);
|
const [historyStack, setHistoryStack] = useState<string[]>([]);
|
||||||
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
||||||
|
const [shortcutsModalOpen, setShortcutsModalOpen] = useState(false);
|
||||||
|
|
||||||
const activeFilterCount =
|
const activeFilterCount =
|
||||||
(filterNotebookId ? 1 : 0) +
|
(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