
Some checks failed
GBCI / build (push) Failing after 10m45s
- Introduced a new CSS theme for Orange, featuring a modern color palette with distinct foreground and background colors. - Added an XTree Gold theme that emulates the classic 1980s DOS interface, complete with authentic colors and styles for file management elements. - Both themes include variables for customization and specific styles for various UI components such as cards, popovers, and menus.
333 lines
No EOL
11 KiB
TypeScript
333 lines
No EOL
11 KiB
TypeScript
"use client";
|
|
import { useState, useRef, useEffect } from 'react';
|
|
import { useEditor, EditorContent, BubbleMenu } from '@tiptap/react';
|
|
import StarterKit from '@tiptap/starter-kit';
|
|
import TextStyle from '@tiptap/extension-text-style';
|
|
import Color from '@tiptap/extension-color';
|
|
import Highlight from '@tiptap/extension-highlight';
|
|
import TextAlign from '@tiptap/extension-text-align';
|
|
import Footer from '../footer'
|
|
|
|
import {
|
|
Bold, Italic, Underline, AlignLeft, AlignCenter, AlignRight,
|
|
Link, Highlighter, Type, Palette, Sparkles
|
|
} from 'lucide-react';
|
|
|
|
const SimplePaperNote = () => {
|
|
const [title, setTitle] = useState('Untitled');
|
|
const [isEditingTitle, setIsEditingTitle] = useState(false);
|
|
const titleInputRef = useRef(null);
|
|
|
|
const editor = useEditor({
|
|
extensions: [
|
|
StarterKit,
|
|
TextStyle,
|
|
Color,
|
|
Highlight.configure({ multicolor: true }),
|
|
TextAlign.configure({
|
|
types: ['heading', 'paragraph'],
|
|
}),
|
|
|
|
],
|
|
content: `
|
|
<p>Start writing your thoughts here...</p>
|
|
`,
|
|
editorProps: {
|
|
attributes: {
|
|
class: 'prose prose-invert max-w-none focus:outline-none min-h-[calc(100vh-8rem)] p-8 text-foreground',
|
|
},
|
|
},
|
|
});
|
|
|
|
const addLink = () => {
|
|
const previousUrl = editor?.getAttributes('link').href;
|
|
const url = window.prompt('Enter URL:', previousUrl);
|
|
if (url === null) return;
|
|
if (url === '') {
|
|
editor?.chain().focus().extendMarkRange('link').unsetLink().run();
|
|
return;
|
|
}
|
|
editor?.chain().focus().extendMarkRange('link').setLink({ href: url }).run();
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (isEditingTitle && titleInputRef.current) {
|
|
titleInputRef.current.focus();
|
|
}
|
|
}, [isEditingTitle]);
|
|
|
|
if (!editor) {
|
|
return null;
|
|
}
|
|
|
|
|
|
// OneNote-like keyboard shortcuts (note editing & navigation)
|
|
// Two rows, unique qkeys, avoid browser/reserved keys
|
|
// File/Note operations row
|
|
const shortcuts = [
|
|
[
|
|
{ key: 'Q', label: 'Resume', action: () => {/* Implement resume logic here */ } },
|
|
{ key: 'W', label: 'Write', action: () => {/* Implement write logic here */ } },
|
|
{ key: 'E', label: 'Expand', action: () => {/* Implement expand logic here */ } },
|
|
{ key: 'R', label: 'One Word', action: () => {/* Implement one word logic here */ } },
|
|
{ key: 'T', label: 'As List', action: () => editor?.chain().focus().toggleBulletList().run() },
|
|
{ key: 'Y', label: 'As Mail', action: () => {/* Implement as mail logic here */ } },
|
|
{ key: 'U', label: 'Copy', action: () => document.execCommand('copy') },
|
|
{ key: 'I', label: 'Paste', action: () => document.execCommand('paste') },
|
|
{ key: 'O', label: 'Undo', action: () => editor?.chain().focus().undo().run() },
|
|
{ key: 'P', label: 'Redo', action: () => editor?.chain().focus().redo().run() },
|
|
],
|
|
[
|
|
{ key: 'A', label: 'Select', action: () => {/* Implement select logic here */ } },
|
|
{ key: 'S', label: 'Select All', action: () => editor?.chain().focus().selectAll().run() },
|
|
{ key: 'D', label: 'Deselect', action: () => {/* Implement deselect logic here */ } },
|
|
{ key: 'G', label: 'Random', action: () => {/* Implement insert image logic here */ } },
|
|
{ key: 'H', label: 'Idea', action: () => {/* Implement insert table logic here */ } },
|
|
{ key: 'J', label: 'Insert Link', action: addLink },
|
|
{ key: 'K', label: 'Highlight', action: () => editor?.chain().focus().toggleHighlight({ color: '#ffff00' }).run() },
|
|
{ key: 'L', label: 'To-Do', action: () => editor?.chain().focus().toggleTaskList?.().run?.() },
|
|
{ key: 'Z', label: 'Zoom In', action: () => {/* Implement zoom in logic here */ } },
|
|
{ key: 'X', label: 'Zoom Out', action: () => {/* Implement zoom out logic here */ } },
|
|
]
|
|
];
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background text-foreground">
|
|
<div>
|
|
<div className="max-w-4xl mx-auto">
|
|
{/* Paper Shadow Effect */}
|
|
<div className="mx-4 my-8 bg-card rounded-lg shadow-2xl shadow-black/20 border border-border">
|
|
<EditorContent
|
|
editor={editor}
|
|
className="min-h-[calc(100vh-12rem)]"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Floating Selection Toolbar */}
|
|
{editor && (
|
|
<BubbleMenu
|
|
editor={editor}
|
|
tippyOptions={{
|
|
duration: 100,
|
|
placement: 'top',
|
|
animation: 'shift-away'
|
|
}}
|
|
>
|
|
<div className="flex items-center bg-card border border-border rounded-lg shadow-lg p-1">
|
|
{/* Text Formatting */}
|
|
<button
|
|
onClick={() => editor.chain().focus().toggleBold().run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('bold') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Bold"
|
|
>
|
|
<Bold className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => editor.chain().focus().toggleItalic().run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('italic') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Italic"
|
|
>
|
|
<Italic className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => editor.chain().focus().toggleUnderline().run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('underline') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Underline"
|
|
>
|
|
<Underline className="h-4 w-4" />
|
|
</button>
|
|
|
|
<div className="w-px h-6 bg-border mx-1"></div>
|
|
|
|
{/* Text Alignment */}
|
|
<button
|
|
onClick={() => editor.chain().focus().setTextAlign('left').run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'left' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Align Left"
|
|
>
|
|
<AlignLeft className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => editor.chain().focus().setTextAlign('center').run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'center' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Align Center"
|
|
>
|
|
<AlignCenter className="h-4 w-4" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => editor.chain().focus().setTextAlign('right').run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive({ textAlign: 'right' }) ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Align Right"
|
|
>
|
|
<AlignRight className="h-4 w-4" />
|
|
</button>
|
|
|
|
<div className="w-px h-6 bg-border mx-1"></div>
|
|
|
|
{/* Highlight */}
|
|
<button
|
|
onClick={() => editor.chain().focus().toggleHighlight({ color: '#ffff00' }).run()}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('highlight') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Highlight"
|
|
>
|
|
<Highlighter className="h-4 w-4" />
|
|
</button>
|
|
|
|
{/* Link */}
|
|
<button
|
|
onClick={addLink}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('link') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Add Link"
|
|
>
|
|
<Link className="h-4 w-4" />
|
|
</button>
|
|
|
|
<div className="w-px h-6 bg-border mx-1"></div>
|
|
|
|
{/* Heading */}
|
|
<button
|
|
onClick={() => {
|
|
if (editor.isActive('heading')) {
|
|
editor.chain().focus().setParagraph().run();
|
|
} else {
|
|
editor.chain().focus().toggleHeading({ level: 2 }).run();
|
|
}
|
|
}}
|
|
className={`p-2 rounded hover:bg-accent transition-colors ${editor.isActive('heading') ? 'bg-primary text-primary-foreground' : 'text-foreground'
|
|
}`}
|
|
title="Heading"
|
|
>
|
|
<Type className="h-4 w-4" />
|
|
</button>
|
|
</div>
|
|
</BubbleMenu>
|
|
)}
|
|
</div>
|
|
|
|
{/* Custom Styles */}
|
|
<style jsx global>{`
|
|
.ProseMirror {
|
|
outline: none;
|
|
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
|
font-size: 16px;
|
|
line-height: 1.7;
|
|
color: hsl(var(--foreground));
|
|
padding: 3rem;
|
|
|
|
min-height: calc(100vh - 12rem);
|
|
}
|
|
|
|
.ProseMirror h1 {
|
|
font-size: 2.5rem;
|
|
font-weight: 700;
|
|
margin: 2rem 0 1rem 0;
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.ProseMirror h2 {
|
|
font-size: 2rem;
|
|
font-weight: 600;
|
|
margin: 1.5rem 0 0.75rem 0;
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.ProseMirror h3 {
|
|
font-size: 1.5rem;
|
|
font-weight: 600;
|
|
margin: 1.25rem 0 0.5rem 0;
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.ProseMirror p {
|
|
margin: 0.75rem 0;
|
|
}
|
|
|
|
.ProseMirror a {
|
|
color: hsl(var(--accent));
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
}
|
|
|
|
.ProseMirror a:hover {
|
|
color: hsl(var(--primary));
|
|
}
|
|
|
|
.ProseMirror mark {
|
|
background-color: #ffff0040;
|
|
border-radius: 2px;
|
|
padding: 0 2px;
|
|
}
|
|
|
|
.ProseMirror ul, .ProseMirror ol {
|
|
margin: 1rem 0;
|
|
padding-left: 1.5rem;
|
|
}
|
|
|
|
.ProseMirror li {
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
.ProseMirror blockquote {
|
|
border-left: 4px solid hsl(var(--primary));
|
|
padding-left: 1rem;
|
|
margin: 1rem 0;
|
|
font-style: italic;
|
|
color: hsl(var(--muted-foreground));
|
|
}
|
|
|
|
.ProseMirror code {
|
|
background-color: hsl(var(--muted));
|
|
padding: 0.25rem 0.5rem;
|
|
border-radius: 4px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.9em;
|
|
}
|
|
|
|
.ProseMirror pre {
|
|
background-color: hsl(var(--muted));
|
|
padding: 1rem;
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.ProseMirror pre code {
|
|
background: none;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Selection highlighting */
|
|
.ProseMirror ::selection {
|
|
background-color: hsl(var(--primary) / 0.2);
|
|
}
|
|
|
|
/* Placeholder styling */
|
|
.ProseMirror p.is-editor-empty:first-child::before {
|
|
content: attr(data-placeholder);
|
|
float: left;
|
|
color: hsl(var(--muted-foreground));
|
|
pointer-events: none;
|
|
height: 0;
|
|
}
|
|
`}</style>
|
|
|
|
<Footer shortcuts={shortcuts} />
|
|
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SimplePaperNote; |