Skip to content

Commit c5b41cf

Browse files
committed
Export feature
1 parent 73897af commit c5b41cf

File tree

4 files changed

+406
-26
lines changed

4 files changed

+406
-26
lines changed

frontend/src/components/Editor.tsx

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { UserPresence } from "./UserPresence";
1414
import { ConnectionStatus } from "./ConnectionStatus";
1515
import { NamePrompt } from "./NamePrompt";
1616
import { Cursors } from "./Cursors";
17+
import { ExportMenu } from "./ExportMenu";
1718
import { useCursors } from "../hooks/useCursors";
1819
import { generateDocId } from "../lib/generateDocId";
1920
import "@blocknote/core/fonts/inter.css";
@@ -225,7 +226,7 @@ export function Editor({ docId }: EditorProps) {
225226
color: "#1a1a1a",
226227
}}
227228
>
228-
Markdoc
229+
[ Markdoc ]
229230
</h1>
230231
<p
231232
style={{ margin: "4px 0 0 0", color: "#666", fontSize: "13px" }}
@@ -261,30 +262,14 @@ export function Editor({ docId }: EditorProps) {
261262
{/* User Presence Avatars */}
262263
<UserPresence channel={provider?.channel || null} />
263264

264-
{/* New Document Button */}
265-
<button
266-
onClick={handleNewDocument}
267-
style={{
268-
padding: "10px 20px",
269-
fontSize: "14px",
270-
fontWeight: 600,
271-
color: "white",
272-
backgroundColor: "#646cff",
273-
border: "none",
274-
borderRadius: "6px",
275-
cursor: "pointer",
276-
transition: "background-color 0.2s",
277-
whiteSpace: "nowrap",
278-
}}
279-
onMouseEnter={(e) => {
280-
e.currentTarget.style.backgroundColor = "#535bf2";
281-
}}
282-
onMouseLeave={(e) => {
283-
e.currentTarget.style.backgroundColor = "#646cff";
284-
}}
285-
>
286-
+ New Document
287-
</button>
265+
{/* Combined Menu (New Document + Export) */}
266+
{editor && (
267+
<ExportMenu
268+
editor={editor}
269+
docId={docId}
270+
onNewDocument={handleNewDocument}
271+
/>
272+
)}
288273
</div>
289274
</div>
290275
</div>
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
/**
2+
* ExportMenu Component
3+
*
4+
* Combined dropdown menu for document actions and exporting in various formats.
5+
* Supports New Document creation, Markdown, HTML, and Plain Text exports.
6+
*/
7+
8+
import { useState } from "react";
9+
import type { BlockNoteEditor } from "@blocknote/core";
10+
import {
11+
exportToMarkdown,
12+
exportToHTML,
13+
exportToPlainText,
14+
} from "../lib/exportDocument";
15+
16+
interface ExportMenuProps {
17+
editor: BlockNoteEditor;
18+
docId: string;
19+
onNewDocument: () => void;
20+
}
21+
22+
export function ExportMenu({ editor, docId, onNewDocument }: ExportMenuProps) {
23+
const [isOpen, setIsOpen] = useState(false);
24+
25+
const handleNewDocument = () => {
26+
setIsOpen(false);
27+
onNewDocument();
28+
};
29+
30+
const handleExport = async (format: "markdown" | "html" | "text") => {
31+
setIsOpen(false);
32+
33+
switch (format) {
34+
case "markdown":
35+
await exportToMarkdown(editor, docId);
36+
break;
37+
case "html":
38+
await exportToHTML(editor, docId);
39+
break;
40+
case "text":
41+
await exportToPlainText(editor, docId);
42+
break;
43+
}
44+
};
45+
46+
return (
47+
<div style={{ position: "relative" }}>
48+
{/* Split Button Container */}
49+
<div
50+
style={{
51+
display: "flex",
52+
backgroundColor: "#646cff",
53+
borderRadius: "6px",
54+
overflow: "hidden",
55+
transition: "background-color 0.2s",
56+
}}
57+
>
58+
{/* Main Action - New Document */}
59+
<button
60+
onClick={handleNewDocument}
61+
style={{
62+
padding: "10px 20px",
63+
fontSize: "14px",
64+
fontWeight: 600,
65+
color: "white",
66+
backgroundColor: "transparent",
67+
border: "none",
68+
cursor: "pointer",
69+
display: "flex",
70+
alignItems: "center",
71+
gap: "10px",
72+
transition: "background-color 0.2s",
73+
whiteSpace: "nowrap",
74+
}}
75+
onMouseEnter={(e) => {
76+
e.currentTarget.style.backgroundColor = "rgba(0,0,0,0.1)";
77+
}}
78+
onMouseLeave={(e) => {
79+
e.currentTarget.style.backgroundColor = "transparent";
80+
}}
81+
>
82+
<span
83+
style={{
84+
display: "flex",
85+
alignItems: "center",
86+
justifyContent: "center",
87+
width: "24px",
88+
height: "24px",
89+
backgroundColor: "rgba(255,255,255,0.3)",
90+
borderRadius: "6px",
91+
fontSize: "18px",
92+
}}
93+
>
94+
+
95+
</span>
96+
New
97+
</button>
98+
99+
{/* Divider */}
100+
<div
101+
style={{
102+
width: "1px",
103+
backgroundColor: "rgba(255,255,255,0.3)",
104+
}}
105+
/>
106+
107+
{/* Dropdown Toggle */}
108+
<button
109+
onClick={() => setIsOpen(!isOpen)}
110+
style={{
111+
padding: "10px 16px",
112+
backgroundColor: "transparent",
113+
border: "none",
114+
cursor: "pointer",
115+
display: "flex",
116+
alignItems: "center",
117+
justifyContent: "center",
118+
transition: "background-color 0.2s",
119+
}}
120+
onMouseEnter={(e) => {
121+
e.currentTarget.style.backgroundColor = "rgba(0,0,0,0.1)";
122+
}}
123+
onMouseLeave={(e) => {
124+
e.currentTarget.style.backgroundColor = "transparent";
125+
}}
126+
>
127+
<span style={{ fontSize: "16px", color: "white" }}></span>
128+
</button>
129+
</div>
130+
131+
{/* Dropdown Menu */}
132+
{isOpen && (
133+
<>
134+
{/* Backdrop */}
135+
<div
136+
onClick={() => setIsOpen(false)}
137+
style={{
138+
position: "fixed",
139+
inset: 0,
140+
zIndex: 1001,
141+
}}
142+
/>
143+
144+
{/* Menu Content */}
145+
<div
146+
style={{
147+
position: "absolute",
148+
top: "calc(100% + 8px)",
149+
right: 0,
150+
background: "white",
151+
borderRadius: "8px",
152+
boxShadow: "0 4px 20px rgba(0,0,0,0.15)",
153+
border: "1px solid #e0e0e0",
154+
padding: "8px",
155+
minWidth: "200px",
156+
zIndex: 1002,
157+
}}
158+
>
159+
<div
160+
style={{
161+
fontSize: "11px",
162+
fontWeight: 600,
163+
color: "#999",
164+
textTransform: "uppercase",
165+
letterSpacing: "0.5px",
166+
padding: "8px 12px 4px",
167+
}}
168+
>
169+
Export as
170+
</div>
171+
172+
<MenuItem
173+
label="Markdown (.md)"
174+
onClick={() => handleExport("markdown")}
175+
/>
176+
<MenuItem
177+
label="HTML (.html)"
178+
onClick={() => handleExport("html")}
179+
/>
180+
<MenuItem
181+
label="Plain Text (.txt)"
182+
onClick={() => handleExport("text")}
183+
/>
184+
</div>
185+
</>
186+
)}
187+
</div>
188+
);
189+
}
190+
191+
// Menu item component for reusability
192+
function MenuItem({ label, onClick }: { label: string; onClick: () => void }) {
193+
return (
194+
<div
195+
onClick={onClick}
196+
style={{
197+
padding: "12px 16px",
198+
borderRadius: "4px",
199+
cursor: "pointer",
200+
fontSize: "14px",
201+
color: "#1a1a1a",
202+
transition: "background-color 0.2s",
203+
}}
204+
onMouseEnter={(e) => {
205+
e.currentTarget.style.backgroundColor = "#f5f5f5";
206+
}}
207+
onMouseLeave={(e) => {
208+
e.currentTarget.style.backgroundColor = "transparent";
209+
}}
210+
>
211+
{label}
212+
</div>
213+
);
214+
}

0 commit comments

Comments
 (0)