Skip to content

Commit 7add50f

Browse files
committed
sync: export from private super-repo
1 parent 41dc545 commit 7add50f

7 files changed

Lines changed: 99 additions & 28 deletions

File tree

packages/web/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"exports": {
66
".": "./src/index.ts",
77
"./preview": "./src/preview/index.ts",
8-
"./adapters": "./src/adapters/index.ts"
8+
"./adapters": "./src/adapters/index.ts",
9+
"./embed": "./src/embed/index.ts"
910
},
1011
"scripts": {
1112
"dev": "vite",

packages/web/src/App.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,11 @@ export default function App() {
229229
<PaperEditor
230230
initialMarkdown={activeProject.spec}
231231
filename={activeProject.name}
232-
onSave={(content) => {
232+
onPersist={async (content) => {
233+
await saveProject(activeProject.name, content, 'paper');
234+
setActiveProject(prev => prev ? ({ ...prev, spec: content }) : null);
235+
}}
236+
onChange={(content) => {
233237
setActiveProject(prev => prev ? ({ ...prev, spec: content }) : null);
234238
}}
235239
/>
@@ -332,4 +336,4 @@ export default function App() {
332336
/>
333337
</div>
334338
);
335-
}
339+
}

packages/web/src/components/PaperEditor/PaperEditor.tsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,26 @@ import { PreviewController } from './PreviewController';
33
import { PanelLeft, PanelRight, Printer, Save, Image as ImageIcon, X } from 'lucide-react';
44
import { ArrowGramEditor } from '../../ArrowGramEditor';
55
import { useToast } from '../../context/ToastContext';
6-
import { saveProject } from '../../utils/storage';
76

87
interface PaperEditorProps {
98
initialMarkdown: string;
109
filename: string;
11-
onSave?: (content: string) => void;
10+
onPersist?: (content: string) => Promise<void> | void;
11+
onChange?: (content: string) => void;
12+
onPrint?: (content: string, opts: { isTwoColumn: boolean }) => void;
13+
showSaveButton?: boolean;
14+
showPrintButton?: boolean;
1215
}
1316

14-
export function PaperEditor({ initialMarkdown, filename, onSave }: PaperEditorProps) {
17+
export function PaperEditor({
18+
initialMarkdown,
19+
filename,
20+
onPersist,
21+
onChange,
22+
onPrint,
23+
showSaveButton = true,
24+
showPrintButton = true,
25+
}: PaperEditorProps) {
1526
const [markdown, setMarkdown] = useState(initialMarkdown);
1627
const [isTwoColumn, setIsTwoColumn] = useState(false);
1728
const [filesView, setFilesView] = useState('split'); // 'split', 'code', 'preview'
@@ -25,20 +36,31 @@ export function PaperEditor({ initialMarkdown, filename, onSave }: PaperEditorPr
2536

2637
// -- File Operations --
2738
const handleSave = async () => {
39+
if (!onPersist) {
40+
addToast("Save is not configured in this host app.", "error");
41+
return;
42+
}
43+
2844
try {
29-
await saveProject(filename, markdown, 'paper');
45+
await onPersist(markdown);
3046
addToast("Paper saved successfully!", "success");
31-
onSave?.(markdown);
3247
} catch (e) {
3348
console.error(e);
3449
addToast("Failed to save paper.", "error");
3550
}
3651
};
3752

3853
const handlePrint = () => {
54+
if (onPrint) {
55+
onPrint(markdown, { isTwoColumn });
56+
return;
57+
}
58+
3959
localStorage.setItem('print_content', markdown);
4060
localStorage.setItem('print_layout', String(isTwoColumn));
41-
window.open('/print-preview', '_blank');
61+
const baseUrl = new URL(import.meta.env.BASE_URL, window.location.origin);
62+
const href = new URL('print-preview', baseUrl).toString();
63+
window.open(href, '_blank');
4264
};
4365

4466
// -- Diagram Editing --
@@ -107,6 +129,7 @@ export function PaperEditor({ initialMarkdown, filename, onSave }: PaperEditorPr
107129
<button
108130
onClick={handlePrint}
109131
className="flex items-center gap-2 px-3 py-1.5 text-gray-600 hover:bg-gray-100 rounded-md text-sm font-medium transition-colors"
132+
style={{ display: showPrintButton ? undefined : 'none' }}
110133
>
111134
<Printer size={16} />
112135
Print PDF
@@ -115,6 +138,7 @@ export function PaperEditor({ initialMarkdown, filename, onSave }: PaperEditorPr
115138
<button
116139
onClick={handleSave}
117140
className="flex items-center gap-2 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md text-sm font-medium transition-colors shadow-sm"
141+
style={{ display: showSaveButton ? undefined : 'none' }}
118142
>
119143
<Save size={16} />
120144
Save
@@ -132,7 +156,11 @@ export function PaperEditor({ initialMarkdown, filename, onSave }: PaperEditorPr
132156
<textarea
133157
className="flex-1 w-full h-full p-6 resize-none outline-none font-mono text-sm bg-white text-gray-800 leading-relaxed"
134158
value={markdown}
135-
onChange={(e) => setMarkdown(e.target.value)}
159+
onChange={(e) => {
160+
const next = e.target.value;
161+
setMarkdown(next);
162+
onChange?.(next);
163+
}}
136164
placeholder="# Start writing your paper..."
137165
spellCheck={false}
138166
/>

packages/web/src/components/PaperEditor/PreviewController.tsx

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import React, { useEffect, useRef } from 'react';
22
import { createRoot } from 'react-dom/client';
3-
import ReactDOMServer from 'react-dom/server';
4-
import showdown from 'showdown';
5-
import mermaid from 'mermaid';
6-
import katex from 'katex';
7-
// @ts-ignore
8-
import * as vega from 'vega';
9-
// @ts-ignore
10-
import * as vegaLite from 'vega-lite';
11-
import { Previewer } from 'pagedjs';
123
import { ArrowGramStatic } from '../ArrowGramStatic';
134
import './styles.css';
145

@@ -55,6 +46,8 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
5546
const pagedInstance = useRef<any>(null);
5647

5748
useEffect(() => {
49+
if (import.meta.env.SSR) return;
50+
5851
let isMounted = true;
5952
// Use a simple ID to track the latest render request
6053
const renderId = Date.now();
@@ -66,6 +59,32 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
6659
const processAndRender = async () => {
6760
if (!containerRef.current) return;
6861

62+
const [
63+
ReactDOMServer,
64+
showdownMod,
65+
mermaidMod,
66+
katexMod,
67+
vega,
68+
vegaLite,
69+
pagedjs,
70+
] = await Promise.all([
71+
import('react-dom/server.browser'),
72+
import('showdown'),
73+
import('mermaid'),
74+
import('katex'),
75+
// These modules are browser-safe. Keeping them dynamic avoids SSR bundling issues (e.g. node-canvas).
76+
// @ts-ignore
77+
import('vega'),
78+
// @ts-ignore
79+
import('vega-lite'),
80+
import('pagedjs'),
81+
]);
82+
83+
const showdown = (showdownMod as any).default ?? showdownMod;
84+
const mermaid = (mermaidMod as any).default ?? mermaidMod;
85+
const katex = (katexMod as any).default ?? katexMod;
86+
const Previewer = (pagedjs as any).Previewer ?? (pagedjs as any).default?.Previewer;
87+
6988
// 1. Pre-process Vega Lite
7089
let processedText = markdown;
7190
const vegaRegex = /<div class="vega-lite"([^>]*)>([\s\S]*?)<\/div>/g;
@@ -76,8 +95,8 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
7695
if (!isMounted) return { original: '', replacement: '' };
7796
try {
7897
const spec = JSON.parse(match[2].trim());
79-
const vegaSpec = vegaLite.compile(spec).spec;
80-
const view = new vega.View(vega.parse(vegaSpec), { renderer: 'svg' });
98+
const vegaSpec = (vegaLite as any).compile(spec).spec;
99+
const view = new (vega as any).View((vega as any).parse(vegaSpec), { renderer: 'svg' });
81100
const svg = await view.toSVG();
82101
return { original: match[0], replacement: `<div class="vega-container"${match[1]}>${svg}</div>` };
83102
} catch (e: any) {
@@ -95,14 +114,14 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
95114
}
96115

97116
// 2. Pre-process Mermaid
98-
mermaid.initialize({ startOnLoad: false, theme: 'base' });
117+
(mermaid as any).initialize({ startOnLoad: false, theme: 'base' });
99118
const mermaidRegex = /<div class="mermaid"([^>]*)>([\s\S]*?)<\/div>/g;
100119
const mermaidMatches = Array.from(processedText.matchAll(mermaidRegex));
101120
const mermaidResults = await Promise.all(
102121
mermaidMatches.map(async (match, i) => {
103122
if (!isMounted) return { original: '', replacement: '' };
104123
try {
105-
const { svg } = await mermaid.render(`mermaid-svg-${Date.now()}-${i}`, match[2].trim());
124+
const { svg } = await (mermaid as any).render(`mermaid-svg-${Date.now()}-${i}`, match[2].trim());
106125
return { original: match[0], replacement: `<div class="mermaid-container"${match[1]}>${svg}</div>` };
107126
} catch (e) { return { original: match[0], replacement: `<div class="mermaid-error">Diagram Error</div>` }; }
108127
})
@@ -129,7 +148,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
129148
try {
130149
// Render the static SVG markup to ensure Paged.js sees the correct dimensions.
131150
// onEdit is undefined here, so no button, but sizing is correct.
132-
const staticHtml = ReactDOMServer.renderToStaticMarkup(
151+
const staticHtml = (ReactDOMServer as any).renderToStaticMarkup(
133152
<ArrowGramStatic spec={spec} />
134153
);
135154
return `<div id="${id}" class="arrowgram-hydrate-target">${staticHtml}</div>`;
@@ -158,7 +177,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
158177
processedText = processedText.replace(/\$(?!\$)(?:\\.|[^$\\\n])+\$/g, protectMath);
159178

160179
// 5. Markdown -> HTML
161-
const converter = new showdown.Converter({
180+
const converter = new (showdown as any).Converter({
162181
metadata: true,
163182
noHeaderId: true,
164183
literalMidWordUnderscores: true
@@ -189,7 +208,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
189208
.replace(/\\\\([A-Za-z_])/g, '\\$1')
190209
.replace(/\\\\([,;:.!])/g, '\\$1');
191210
try {
192-
return `<span class="katex-display">${katex.renderToString(cleaned, { displayMode: true, throwOnError: false })}</span>`;
211+
return `<span class="katex-display">${(katex as any).renderToString(cleaned, { displayMode: true, throwOnError: false })}</span>`;
193212
} catch (e: any) { return `<span class="katex-error">${e.message}</span>`; }
194213
});
195214
// Inline mode: $...$ (non-greedy)
@@ -199,7 +218,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
199218
.replace(/\\\\([A-Za-z_])/g, '\\$1')
200219
.replace(/\\\\([,;:.!])/g, '\\$1');
201220
try {
202-
return katex.renderToString(cleaned, { displayMode: false, throwOnError: false });
221+
return (katex as any).renderToString(cleaned, { displayMode: false, throwOnError: false });
203222
} catch (e: any) { return `<span class="katex-error">${e.message}</span>`; }
204223
});
205224

@@ -229,7 +248,7 @@ export const PreviewController = ({ markdown, isTwoColumn, onEditDiagram }: Prev
229248
const katexCss = document.querySelector('link[href*="katex"]')?.getAttribute('href') || "https://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css";
230249
stylesheets.push(katexCss);
231250

232-
const paged = new Previewer();
251+
const paged = new (Previewer as any)();
233252
pagedInstance.current = paged;
234253

235254
paged.preview(finalHtml, stylesheets, containerRef.current).then(() => {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import '../styles.css';
3+
import { ToastProvider } from '../context/ToastContext';
4+
import { PaperEditor } from '../components/PaperEditor/PaperEditor';
5+
6+
export type ArrowgramPaperEditorProps = React.ComponentProps<typeof PaperEditor>;
7+
8+
export function ArrowgramPaperEditor(props: ArrowgramPaperEditorProps) {
9+
return (
10+
<ToastProvider>
11+
<PaperEditor {...props} />
12+
</ToastProvider>
13+
);
14+
}
15+

packages/web/src/embed/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './ArrowgramPaperEditor';
2+

packages/web/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
export * from './preview';
22
export * from './adapters';
3+
export * from './embed';
4+
export * from './context/ToastContext';

0 commit comments

Comments
 (0)