@pretextbook/web-editor 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,12 +19,17 @@ import '@pretextbook/web-editor/dist/web-editor.css';
19
19
 
20
20
  function App() {
21
21
  const [content, setContent] = useState('');
22
+ const [sourceFormat, setSourceFormat] = useState<'pretext' | 'latex'>('pretext');
22
23
  const [title, setTitle] = useState('My Document');
23
24
 
24
25
  return (
25
26
  <Editors
26
27
  content={content}
27
- onContentChange={setContent}
28
+ sourceFormat={sourceFormat}
29
+ onContentChange={(value, meta) => {
30
+ setContent(value || '');
31
+ setSourceFormat(meta?.sourceFormat || 'pretext');
32
+ }}
28
33
  title={title}
29
34
  onTitleChange={setTitle}
30
35
  onSaveButton={() => console.log('Save clicked')}
@@ -63,18 +68,45 @@ All button styles, layout, and MenuBar styling will work automatically without a
63
68
  The `Editors` component accepts the following props:
64
69
 
65
70
  ```tsx
71
+ type SourceFormat = 'pretext' | 'latex';
72
+
73
+ interface EditorContentChange {
74
+ sourceContent: string;
75
+ sourceFormat: SourceFormat;
76
+ pretextContent?: string;
77
+ pretextError?: string;
78
+ }
79
+
66
80
  interface editorProps {
67
- content: string; // The current PreTeXt content
68
- onContentChange: (value: string | undefined) => void; // Called when content changes
81
+ content: string; // The canonical source content
82
+ sourceFormat?: SourceFormat; // The canonical source format
83
+ pretextContent?: string; // Optional derived PreTeXt for previewing LaTeX sources
84
+ onContentChange: (value: string | undefined, meta?: EditorContentChange) => void;
69
85
  title?: string; // Document title
70
86
  onTitleChange?: (value: string) => void; // Called when title changes
71
87
  onSaveButton?: () => void; // Save button callback
72
88
  saveButtonLabel?: string; // Custom save button text
73
89
  onCancelButton?: () => void; // Cancel button callback
74
90
  cancelButtonLabel?: string; // Custom cancel button text
91
+ onSave?: () => void; // Keyboard save callback
92
+ onPreviewRebuild?: (content: string, title: string, postToIframe: (url: string, data: any) => void) => void;
75
93
  }
76
94
  ```
77
95
 
96
+ For LaTeX-authored documents, the editor can now derive previewable PreTeXt on the fly. The simple preview remains read-only until the document is explicitly converted to PreTeXt source.
97
+
98
+ When a document is in LaTeX mode, the toolbar exposes a `Convert to PreTeXt` action. That action replaces the canonical source with the latest generated PreTeXt so the visual editor can become editable.
99
+
100
+ ### Persistence recommendation
101
+
102
+ For consuming apps, the recommended storage model is:
103
+
104
+ - store one canonical source: `content` plus `sourceFormat`
105
+ - optionally store derived `pretextContent` as a cache for LaTeX-authored documents
106
+ - do not treat LaTeX and PreTeXt as two independently editable canonical sources
107
+
108
+ Once a user clicks `Convert to PreTeXt`, the canonical source should become PreTeXt. If your product needs an audit trail, keep the original LaTeX separately in your own persistence layer.
109
+
78
110
  ## Features
79
111
 
80
112
  - **Visual Editor**: Intuitive WYSIWYG editor for PreTeXt documents using Tiptap
@@ -1,8 +1,42 @@
1
+ import type { SourceFormat } from "../types/editor";
1
2
  interface CodeEditorProps {
3
+ /** The current source content to display. */
2
4
  content: string;
5
+ /** Determines the Monaco language mode (`"xml"` for PreTeXt, `"latex"` for LaTeX). */
6
+ sourceFormat: SourceFormat;
7
+ /** Called (debounced 500 ms) whenever the user edits the content. */
3
8
  onChange: (value: string | undefined) => void;
9
+ /** If provided, Ctrl+Enter in the editor triggers this callback. */
4
10
  onRebuild?: () => void;
11
+ /** If provided, Ctrl+S in the editor triggers this callback. */
5
12
  onSave?: () => void;
13
+ /**
14
+ * If provided, a "Convert to PreTeXt" button is shown in the toolbar.
15
+ * Called when the user clicks to promote the derived PreTeXt to the canonical source.
16
+ */
17
+ onConvertToPretext?: () => void;
18
+ /**
19
+ * Controls whether the "Convert to PreTeXt" button is enabled.
20
+ * Should be `false` when conversion has failed.
21
+ */
22
+ canConvertToPretext?: boolean;
23
+ /**
24
+ * The already-converted PreTeXt content. Shown in the confirmation dialog
25
+ * so the user can review before committing. Only meaningful when
26
+ * `sourceFormat` is `"latex"`.
27
+ */
28
+ pretextContent?: string;
6
29
  }
7
- declare const CodeEditor: ({ content, onChange, onRebuild, onSave }: CodeEditorProps) => import("react/jsx-runtime").JSX.Element;
30
+ /**
31
+ * Monaco-based code editor with an attached toolbar.
32
+ *
33
+ * Manages its own undo/redo state so the toolbar buttons stay in sync with
34
+ * the editor model. `onRebuild` and `onSave` callbacks are stored in refs
35
+ * so keyboard shortcuts registered at mount time always call the latest
36
+ * version without needing to re-register.
37
+ *
38
+ * Content is synced from props only when the prop value differs from what the
39
+ * editor model already contains, to prevent cursor jumps on re-render.
40
+ */
41
+ declare const CodeEditor: ({ content, sourceFormat, onChange, onRebuild, onSave, onConvertToPretext, canConvertToPretext, pretextContent, }: CodeEditorProps) => import("react/jsx-runtime").JSX.Element;
8
42
  export default CodeEditor;
@@ -1,12 +1,33 @@
1
1
  import React from "react";
2
2
  import "./CodeEditorMenu.css";
3
+ import type { SourceFormat } from "../types/editor";
3
4
  interface CodeEditorMenuProps {
5
+ /** Current source content; passed to the formatter when the user clicks "Format PreTeXt". */
4
6
  content: string;
7
+ /** Determines which toolbar actions are available (e.g. formatting is PreTeXt-only). */
8
+ sourceFormat: SourceFormat;
9
+ /** Called with the formatted content after a successful format operation. */
5
10
  onContentChange: (newContent: string) => void;
11
+ /** Called when the user clicks "Import LaTeX" to open the import dialog. */
12
+ onOpenLatexImport: () => void;
13
+ /** Triggers an undo in the Monaco editor. Passed through from the parent. */
6
14
  onUndo: () => void;
15
+ /** Triggers a redo in the Monaco editor. Passed through from the parent. */
7
16
  onRedo: () => void;
17
+ /** Whether the undo button should be enabled. */
8
18
  canUndo: boolean;
19
+ /** Whether the redo button should be enabled. */
9
20
  canRedo: boolean;
21
+ /**
22
+ * If provided, a "Convert to PreTeXt" button is shown.
23
+ * Called when the user clicks to promote the derived PreTeXt to the canonical source.
24
+ */
25
+ onConvertToPretext?: () => void;
26
+ /**
27
+ * Controls whether the "Convert to PreTeXt" button is enabled.
28
+ * Should be `false` when conversion has failed.
29
+ */
30
+ canConvertToPretext?: boolean;
10
31
  }
11
32
  declare const CodeEditorMenu: React.FC<CodeEditorMenuProps>;
12
33
  export default CodeEditorMenu;
@@ -0,0 +1,18 @@
1
+ import "./LatexImportDialog.css";
2
+ interface ConvertToPretextDialogProps {
3
+ /** The current LaTeX source to display (read-only) on the left. */
4
+ latexSource: string;
5
+ /** The already-converted PreTeXt to display (read-only) on the right. */
6
+ pretextContent: string;
7
+ /** Called when the user confirms the conversion. */
8
+ onConfirm: () => void;
9
+ /** Called when the dialog should close without converting. */
10
+ onClose: () => void;
11
+ }
12
+ /**
13
+ * Confirmation dialog shown before permanently replacing LaTeX source with
14
+ * the derived PreTeXt. Displays the current LaTeX and the converted PreTeXt
15
+ * side-by-side so the user can review before committing.
16
+ */
17
+ declare const ConvertToPretextDialog: ({ latexSource, pretextContent, onConfirm, onClose, }: ConvertToPretextDialogProps) => import("react/jsx-runtime").JSX.Element;
18
+ export default ConvertToPretextDialog;
@@ -1,15 +1,68 @@
1
1
  import "./Editors.css";
2
+ import type { EditorContentChange, SourceFormat } from "../types/editor";
2
3
  export interface editorProps {
4
+ /** The source content string (PreTeXt XML or LaTeX). */
3
5
  content: string;
4
- onContentChange: (value: string | undefined) => void;
6
+ /**
7
+ * The format of `content`. Defaults to `"pretext"` when omitted.
8
+ * When set to `"latex"`, the editor displays a LaTeX code editor and
9
+ * derives a read-only PreTeXt preview via conversion.
10
+ */
11
+ sourceFormat?: SourceFormat;
12
+ /**
13
+ * Pre-computed PreTeXt XML corresponding to `content`.
14
+ * Providing this avoids running the conversion on first render when the
15
+ * host already has a cached result. Only meaningful when
16
+ * `sourceFormat` is not `"pretext"`.
17
+ */
18
+ pretextContent?: string;
19
+ /**
20
+ * Called whenever the source content changes (user edits in the code
21
+ * editor or WYSIWYG editor).
22
+ *
23
+ * @param value - The new source string (`undefined` is passed by Monaco on
24
+ * certain edge cases; treat it as an empty string).
25
+ * @param meta - The full derived {@link EditorContentChange} state at the
26
+ * time of the change, including the converted PreTeXt and any error.
27
+ */
28
+ onContentChange: (value: string | undefined, meta?: EditorContentChange) => void;
29
+ /** Document title shown in the menu bar title field. */
5
30
  title?: string;
31
+ /** Called when the user edits the title field. */
6
32
  onTitleChange?: (value: string) => void;
33
+ /** If provided, a Save button is rendered in the menu bar. */
7
34
  onSaveButton?: () => void;
35
+ /** Label for the Save button. Defaults to `"Save"`. */
8
36
  saveButtonLabel?: string;
37
+ /** If provided, a Cancel button is rendered in the menu bar. */
9
38
  onCancelButton?: () => void;
39
+ /** Label for the Cancel button. Defaults to `"Cancel"`. */
10
40
  cancelButtonLabel?: string;
41
+ /**
42
+ * If provided, `onSave` is called on Ctrl+S in addition to `onSaveButton`.
43
+ * Useful when the host wants a keyboard shortcut to trigger saving without
44
+ * necessarily showing an explicit Save button.
45
+ */
11
46
  onSave?: () => void;
47
+ /**
48
+ * If provided, the right-hand panel shows a full iframe-based preview
49
+ * instead of the Tiptap visual editor, and a rebuild button / Ctrl+Enter
50
+ * shortcut become active.
51
+ *
52
+ * @param content - The current PreTeXt XML to render.
53
+ * @param title - The current document title.
54
+ * @param postToIframe - Helper to post a message into the preview iframe.
55
+ */
12
56
  onPreviewRebuild?: (content: string, title: string, postToIframe: (url: string, data: any) => void) => void;
13
57
  }
58
+ /**
59
+ * Top-level editor component that wires together the Monaco code editor and
60
+ * the right-hand preview panel (either Tiptap visual editor or full iframe
61
+ * preview). Also owns the menu bar and responsive layout logic.
62
+ *
63
+ * Content state is derived from props on every render via `useMemo` so the
64
+ * parent always controls the source of truth; the component itself holds no
65
+ * long-lived content state.
66
+ */
14
67
  declare const Editors: (props: editorProps) => import("react/jsx-runtime").JSX.Element;
15
68
  export default Editors;
@@ -0,0 +1,14 @@
1
+ import "./LatexImportDialog.css";
2
+ interface LatexImportDialogProps {
3
+ /** Called when the dialog should close (Cancel button, Escape key, or after "Copy and Close"). */
4
+ onClose: () => void;
5
+ }
6
+ /**
7
+ * Modal dialog that lets the user paste, open, or drag-and-drop a `.tex` file,
8
+ * convert it to PreTeXt, and copy the result to the clipboard.
9
+ *
10
+ * The dialog does not modify the editor content directly; it relies on the
11
+ * user copying the output and pasting it wherever needed.
12
+ */
13
+ declare const LatexImportDialog: ({ onClose }: LatexImportDialogProps) => import("react/jsx-runtime").JSX.Element;
14
+ export default LatexImportDialog;
@@ -1,14 +1,29 @@
1
+ import "./MenuBar.css";
1
2
  export interface MenuBarProps {
3
+ /**
4
+ * Whether the "Full" preview mode is active.
5
+ * The toggle slider reflects `isChecked === true` → Full, `false` → Simple.
6
+ */
2
7
  isChecked: boolean;
8
+ /** Called when the user clicks the Simple/Full preview mode toggle. */
3
9
  onChange: () => void;
10
+ /** Current document title shown in the editable title field. */
4
11
  title?: string;
12
+ /** Called when the user changes the title field value. */
5
13
  onTitleChange?: (value: string) => void;
14
+ /** If provided, a Save button is rendered. */
6
15
  onSaveButton?: () => void;
16
+ /** Label for the Save button. Defaults to `"Save"`. */
7
17
  saveButtonLabel?: string;
18
+ /** If provided, a Cancel button is rendered. */
8
19
  onCancelButton?: () => void;
20
+ /** Label for the Cancel button. Defaults to `"Cancel"`. */
9
21
  cancelButtonLabel?: string;
22
+ /**
23
+ * When `false`, the Simple/Full preview mode toggle is hidden entirely.
24
+ * Defaults to showing the toggle.
25
+ */
10
26
  showPreviewModeToggle?: boolean;
11
27
  }
12
- import "./MenuBar.css";
13
28
  declare const MenuBar: (props: MenuBarProps) => import("react/jsx-runtime").JSX.Element;
14
29
  export default MenuBar;
@@ -2,12 +2,35 @@ import "katex/dist/katex.min.css";
2
2
  import "../styles.scss";
3
3
  import "./VisualEditor.css";
4
4
  interface VisualEditorProps {
5
+ /** PreTeXt XML string to render and (optionally) edit. */
5
6
  content: string;
7
+ /**
8
+ * Called (debounced 500 ms) with the updated PreTeXt XML whenever the user
9
+ * edits content. Only fired when editing is enabled.
10
+ */
6
11
  onChange: (html: string) => void;
12
+ /**
13
+ * Whether editing is permitted. Defaults to `true`.
14
+ * When `false`, the "Edit" toggle is hidden and the editor stays read-only.
15
+ */
16
+ canEdit?: boolean;
17
+ /**
18
+ * Message displayed instead of the "Edit" toggle when `canEdit` is `false`.
19
+ * Explains to the user why editing is unavailable (e.g. LaTeX source mode).
20
+ */
21
+ editDisabledReason?: string;
7
22
  }
8
- interface VisualEditorProps {
9
- content: string;
10
- onChange: (html: string) => void;
11
- }
12
- declare const VisualEditor: ({ content, onChange }: VisualEditorProps) => import("react/jsx-runtime").JSX.Element;
23
+ /**
24
+ * Tiptap-based visual (WYSIWYG) preview/editor for PreTeXt content.
25
+ *
26
+ * In read-only mode it renders a styled preview of the PreTeXt XML.
27
+ * When the user enables the "Edit" toggle, editing becomes active and
28
+ * changes are serialised back to PreTeXt via `json2ptx` and reported to
29
+ * the parent via `onChange`.
30
+ *
31
+ * External content changes (e.g. the user typing in the code editor) are
32
+ * applied via `setContent` only when the update did not originate inside
33
+ * this component, preventing feedback loops.
34
+ */
35
+ declare const VisualEditor: ({ content, onChange, canEdit, editDisabledReason, }: VisualEditorProps) => import("react/jsx-runtime").JSX.Element;
13
36
  export default VisualEditor;
@@ -0,0 +1,46 @@
1
+ import type { SourceFormat } from "./types/editor";
2
+ /** Returned by {@link derivePretextContent}. Exactly one of the two fields will be set. */
3
+ export interface DerivedPretextResult {
4
+ /** The converted (or pass-through) PreTeXt XML string. */
5
+ pretextContent?: string;
6
+ /** Human-readable error when conversion fails. */
7
+ pretextError?: string;
8
+ }
9
+ /**
10
+ * Inspects `content` and returns the most likely {@link SourceFormat}.
11
+ *
12
+ * Rules (applied in order):
13
+ * 1. Empty/whitespace-only → `"pretext"` (safe default).
14
+ * 2. Starts with `<` → `"pretext"` (XML document).
15
+ * 3. Contains any {@link LATEX_FORMAT_MARKERS} → `"latex"`.
16
+ * 4. Otherwise → `"pretext"`.
17
+ */
18
+ export declare function detectSourceFormat(content: string): SourceFormat;
19
+ /**
20
+ * Converts a LaTeX document string to formatted PreTeXt XML.
21
+ *
22
+ * @param latexContent - The raw LaTeX source to convert.
23
+ * @returns The formatted PreTeXt XML string, or `""` if `latexContent` is blank.
24
+ * @throws If the underlying `latexToPretext` conversion throws.
25
+ */
26
+ export declare function convertLatexToPretext(latexContent: string): string;
27
+ /**
28
+ * Normalises an unknown thrown value into a displayable error message.
29
+ *
30
+ * @param error - The value caught in a `catch` block.
31
+ * @returns A non-empty string suitable for showing to the user.
32
+ */
33
+ export declare function getConversionErrorMessage(error: unknown): string;
34
+ /**
35
+ * Derives PreTeXt content from `sourceContent` according to `sourceFormat`.
36
+ *
37
+ * - If `sourceFormat` is `"pretext"`, the content is returned as-is.
38
+ * - If `sourceFormat` is `"latex"`, the content is converted via
39
+ * {@link convertLatexToPretext}. Conversion errors are caught and returned
40
+ * as `pretextError` so callers never need a try/catch.
41
+ *
42
+ * @param sourceContent - The raw source string.
43
+ * @param sourceFormat - The format of `sourceContent`.
44
+ * @returns A {@link DerivedPretextResult} with either `pretextContent` or `pretextError`.
45
+ */
46
+ export declare function derivePretextContent(sourceContent: string, sourceFormat: SourceFormat): DerivedPretextResult;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import './index.css';
2
2
  export { default as Editors } from './components/Editors';
3
3
  export type { editorProps } from './components/Editors';
4
+ export { convertLatexToPretext, derivePretextContent, detectSourceFormat, } from './contentConversion';
5
+ export type { EditorContentChange, EditorContentState, SourceFormat, } from './types/editor';
4
6
  export { default as CodeEditor } from './components/CodeEditor';
5
7
  export { default as VisualEditor } from './components/VisualEditor';
6
8
  export { default as FullPreview } from './components/FullPreview';