@omnitron-dev/prism 0.3.0 → 0.3.1

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.
Files changed (214) hide show
  1. package/README.md +148 -0
  2. package/dist/blocks/index.js +3 -1
  3. package/dist/blocks/index.js.map +1 -1
  4. package/dist/{chunk-S6SEGBX6.js → chunk-472SOF76.js} +4 -4
  5. package/dist/{chunk-S6SEGBX6.js.map → chunk-472SOF76.js.map} +1 -1
  6. package/dist/{chunk-Z5OSPAOC.js → chunk-4Z5DE5GK.js} +78 -75
  7. package/dist/chunk-4Z5DE5GK.js.map +1 -0
  8. package/dist/chunk-52PQL67Q.js +559 -0
  9. package/dist/chunk-52PQL67Q.js.map +1 -0
  10. package/dist/chunk-75FQ5IW3.js +590 -0
  11. package/dist/chunk-75FQ5IW3.js.map +1 -0
  12. package/dist/{chunk-6AHI4GSV.js → chunk-7G5D5PTC.js} +4 -3
  13. package/dist/chunk-7G5D5PTC.js.map +1 -0
  14. package/dist/chunk-BJE2AKD4.js +105 -0
  15. package/dist/chunk-BJE2AKD4.js.map +1 -0
  16. package/dist/chunk-FF5RF32O.js +248 -0
  17. package/dist/chunk-FF5RF32O.js.map +1 -0
  18. package/dist/{chunk-OBJ244BO.js → chunk-GMIE6III.js} +9 -42
  19. package/dist/chunk-GMIE6III.js.map +1 -0
  20. package/dist/chunk-GUGTDURV.js +826 -0
  21. package/dist/chunk-GUGTDURV.js.map +1 -0
  22. package/dist/chunk-HOXFSQUV.js +573 -0
  23. package/dist/chunk-HOXFSQUV.js.map +1 -0
  24. package/dist/chunk-JSQTYDPG.js +3 -0
  25. package/dist/chunk-JSQTYDPG.js.map +1 -0
  26. package/dist/{chunk-ZTATTUH7.js → chunk-KQ67XB6T.js} +213 -651
  27. package/dist/chunk-KQ67XB6T.js.map +1 -0
  28. package/dist/{chunk-WFD54GQQ.js → chunk-LOY2NPCO.js} +14 -573
  29. package/dist/chunk-LOY2NPCO.js.map +1 -0
  30. package/dist/{chunk-YQW43BDH.js → chunk-LSPFFEJT.js} +240 -1824
  31. package/dist/chunk-LSPFFEJT.js.map +1 -0
  32. package/dist/{chunk-BZOL7GMU.js → chunk-OKTLA23Q.js} +4 -4
  33. package/dist/chunk-OKTLA23Q.js.map +1 -0
  34. package/dist/chunk-PFT2BNYO.js +191 -0
  35. package/dist/chunk-PFT2BNYO.js.map +1 -0
  36. package/dist/chunk-PGHEP4JD.js +106 -0
  37. package/dist/chunk-PGHEP4JD.js.map +1 -0
  38. package/dist/chunk-PXXPLHIY.js +74 -0
  39. package/dist/chunk-PXXPLHIY.js.map +1 -0
  40. package/dist/chunk-SU3LVQ7N.js +518 -0
  41. package/dist/chunk-SU3LVQ7N.js.map +1 -0
  42. package/dist/chunk-YHE7HXSG.js +206 -0
  43. package/dist/chunk-YHE7HXSG.js.map +1 -0
  44. package/dist/cli/index.js +7 -2
  45. package/dist/cli/index.js.map +1 -1
  46. package/dist/components/accordion/accordion.d.ts.map +1 -1
  47. package/dist/components/admin-filters/admin-data-table.d.ts.map +1 -1
  48. package/dist/components/admin-filters/filter-toolbar.d.ts.map +1 -1
  49. package/dist/components/date-input/date-input.d.ts +27 -0
  50. package/dist/components/date-input/date-input.d.ts.map +1 -0
  51. package/dist/components/date-input/index.d.ts +4 -0
  52. package/dist/components/date-input/index.d.ts.map +1 -0
  53. package/dist/components/date-input/value.d.ts +9 -0
  54. package/dist/components/date-input/value.d.ts.map +1 -0
  55. package/dist/components/doc-layout/doc-sidebar.d.ts +4 -1
  56. package/dist/components/doc-layout/doc-sidebar.d.ts.map +1 -1
  57. package/dist/components/doc-layout/index.js +6 -0
  58. package/dist/components/doc-layout/index.js.map +1 -0
  59. package/dist/components/editor/editor.d.ts.map +1 -1
  60. package/dist/components/editor/icons/index.d.ts +32 -40
  61. package/dist/components/editor/icons/index.d.ts.map +1 -1
  62. package/dist/components/editor/index.js +3 -1
  63. package/dist/components/error-boundary/error-boundary.d.ts.map +1 -1
  64. package/dist/components/index.css +4 -0
  65. package/dist/components/index.css.map +1 -1
  66. package/dist/components/index.d.ts +4 -4
  67. package/dist/components/index.d.ts.map +1 -1
  68. package/dist/components/index.js +18 -8
  69. package/dist/components/knowledge-graph/edge-labels.d.ts +14 -0
  70. package/dist/components/knowledge-graph/edge-labels.d.ts.map +1 -0
  71. package/dist/components/knowledge-graph/graph.d.ts +56 -0
  72. package/dist/components/knowledge-graph/graph.d.ts.map +1 -0
  73. package/dist/components/knowledge-graph/index.css +176 -0
  74. package/dist/components/knowledge-graph/index.css.map +1 -0
  75. package/dist/components/knowledge-graph/index.d.ts +3 -0
  76. package/dist/components/knowledge-graph/index.d.ts.map +1 -0
  77. package/dist/components/knowledge-graph/index.js +795 -0
  78. package/dist/components/knowledge-graph/index.js.map +1 -0
  79. package/dist/components/knowledge-graph/label-layout.d.ts +28 -0
  80. package/dist/components/knowledge-graph/label-layout.d.ts.map +1 -0
  81. package/dist/components/knowledge-graph/label-store.d.ts +15 -0
  82. package/dist/components/knowledge-graph/label-store.d.ts.map +1 -0
  83. package/dist/components/knowledge-graph/model.d.ts +92 -0
  84. package/dist/components/knowledge-graph/model.d.ts.map +1 -0
  85. package/dist/components/lightbox/lightbox.d.ts.map +1 -1
  86. package/dist/components/map/Map.d.ts +19 -43
  87. package/dist/components/map/Map.d.ts.map +1 -1
  88. package/dist/components/map/MapCoverageLayer.d.ts +1 -1
  89. package/dist/components/map/MapCoverageLayer.d.ts.map +1 -1
  90. package/dist/components/map/MapMarker.d.ts +1 -1
  91. package/dist/components/map/MapMarker.d.ts.map +1 -1
  92. package/dist/components/map/MapPlaceSearch.d.ts +33 -0
  93. package/dist/components/map/MapPlaceSearch.d.ts.map +1 -0
  94. package/dist/components/map/MapPointPicker.d.ts +21 -12
  95. package/dist/components/map/MapPointPicker.d.ts.map +1 -1
  96. package/dist/components/map/coordinates.d.ts +10 -0
  97. package/dist/components/map/coordinates.d.ts.map +1 -0
  98. package/dist/components/map/index.css +4 -0
  99. package/dist/components/map/index.css.map +1 -0
  100. package/dist/components/map/index.d.ts +7 -2
  101. package/dist/components/map/index.d.ts.map +1 -1
  102. package/dist/components/map/index.js +3 -0
  103. package/dist/components/map/index.js.map +1 -0
  104. package/dist/components/nav-card/nav-card.d.ts.map +1 -1
  105. package/dist/components/rail/rail.d.ts.map +1 -1
  106. package/dist/components/research/diagram-image.d.ts +11 -0
  107. package/dist/components/research/diagram-image.d.ts.map +1 -0
  108. package/dist/components/research/diagram.d.ts +8 -0
  109. package/dist/components/research/diagram.d.ts.map +1 -0
  110. package/dist/components/research/document.d.ts +33 -0
  111. package/dist/components/research/document.d.ts.map +1 -0
  112. package/dist/components/research/frontmatter.d.ts +15 -0
  113. package/dist/components/research/frontmatter.d.ts.map +1 -0
  114. package/dist/components/research/index.css +51 -0
  115. package/dist/components/research/index.css.map +1 -0
  116. package/dist/components/research/index.d.ts +3 -0
  117. package/dist/components/research/index.d.ts.map +1 -0
  118. package/dist/components/research/index.js +6 -0
  119. package/dist/components/research/index.js.map +1 -0
  120. package/dist/components/research/parse.d.ts +29 -0
  121. package/dist/components/research/parse.d.ts.map +1 -0
  122. package/dist/components/research-editor/block-commands.d.ts +20 -0
  123. package/dist/components/research-editor/block-commands.d.ts.map +1 -0
  124. package/dist/components/research-editor/block-controls.d.ts +13 -0
  125. package/dist/components/research-editor/block-controls.d.ts.map +1 -0
  126. package/dist/components/research-editor/editor.d.ts +75 -0
  127. package/dist/components/research-editor/editor.d.ts.map +1 -0
  128. package/dist/components/research-editor/extensions.d.ts +14 -0
  129. package/dist/components/research-editor/extensions.d.ts.map +1 -0
  130. package/dist/components/research-editor/index.css +332 -0
  131. package/dist/components/research-editor/index.css.map +1 -0
  132. package/dist/components/research-editor/index.d.ts +5 -0
  133. package/dist/components/research-editor/index.d.ts.map +1 -0
  134. package/dist/components/research-editor/index.js +1592 -0
  135. package/dist/components/research-editor/index.js.map +1 -0
  136. package/dist/components/research-editor/math-input.d.ts +32 -0
  137. package/dist/components/research-editor/math-input.d.ts.map +1 -0
  138. package/dist/components/research-editor/math-markup.d.ts +4 -0
  139. package/dist/components/research-editor/math-markup.d.ts.map +1 -0
  140. package/dist/components/research-editor/math-navigation.d.ts +6 -0
  141. package/dist/components/research-editor/math-navigation.d.ts.map +1 -0
  142. package/dist/components/research-editor/math-node.d.ts +11 -0
  143. package/dist/components/research-editor/math-node.d.ts.map +1 -0
  144. package/dist/components/research-editor/source.d.ts +19 -0
  145. package/dist/components/research-editor/source.d.ts.map +1 -0
  146. package/dist/components/scrollbar/scrollbar.d.ts +4 -2
  147. package/dist/components/scrollbar/scrollbar.d.ts.map +1 -1
  148. package/dist/components/search-input/search-input.d.ts +0 -10
  149. package/dist/components/search-input/search-input.d.ts.map +1 -1
  150. package/dist/components/tiptap-renderer/tiptap-renderer.d.ts +4 -3
  151. package/dist/components/tiptap-renderer/tiptap-renderer.d.ts.map +1 -1
  152. package/dist/core/index.d.ts +1 -1
  153. package/dist/core/index.d.ts.map +1 -1
  154. package/dist/core/index.js +8 -4
  155. package/dist/core/provider.d.ts +7 -2
  156. package/dist/core/provider.d.ts.map +1 -1
  157. package/dist/forms/schema-provider.d.ts +3 -3
  158. package/dist/forms/schema-utils.d.ts +3 -3
  159. package/dist/hooks/index.js +9 -5
  160. package/dist/icons/context.d.ts +17 -0
  161. package/dist/icons/context.d.ts.map +1 -0
  162. package/dist/icons/controls.d.ts +51 -0
  163. package/dist/icons/controls.d.ts.map +1 -0
  164. package/dist/icons/index.d.ts +3 -0
  165. package/dist/icons/index.d.ts.map +1 -0
  166. package/dist/icons/index.js +4 -0
  167. package/dist/icons/index.js.map +1 -0
  168. package/dist/icons/theme.d.ts +4 -0
  169. package/dist/icons/theme.d.ts.map +1 -0
  170. package/dist/index.css +4 -0
  171. package/dist/index.css.map +1 -1
  172. package/dist/index.js +24 -13
  173. package/dist/index.js.map +1 -1
  174. package/dist/layouts/core/header-section.d.ts.map +1 -1
  175. package/dist/layouts/dashboard/labels.d.ts +12 -0
  176. package/dist/layouts/dashboard/labels.d.ts.map +1 -0
  177. package/dist/layouts/dashboard/layout.d.ts +7 -1
  178. package/dist/layouts/dashboard/layout.d.ts.map +1 -1
  179. package/dist/layouts/dashboard/sidenav.d.ts +7 -3
  180. package/dist/layouts/dashboard/sidenav.d.ts.map +1 -1
  181. package/dist/layouts/index.d.ts +5 -0
  182. package/dist/layouts/index.d.ts.map +1 -1
  183. package/dist/layouts/index.js +6 -1
  184. package/dist/layouts/panel-layout.d.ts +35 -0
  185. package/dist/layouts/panel-layout.d.ts.map +1 -0
  186. package/dist/layouts/sidebar-toggle.d.ts +11 -0
  187. package/dist/layouts/sidebar-toggle.d.ts.map +1 -0
  188. package/dist/state/stores/auth.d.ts +4 -16
  189. package/dist/state/stores/auth.d.ts.map +1 -1
  190. package/dist/state/stores/ui.d.ts +7 -105
  191. package/dist/state/stores/ui.d.ts.map +1 -1
  192. package/dist/theme/components/Button.d.ts.map +1 -1
  193. package/dist/theme/components/FormControls.d.ts +3 -12
  194. package/dist/theme/components/FormControls.d.ts.map +1 -1
  195. package/dist/theme/components/index.d.ts.map +1 -1
  196. package/dist/theme/create-theme.d.ts +5 -17
  197. package/dist/theme/create-theme.d.ts.map +1 -1
  198. package/dist/theme/index.js +6 -2
  199. package/dist/theme/mixins.d.ts +3 -1
  200. package/dist/theme/mixins.d.ts.map +1 -1
  201. package/dist/theme/palette/overrides.d.ts +23 -0
  202. package/dist/theme/palette/overrides.d.ts.map +1 -0
  203. package/dist/ui/index.d.ts +18 -0
  204. package/dist/ui/index.d.ts.map +1 -0
  205. package/dist/ui/index.js +148 -0
  206. package/dist/ui/index.js.map +1 -0
  207. package/package.json +92 -45
  208. package/dist/chunk-6AHI4GSV.js.map +0 -1
  209. package/dist/chunk-BZOL7GMU.js.map +0 -1
  210. package/dist/chunk-OBJ244BO.js.map +0 -1
  211. package/dist/chunk-WFD54GQQ.js.map +0 -1
  212. package/dist/chunk-YQW43BDH.js.map +0 -1
  213. package/dist/chunk-Z5OSPAOC.js.map +0 -1
  214. package/dist/chunk-ZTATTUH7.js.map +0 -1
@@ -0,0 +1,1592 @@
1
+ import { researchLimits, ResearchDocument, parseResearchMarkdown, splitFrontmatter } from '../../chunk-SU3LVQ7N.js';
2
+ import '../../chunk-GUGTDURV.js';
3
+ import { KeyboardIcon, CheckIcon, AddIcon, DragIcon } from '../../chunk-PGHEP4JD.js';
4
+ import '../../chunk-BJE2AKD4.js';
5
+ import { createContext, useRef, useState, useEffect, useMemo, useImperativeHandle, useContext } from 'react';
6
+ import { getSchema, Node, Extension } from '@tiptap/core';
7
+ import { useEditor, useEditorState, EditorContent, ReactNodeViewRenderer, NodeViewWrapper } from '@tiptap/react';
8
+ import { PluginKey, NodeSelection, TextSelection, Plugin, Selection } from '@tiptap/pm/state';
9
+ import Box from '@mui/material/Box';
10
+ import Button2 from '@mui/material/Button';
11
+ import Dialog from '@mui/material/Dialog';
12
+ import DialogTitle from '@mui/material/DialogTitle';
13
+ import DialogContent from '@mui/material/DialogContent';
14
+ import DialogActions from '@mui/material/DialogActions';
15
+ import TextField2 from '@mui/material/TextField';
16
+ import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
17
+ import StarterKit from '@tiptap/starter-kit';
18
+ import TaskList from '@tiptap/extension-task-list';
19
+ import TaskItem from '@tiptap/extension-task-item';
20
+ import { TableKit } from '@tiptap/extension-table';
21
+ import { Decoration, DecorationSet } from '@tiptap/pm/view';
22
+ import { flushSync } from 'react-dom';
23
+ import { closeHistory } from '@tiptap/pm/history';
24
+ import katex from 'katex';
25
+ import IconButton from '@mui/material/IconButton';
26
+ import Tooltip from '@mui/material/Tooltip';
27
+ import GlobalStyles from '@mui/material/GlobalStyles';
28
+ import { useTheme } from '@mui/material/styles';
29
+ import DOMPurify from 'dompurify';
30
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
31
+ import { fromMarkdown } from 'mdast-util-from-markdown';
32
+ import { toMarkdown } from 'mdast-util-to-markdown';
33
+ import { gfm } from 'micromark-extension-gfm';
34
+ import { gfmToMarkdown, gfmFromMarkdown } from 'mdast-util-gfm';
35
+ import { math } from 'micromark-extension-math';
36
+ import { mathToMarkdown, mathFromMarkdown } from 'mdast-util-math';
37
+ import { directive } from 'micromark-extension-directive';
38
+ import { directiveFromMarkdown } from 'mdast-util-directive';
39
+ import { canSplit } from '@tiptap/pm/transform';
40
+ import Menu from '@mui/material/Menu';
41
+ import MenuItem from '@mui/material/MenuItem';
42
+
43
+ var layoutProperties = /* @__PURE__ */ new Set([
44
+ "color",
45
+ "background-color",
46
+ "opacity",
47
+ "height",
48
+ "width",
49
+ "min-width",
50
+ "max-width",
51
+ "min-height",
52
+ "max-height",
53
+ "top",
54
+ "bottom",
55
+ "left",
56
+ "right",
57
+ "vertical-align",
58
+ "margin",
59
+ "margin-top",
60
+ "margin-bottom",
61
+ "margin-left",
62
+ "margin-right",
63
+ "padding",
64
+ "padding-top",
65
+ "padding-bottom",
66
+ "padding-left",
67
+ "padding-right",
68
+ "border",
69
+ "border-top",
70
+ "border-bottom",
71
+ "border-left",
72
+ "border-right",
73
+ "border-color",
74
+ "border-width",
75
+ "border-style",
76
+ "border-radius",
77
+ "font-size",
78
+ "font-weight",
79
+ "font-style",
80
+ "line-height",
81
+ "letter-spacing",
82
+ "display",
83
+ "visibility",
84
+ "text-align",
85
+ "white-space",
86
+ "transform",
87
+ "transform-origin"
88
+ ]);
89
+ function sanitizeMathMarkup(html) {
90
+ const fragment = DOMPurify.sanitize(html, {
91
+ ADD_ATTR: ["contenteditable"],
92
+ ADD_TAGS: ["use"],
93
+ FORBID_TAGS: ["style", "link", "iframe", "object", "embed", "img", "image", "foreignObject"],
94
+ RETURN_DOM_FRAGMENT: true
95
+ });
96
+ for (const element of fragment.querySelectorAll("*")) {
97
+ for (const attribute of Array.from(element.attributes)) {
98
+ if (["href", "xlink:href", "src", "action", "formaction", "poster"].includes(attribute.name) && !(element.localName === "use" && /^#[\w-]+$/.test(attribute.value)))
99
+ element.removeAttribute(attribute.name);
100
+ }
101
+ }
102
+ for (const element of fragment.querySelectorAll("[style]")) {
103
+ const parsed = document.createElement("span").style;
104
+ parsed.cssText = element.getAttribute("style") ?? "";
105
+ const safe = document.createElement("span").style;
106
+ for (const name of Array.from(parsed)) {
107
+ if (layoutProperties.has(name)) safe.setProperty(name, parsed.getPropertyValue(name));
108
+ }
109
+ if (safe.cssText) element.setAttribute("style", safe.cssText);
110
+ else element.removeAttribute("style");
111
+ }
112
+ for (const element of fragment.querySelectorAll("[fill], [stroke]")) {
113
+ for (const name of ["fill", "stroke"]) {
114
+ const value = element.getAttribute(name);
115
+ if (value && /url|\\/i.test(value)) element.removeAttribute(name);
116
+ }
117
+ }
118
+ const wrapper = document.createElement("div");
119
+ wrapper.append(fragment);
120
+ return wrapper.innerHTML;
121
+ }
122
+ var defaultMathInputLabels = {
123
+ field: "Equation",
124
+ source: "LaTeX source",
125
+ keyboard: "Math keyboard",
126
+ done: "Return to document",
127
+ loading: "Loading equation editor\u2026",
128
+ unavailable: "Visual input unavailable. LaTeX editing is available.",
129
+ syntax: "Check the LaTeX syntax. The source is retained.",
130
+ limit: "Equation exceeds the configured limit."
131
+ };
132
+ var runtime;
133
+ var keyboardOwner;
134
+ function loadMathLive() {
135
+ return runtime ??= import('mathlive').then((module) => {
136
+ module.MathfieldElement.createHTML = sanitizeMathMarkup;
137
+ module.MathfieldElement.fontsDirectory = null;
138
+ module.MathfieldElement.soundsDirectory = null;
139
+ return module;
140
+ }).catch((error) => {
141
+ runtime = void 0;
142
+ throw error;
143
+ });
144
+ }
145
+ function MathInput(props) {
146
+ const labels = { ...defaultMathInputLabels, ...props.labels };
147
+ const theme = useTheme();
148
+ const host = useRef(null), field = useRef(void 0);
149
+ const editingSource = useRef(false);
150
+ const latest = useRef(props);
151
+ latest.current = props;
152
+ const [ready, setReady] = useState(false), [failed, setFailed] = useState(false);
153
+ const [source, setSource] = useState(false), [issue, setIssue] = useState();
154
+ function accept(value) {
155
+ const p = latest.current;
156
+ if (p.readOnly || value.length > (p.maxLength ?? 65536) || p.onChange(value) === false) {
157
+ if (!p.readOnly) setIssue("limit");
158
+ return false;
159
+ }
160
+ return true;
161
+ }
162
+ useEffect(() => {
163
+ let cancelled = false;
164
+ let dispose;
165
+ void loadMathLive().then(({ MathfieldElement }) => {
166
+ if (cancelled || !host.current) return;
167
+ const p = latest.current;
168
+ if (p.locale) MathfieldElement.locale = p.locale;
169
+ const mf = new MathfieldElement({
170
+ defaultMode: p.display ? "math" : "inline-math",
171
+ mathVirtualKeyboardPolicy: "manual",
172
+ ...p.options
173
+ });
174
+ field.current = mf;
175
+ mf.smartFence = false;
176
+ mf.removeExtraneousParentheses = false;
177
+ mf.readOnly = !!p.readOnly;
178
+ mf.setAttribute("aria-label", p.labels?.field ?? defaultMathInputLabels.field);
179
+ mf.setValue(p.value, { silenceNotifications: true });
180
+ const claimKeyboard = () => {
181
+ keyboardOwner = mf;
182
+ };
183
+ mf.addEventListener("focusin", claimKeyboard, true);
184
+ const input = (event) => {
185
+ event.stopPropagation();
186
+ const accepted = accept(mf.value);
187
+ if (!accepted) mf.setValue(latest.current.value, { silenceNotifications: true });
188
+ else setIssue(mf.errors.length ? "syntax" : void 0);
189
+ };
190
+ const move = (event) => {
191
+ if (!latest.current.onExit) return;
192
+ event.preventDefault();
193
+ latest.current.onExit(event.detail.direction);
194
+ };
195
+ const key = (event) => {
196
+ if (event.isComposing) return;
197
+ const p2 = latest.current;
198
+ if (p2.onHistory && (event.metaKey || event.ctrlKey) && ["z", "y"].includes(event.key.toLowerCase())) {
199
+ event.preventDefault();
200
+ event.stopImmediatePropagation();
201
+ p2.onHistory(event.shiftKey || event.key.toLowerCase() === "y");
202
+ } else if (p2.onExit && (event.key === "Escape" || event.key === "Enter" && !event.shiftKey)) {
203
+ if (event.key === "Enter" && mf.mode === "latex") return;
204
+ event.preventDefault();
205
+ event.stopImmediatePropagation();
206
+ p2.onExit("forward");
207
+ }
208
+ };
209
+ const stop = (event) => {
210
+ event.stopPropagation();
211
+ if (event.type === "beforeinput" && latest.current.onHistory) {
212
+ const type = event.inputType;
213
+ if (type === "historyUndo" || type === "historyRedo") {
214
+ event.preventDefault();
215
+ latest.current.onHistory(type === "historyRedo");
216
+ }
217
+ }
218
+ };
219
+ for (const name of ["keydown", "keyup", "keypress", "beforeinput", "paste", "cut", "copy"])
220
+ mf.addEventListener(name, stop);
221
+ mf.addEventListener("input", input);
222
+ mf.addEventListener("move-out", move);
223
+ mf.addEventListener("keydown", key, true);
224
+ dispose = () => {
225
+ if (keyboardOwner === mf) {
226
+ window.mathVirtualKeyboard.hide({ animate: false });
227
+ keyboardOwner = void 0;
228
+ }
229
+ mf.removeEventListener("focusin", claimKeyboard, true);
230
+ for (const name of ["keydown", "keyup", "keypress", "beforeinput", "paste", "cut", "copy"])
231
+ mf.removeEventListener(name, stop);
232
+ mf.removeEventListener("input", input);
233
+ mf.removeEventListener("move-out", move);
234
+ mf.removeEventListener("keydown", key, true);
235
+ mf.remove();
236
+ field.current = void 0;
237
+ };
238
+ host.current.replaceChildren(mf);
239
+ mf.menuItems = [];
240
+ setReady(true);
241
+ if (mf.errors.length) {
242
+ setIssue("syntax");
243
+ editingSource.current = true;
244
+ setSource(true);
245
+ }
246
+ }).catch(() => {
247
+ dispose?.();
248
+ if (!cancelled) {
249
+ setFailed(true);
250
+ setSource(true);
251
+ }
252
+ });
253
+ return () => {
254
+ cancelled = true;
255
+ dispose?.();
256
+ };
257
+ }, []);
258
+ useEffect(() => {
259
+ const p = latest.current, mf = field.current;
260
+ if (ready && mf && p.autoFocus && !p.readOnly && !editingSource.current) {
261
+ keyboardOwner = mf;
262
+ mf.focus();
263
+ mf.position = p.entry === "start" ? 0 : -1;
264
+ }
265
+ }, [ready]);
266
+ useEffect(() => {
267
+ const mf = field.current;
268
+ if (!mf) return;
269
+ mf.readOnly = !!props.readOnly;
270
+ mf.setAttribute("aria-label", labels.field);
271
+ if (mf.value !== props.value) mf.setValue(props.value, { silenceNotifications: true });
272
+ }, [props.value, props.readOnly, labels.field, ready]);
273
+ useEffect(() => {
274
+ if (props.locale && ready)
275
+ void loadMathLive().then(({ MathfieldElement }) => {
276
+ MathfieldElement.locale = props.locale;
277
+ });
278
+ }, [props.locale, ready]);
279
+ return /* @__PURE__ */ jsxs(
280
+ Box,
281
+ {
282
+ component: "span",
283
+ className: "prism-math-input",
284
+ sx: {
285
+ color: "text.primary",
286
+ bgcolor: "background.paper",
287
+ borderColor: "divider",
288
+ "--prism-editor-accent": theme.palette.primary.main,
289
+ "--prism-editor-radius": `${theme.shape.borderRadius}px`
290
+ },
291
+ children: [
292
+ /* @__PURE__ */ jsx(
293
+ GlobalStyles,
294
+ {
295
+ styles: {
296
+ ".ML__keyboard": {
297
+ "--keyboard-background": theme.palette.background.default,
298
+ "--keycap-background": theme.palette.background.paper,
299
+ "--keycap-text": theme.palette.text.primary,
300
+ "--keycap-background-hover": theme.palette.action.hover,
301
+ "--keycap-text-hover": theme.palette.text.primary,
302
+ "--keyboard-accent-color": theme.palette.primary.main,
303
+ "--keyboard-toolbar-background": theme.palette.background.paper,
304
+ "--keyboard-toolbar-text": theme.palette.text.secondary,
305
+ "--keyboard-toolbar-text-active": theme.palette.primary.main,
306
+ "--keycap-primary-background": theme.palette.primary.main,
307
+ "--keycap-primary-text": theme.palette.primary.contrastText,
308
+ "--keycap-secondary-background": theme.palette.action.hover,
309
+ "--keycap-secondary-text": theme.palette.text.primary,
310
+ "--keycap-border": `1px solid ${theme.palette.divider}`,
311
+ "--keycap-secondary-border": `1px solid ${theme.palette.divider}`,
312
+ "--keyboard-zindex": theme.zIndex.modal + 1
313
+ }
314
+ }
315
+ }
316
+ ),
317
+ !ready && /* @__PURE__ */ jsx(Box, { component: "span", role: "status", sx: { typography: "caption" }, children: failed ? labels.unavailable : labels.loading }),
318
+ /* @__PURE__ */ jsx("span", { ref: host, className: "prism-math-input__field" }),
319
+ /* @__PURE__ */ jsxs(Box, { component: "span", className: "prism-math-input__tools", children: [
320
+ /* @__PURE__ */ jsx(
321
+ Button2,
322
+ {
323
+ type: "button",
324
+ size: "small",
325
+ "aria-pressed": source,
326
+ onMouseDown: (e) => e.preventDefault(),
327
+ onClick: () => setSource(!source),
328
+ children: "LaTeX"
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(Tooltip, { title: labels.keyboard, children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(
332
+ IconButton,
333
+ {
334
+ "aria-label": labels.keyboard,
335
+ type: "button",
336
+ size: "small",
337
+ disabled: !ready || props.readOnly,
338
+ onMouseDown: (e) => e.preventDefault(),
339
+ onClick: () => {
340
+ keyboardOwner = field.current;
341
+ field.current?.focus();
342
+ field.current?.executeCommand("toggleVirtualKeyboard");
343
+ },
344
+ children: /* @__PURE__ */ jsx(KeyboardIcon, { fontSize: "small" })
345
+ }
346
+ ) }) }),
347
+ props.onExit && /* @__PURE__ */ jsx(Tooltip, { title: labels.done, children: /* @__PURE__ */ jsx(IconButton, { type: "button", size: "small", "aria-label": labels.done, onClick: () => props.onExit?.("forward"), children: /* @__PURE__ */ jsx(CheckIcon, { fontSize: "small" }) }) })
348
+ ] }),
349
+ (source || !ready) && /* @__PURE__ */ jsx(
350
+ TextField2,
351
+ {
352
+ fullWidth: true,
353
+ autoFocus: !ready && props.autoFocus,
354
+ size: "small",
355
+ multiline: true,
356
+ maxRows: 8,
357
+ label: labels.source,
358
+ value: props.value,
359
+ onChange: (event) => {
360
+ editingSource.current = true;
361
+ setSource(true);
362
+ if (accept(event.target.value)) setIssue(void 0);
363
+ },
364
+ slotProps: { input: { readOnly: props.readOnly }, htmlInput: { spellCheck: false } },
365
+ onKeyDown: (event) => {
366
+ if (event.key === "Escape") {
367
+ event.stopPropagation();
368
+ props.onExit?.("forward");
369
+ }
370
+ if (props.onHistory && (event.ctrlKey || event.metaKey) && ["z", "y"].includes(event.key.toLowerCase())) {
371
+ event.preventDefault();
372
+ event.stopPropagation();
373
+ props.onHistory(event.shiftKey || event.key.toLowerCase() === "y");
374
+ }
375
+ },
376
+ sx: { flexBasis: "100%", mt: 1, "& textarea": { fontFamily: "monospace" } }
377
+ }
378
+ ),
379
+ issue && /* @__PURE__ */ jsx(Box, { component: "span", role: "status", sx: { typography: "caption", color: "text.secondary", flexBasis: "100%" }, children: labels[issue] })
380
+ ]
381
+ }
382
+ );
383
+ }
384
+ var opaque = /* @__PURE__ */ new Set([
385
+ "html",
386
+ "frontmatter",
387
+ "image",
388
+ "imageReference",
389
+ "linkReference",
390
+ "definition",
391
+ "footnoteDefinition",
392
+ "footnoteReference",
393
+ "containerDirective",
394
+ "leafDirective",
395
+ "textDirective"
396
+ ]);
397
+ var hasOpaque = (n) => (n.type === "code" || n.type === "math") && !!n.meta || n.type === "list" && n.children?.some((c) => typeof c.checked === "boolean") && !n.children?.every((c) => typeof c.checked === "boolean") || n.type === "table" && Array.isArray(n.align) && n.align.some(Boolean) || opaque.has(n.type) || n.type === "code" && ["mermaid", "pintora", "prism"].includes(String(n.lang).toLowerCase()) || !!n.children?.some(hasOpaque);
398
+ var newBlockId = () => crypto.randomUUID();
399
+ function semantic(n) {
400
+ const attrs = Object.fromEntries(
401
+ Object.entries(n.attrs ?? {}).filter(([k, v]) => !["source", "id"].includes(k) && v != null)
402
+ );
403
+ return {
404
+ type: n.type,
405
+ ...Object.keys(attrs).length ? { attrs } : {},
406
+ ...n.text !== void 0 ? { text: n.text } : {},
407
+ ...n.marks?.length ? { marks: n.marks } : {},
408
+ ...n.content?.length ? { content: n.content.map(semantic) } : {}
409
+ };
410
+ }
411
+ var signature = (content) => JSON.stringify(content.map(semantic));
412
+ function sourceDocument(source, id = newBlockId) {
413
+ if (source.length > researchLimits.source) throw new Error("document_limit");
414
+ parseResearchMarkdown(source);
415
+ const { body, metadata } = splitFrontmatter(source);
416
+ const ast = fromMarkdown(body, {
417
+ extensions: [gfm(), math(), directive()],
418
+ mdastExtensions: [gfmFromMarkdown(), mathFromMarkdown(), directiveFromMarkdown()]
419
+ });
420
+ if (metadata) {
421
+ const stop = metadata.end - (metadata.raw.endsWith("\r\n") ? 2 : metadata.raw.endsWith("\n") ? 1 : 0);
422
+ ast.children.unshift({ type: "frontmatter", position: { start: { offset: 0 }, end: { offset: stop } } });
423
+ }
424
+ let end = 0;
425
+ const content = (ast.children ?? []).map((n) => {
426
+ const start = n.position.start.offset, stop = n.position.end.offset;
427
+ const raw = source.slice(start, stop), prefix = source.slice(end, start);
428
+ end = stop;
429
+ const nodes = hasOpaque(n) ? [{ type: "researchSource", attrs: { raw, kind: n.type === "code" ? String(n.lang) : n.type } }] : parseResearchMarkdown(raw).doc.content;
430
+ const cleaned = nodes.map(cleanNode);
431
+ return {
432
+ type: "researchBlock",
433
+ attrs: { id: id(), raw, prefix, baseline: signature(cleaned) },
434
+ content: cleaned.length ? cleaned : [{ type: "paragraph" }]
435
+ };
436
+ });
437
+ return {
438
+ type: "doc",
439
+ attrs: { tail: source.slice(end) },
440
+ content: content.length ? content : [
441
+ {
442
+ type: "researchBlock",
443
+ attrs: { id: id(), raw: "", prefix: "", baseline: signature([{ type: "paragraph" }]) },
444
+ content: [{ type: "paragraph" }]
445
+ }
446
+ ]
447
+ };
448
+ }
449
+ function cleanNode(n) {
450
+ const attrs = Object.fromEntries(Object.entries(n.attrs ?? {}).filter(([k]) => !["source", "id"].includes(k)));
451
+ if (n.type === "orderedList") attrs.start ??= 1;
452
+ if (n.type === "codeBlock") attrs.language ||= null;
453
+ if (n.type === "taskItem") attrs.checked ??= false;
454
+ return { ...n, attrs, content: n.content?.map(cleanNode) };
455
+ }
456
+ function astNode(n) {
457
+ const children = () => (n.content ?? []).map(astNode), a = n.attrs ?? {};
458
+ let result;
459
+ switch (n.type) {
460
+ case "paragraph":
461
+ result = { type: "paragraph", children: children() };
462
+ break;
463
+ case "heading":
464
+ result = { type: "heading", depth: a.level, children: children() };
465
+ break;
466
+ case "text":
467
+ result = { type: "text", value: n.text ?? "" };
468
+ break;
469
+ case "hardBreak":
470
+ return { type: "break" };
471
+ case "horizontalRule":
472
+ return { type: "thematicBreak" };
473
+ case "blockquote":
474
+ return { type: "blockquote", children: children() };
475
+ case "codeBlock":
476
+ return { type: "code", lang: a.language || null, value: (n.content ?? []).map((x) => x.text ?? "").join("") };
477
+ case "mathBlock":
478
+ return { type: "math", value: a.tex ?? "" };
479
+ case "mathInline":
480
+ result = { type: "inlineMath", value: a.tex ?? "" };
481
+ break;
482
+ case "bulletList":
483
+ case "orderedList":
484
+ case "taskList":
485
+ return {
486
+ type: "list",
487
+ ordered: n.type === "orderedList",
488
+ start: a.start ?? 1,
489
+ spread: false,
490
+ children: children()
491
+ };
492
+ case "listItem":
493
+ case "taskItem":
494
+ return {
495
+ type: "listItem",
496
+ spread: false,
497
+ checked: n.type === "taskItem" ? !!a.checked : null,
498
+ children: children()
499
+ };
500
+ case "table":
501
+ return { type: "table", align: (n.content?.[0]?.content ?? []).map(() => null), children: children() };
502
+ case "tableRow":
503
+ return { type: "tableRow", children: children() };
504
+ case "tableCell":
505
+ case "tableHeader":
506
+ if ((a.colspan ?? 1) !== 1 || (a.rowspan ?? 1) !== 1 || n.content?.length !== 1 || n.content[0]?.type !== "paragraph")
507
+ throw Error("unsupported_table_shape");
508
+ return { type: "tableCell", children: (n.content ?? []).flatMap((p) => (p.content ?? []).map(astNode)) };
509
+ default:
510
+ throw new Error(`unsupported_editor_node:${n.type}`);
511
+ }
512
+ for (const mark of n.marks ?? []) {
513
+ const type = { bold: "strong", italic: "emphasis", strike: "delete", code: "inlineCode", link: "link" }[mark.type];
514
+ if (!type) throw new Error(`unsupported_editor_mark:${mark.type}`);
515
+ if (type === "inlineCode") result = { type, value: n.text ?? "" };
516
+ else
517
+ result = {
518
+ type,
519
+ ...type === "link" ? { url: mark.attrs?.href, title: mark.attrs?.title ?? null } : {},
520
+ children: [result]
521
+ };
522
+ }
523
+ return result;
524
+ }
525
+ function blockSource(block) {
526
+ const content = block.content ?? [];
527
+ if (signature(content) === block.attrs?.baseline) return String(block.attrs.raw ?? "");
528
+ if (content.length === 1 && content[0]?.type === "researchSource") return String(content[0].attrs?.raw ?? "");
529
+ return toMarkdown({ type: "root", children: content.map(astNode) }, {
530
+ extensions: [gfmToMarkdown(), mathToMarkdown()],
531
+ bullet: "-",
532
+ fences: true
533
+ }).replace(/\n$/, "");
534
+ }
535
+ var blockCache = /* @__PURE__ */ new WeakMap();
536
+ function editorSource(doc) {
537
+ const parts = [];
538
+ doc.forEach((block, _offset, index) => {
539
+ let raw = blockCache.get(block);
540
+ if (raw === void 0) {
541
+ raw = blockSource(block.toJSON());
542
+ blockCache.set(block, raw);
543
+ }
544
+ const prefix = String(block.attrs.prefix ?? "");
545
+ const afterMetadata = index === 1 && doc.firstChild?.firstChild?.attrs.kind === "frontmatter";
546
+ parts.push(index === 0 || afterMetadata ? prefix : /\r?\n[ \t]*\r?\n/.test(prefix) ? prefix : "\n\n", raw);
547
+ });
548
+ return parts.join("") + String(doc.attrs.tail ?? "");
549
+ }
550
+ function snapshot(doc) {
551
+ let source;
552
+ return Object.freeze({
553
+ format: "prism-research-source/1",
554
+ blocks: doc.childCount,
555
+ getSource: () => source ??= editorSource(doc)
556
+ });
557
+ }
558
+ function hydrateSource(source, schema) {
559
+ const doc = sourceDocument(source);
560
+ for (const block of doc.content ?? [])
561
+ block.attrs.baseline = signature(schema.nodeFromJSON(block).toJSON().content ?? []);
562
+ schema.nodeFromJSON(doc).check();
563
+ return doc;
564
+ }
565
+
566
+ // src/components/research-editor/math-navigation.ts
567
+ function exitMathTransaction(state, pos, before) {
568
+ const node = state.doc.nodeAt(pos);
569
+ if (!node || !["mathInline", "mathBlock"].includes(node.type.name)) return void 0;
570
+ const tr = state.tr;
571
+ let target = TextSelection.near(tr.doc.resolve(before ? pos : pos + node.nodeSize), before ? -1 : 1);
572
+ const neighbourMath = target instanceof NodeSelection && ["mathInline", "mathBlock"].includes(target.node.type.name) && target.from !== pos;
573
+ const reachableText = target instanceof TextSelection && (before ? target.from <= pos : target.from >= pos + node.nodeSize);
574
+ if (!neighbourMath && !reachableText) {
575
+ const $pos = tr.doc.resolve(pos);
576
+ const edge = before ? $pos.before(1) : $pos.after(1);
577
+ tr.insert(
578
+ edge,
579
+ state.schema.nodes.researchBlock.create({ id: newBlockId() }, state.schema.nodes.paragraph.create())
580
+ );
581
+ target = TextSelection.create(tr.doc, edge + 2);
582
+ }
583
+ return closeHistory(tr.setSelection(target)).setMeta("prismMathEntry", before ? "end" : "start").scrollIntoView();
584
+ }
585
+ function updateMathTransaction(state, pos, tex) {
586
+ const node = state.doc.nodeAt(pos);
587
+ if (!node || !["mathInline", "mathBlock"].includes(node.type.name)) return void 0;
588
+ const tr = state.tr.setNodeMarkup(pos, void 0, { ...node.attrs, tex });
589
+ if (state.selection instanceof NodeSelection && state.selection.from === pos)
590
+ tr.setSelection(NodeSelection.create(tr.doc, pos));
591
+ return tr;
592
+ }
593
+ var mathEntryKey = new PluginKey("researchMathEntry");
594
+ var ResearchMathContext = createContext({});
595
+ function MathNodeView({ node, editor, getPos, selected }) {
596
+ const config = useContext(ResearchMathContext);
597
+ const display = node.type.name === "mathBlock", latex = String(node.attrs.tex);
598
+ const editable = config.readOnly === void 0 ? editor.isEditable : !config.readOnly;
599
+ const active = selected && editable;
600
+ const preview = useRef(null);
601
+ useEffect(() => {
602
+ if (!preview.current) return;
603
+ try {
604
+ katex.render(latex === "\\placeholder{}" ? "\\square" : latex, preview.current, {
605
+ displayMode: display,
606
+ trust: false,
607
+ strict: "error",
608
+ throwOnError: true,
609
+ maxExpand: 1e3,
610
+ maxSize: 20
611
+ });
612
+ } catch {
613
+ preview.current.textContent = latex || config.labels?.field || "Equation";
614
+ }
615
+ }, [latex, display, active, config.labels?.field]);
616
+ useEffect(() => {
617
+ if (active) editor.view.dispatch(closeHistory(editor.state.tr));
618
+ return () => {
619
+ if (active && !editor.isDestroyed) editor.view.dispatch(closeHistory(editor.state.tr));
620
+ };
621
+ }, [active, editor]);
622
+ function exit(direction) {
623
+ const pos = getPos();
624
+ if (typeof pos !== "number" || editor.isDestroyed) return;
625
+ const before = direction === "backward" || direction === "upward";
626
+ const tr = exitMathTransaction(editor.state, pos, before);
627
+ if (tr) flushSync(() => editor.view.dispatch(tr));
628
+ editor.view.focus();
629
+ }
630
+ return /* @__PURE__ */ jsx(
631
+ NodeViewWrapper,
632
+ {
633
+ as: display ? "div" : "span",
634
+ className: `prism-science__math${display ? " prism-science__math--display" : ""}`,
635
+ contentEditable: false,
636
+ children: active ? /* @__PURE__ */ jsx(
637
+ MathInput,
638
+ {
639
+ ...config,
640
+ value: latex,
641
+ display,
642
+ entry: mathEntryKey.getState(editor.state),
643
+ autoFocus: true,
644
+ onChange: (input) => {
645
+ const value = input || "\\placeholder{}";
646
+ const pos = getPos();
647
+ if (!editor.isEditable || typeof pos !== "number" || editor.state.doc.nodeAt(pos)?.type !== node.type)
648
+ return false;
649
+ if (value === editor.state.doc.nodeAt(pos)?.attrs.tex) return true;
650
+ const tr = updateMathTransaction(editor.state, pos, value);
651
+ if (!tr) return false;
652
+ editor.view.dispatch(tr);
653
+ return editor.state.doc.nodeAt(pos)?.attrs.tex === value;
654
+ },
655
+ onExit: exit,
656
+ onHistory: (redo) => {
657
+ if (redo) editor.commands.redo();
658
+ else editor.commands.undo();
659
+ }
660
+ }
661
+ ) : /* @__PURE__ */ jsx(
662
+ "span",
663
+ {
664
+ ref: preview,
665
+ role: editable ? "button" : "math",
666
+ tabIndex: editable ? 0 : void 0,
667
+ "aria-label": `${config.labels?.field ?? "Equation"}: ${latex}`,
668
+ className: "prism-science__math-preview",
669
+ onClick: () => {
670
+ const pos = getPos();
671
+ if (editor.isEditable && !config.readOnly && typeof pos === "number") editor.commands.setNodeSelection(pos);
672
+ },
673
+ onKeyDown: (event) => {
674
+ if (event.key === "Enter" || event.key === " ") {
675
+ event.preventDefault();
676
+ event.currentTarget.click();
677
+ }
678
+ }
679
+ }
680
+ )
681
+ }
682
+ );
683
+ }
684
+ function findBlock(doc, id) {
685
+ let found;
686
+ doc.forEach((node, pos, index) => {
687
+ if (node.type.name === "researchBlock" && node.attrs.id === id) found = { node, pos, index };
688
+ });
689
+ return found;
690
+ }
691
+ function currentBlock(editor) {
692
+ const { $from } = editor.state.selection;
693
+ if ($from.depth >= 1) return { pos: $from.before(1), index: $from.index(0) };
694
+ const index = $from.index(0);
695
+ return editor.state.doc.childCount ? {
696
+ pos: index >= editor.state.doc.childCount ? editor.state.doc.content.size - editor.state.doc.lastChild.nodeSize : $from.pos,
697
+ index: Math.min(index, editor.state.doc.childCount - 1)
698
+ } : void 0;
699
+ }
700
+ function selectMoved(tr, state, oldPos, newPos, node) {
701
+ const { selection } = state;
702
+ const within = selection.from >= oldPos && selection.to <= oldPos + node.nodeSize;
703
+ if (within && selection instanceof TextSelection) {
704
+ tr.setSelection(TextSelection.create(tr.doc, newPos + selection.anchor - oldPos, newPos + selection.head - oldPos));
705
+ } else if (within && selection instanceof NodeSelection) {
706
+ tr.setSelection(NodeSelection.create(tr.doc, newPos + selection.from - oldPos));
707
+ } else {
708
+ tr.setSelection(Selection.near(tr.doc.resolve(newPos + 1)));
709
+ }
710
+ return closeHistory(tr).scrollIntoView();
711
+ }
712
+ function moveBlockTransaction(state, id, beforeId) {
713
+ const from = findBlock(state.doc, id);
714
+ const before = beforeId === null ? void 0 : findBlock(state.doc, beforeId);
715
+ if (!from || beforeId !== null && !before || beforeId === id) return void 0;
716
+ const destination = before?.index ?? state.doc.childCount;
717
+ if (destination === from.index + 1 || destination === state.doc.childCount && from.index === destination - 1)
718
+ return void 0;
719
+ const tr = state.tr.delete(from.pos, from.pos + from.node.nodeSize);
720
+ const target = beforeId === null ? tr.doc.content.size : findBlock(tr.doc, beforeId).pos;
721
+ tr.insert(target, from.node);
722
+ return selectMoved(tr, state, from.pos, target, from.node);
723
+ }
724
+ function blockActionTransaction(state, id, action) {
725
+ const block = findBlock(state.doc, id);
726
+ if (!block) return void 0;
727
+ const { node, pos, index } = block;
728
+ if (action === "up")
729
+ return index > 0 ? moveBlockTransaction(state, id, String(state.doc.child(index - 1).attrs.id)) : void 0;
730
+ if (action === "down")
731
+ return index < state.doc.childCount - 1 ? moveBlockTransaction(
732
+ state,
733
+ id,
734
+ index + 2 < state.doc.childCount ? String(state.doc.child(index + 2).attrs.id) : null
735
+ ) : void 0;
736
+ const tr = state.tr;
737
+ if (action === "duplicate") {
738
+ const copy = node.type.create({ ...node.attrs, id: newBlockId(), prefix: "\n\n" }, node.content);
739
+ tr.insert(pos + node.nodeSize, copy);
740
+ return selectMoved(tr, state, pos, pos + node.nodeSize, copy);
741
+ }
742
+ if (state.doc.childCount === 1) {
743
+ tr.replaceWith(
744
+ pos,
745
+ pos + node.nodeSize,
746
+ node.type.create({ id: newBlockId() }, state.schema.nodes.paragraph.create())
747
+ );
748
+ } else {
749
+ tr.delete(pos, pos + node.nodeSize);
750
+ }
751
+ tr.setSelection(Selection.near(tr.doc.resolve(Math.min(pos + 1, tr.doc.content.size))));
752
+ return closeHistory(tr).scrollIntoView();
753
+ }
754
+ function runBlockAction(editor, action, id) {
755
+ if (!editor.isEditable) return false;
756
+ const selected = currentBlock(editor);
757
+ const target = id ?? (selected && String(editor.state.doc.child(selected.index).attrs.id));
758
+ const tr = target && blockActionTransaction(editor.state, target, action);
759
+ return dispatchBlockTransaction(editor, tr || void 0);
760
+ }
761
+ function dispatchBlockTransaction(editor, tr) {
762
+ if (!editor.isEditable || !tr) return false;
763
+ editor.view.dispatch(tr);
764
+ editor.view.dispatch(closeHistory(editor.state.tr));
765
+ editor.commands.focus();
766
+ return true;
767
+ }
768
+ var ResearchViewContext = createContext(void 0);
769
+ function SourceView({ node, extension }) {
770
+ const presentation = useContext(ResearchViewContext);
771
+ const ref = useRef(null), [visible, setVisible] = useState(false);
772
+ useEffect(() => {
773
+ if (!ref.current) return void 0;
774
+ if (typeof IntersectionObserver === "undefined") {
775
+ setVisible(true);
776
+ return void 0;
777
+ }
778
+ const observer = new IntersectionObserver(
779
+ (entries) => {
780
+ if (entries.some((e) => e.isIntersecting)) {
781
+ setVisible(true);
782
+ observer.disconnect();
783
+ }
784
+ },
785
+ { rootMargin: "200px" }
786
+ );
787
+ observer.observe(ref.current);
788
+ return () => observer.disconnect();
789
+ }, []);
790
+ return /* @__PURE__ */ jsx(NodeViewWrapper, { className: "prism-science__source-block", contentEditable: false, children: /* @__PURE__ */ jsxs("div", { ref, children: [
791
+ /* @__PURE__ */ jsx("span", { className: "prism-science__kind", children: String(node.attrs.kind) }),
792
+ visible ? /* @__PURE__ */ jsx(
793
+ ResearchDocument,
794
+ {
795
+ source: String(node.attrs.raw),
796
+ widgets: presentation?.widgets ?? extension.options.widgets,
797
+ labels: presentation?.labels ?? extension.options.labels
798
+ }
799
+ ) : /* @__PURE__ */ jsx("pre", { children: String(node.attrs.raw).slice(0, 160) })
800
+ ] }) });
801
+ }
802
+ function researchExtensions(widgets = {}, limits, labels) {
803
+ const Doc = Node.create({
804
+ name: "doc",
805
+ topNode: true,
806
+ content: "researchBlock+",
807
+ addAttributes: () => ({ tail: { default: "" } })
808
+ });
809
+ const Block = Node.create({
810
+ name: "researchBlock",
811
+ content: "block+",
812
+ group: "researchGroup",
813
+ defining: true,
814
+ draggable: true,
815
+ addAttributes: () => ({
816
+ id: { default: null },
817
+ raw: { default: "" },
818
+ prefix: { default: "" },
819
+ baseline: { default: "" }
820
+ }),
821
+ parseHTML: () => [{ tag: "div[data-research-block]" }],
822
+ renderHTML: ({ node }) => ["div", { "data-research-block": node.attrs.id, class: "prism-science__block" }, 0],
823
+ addKeyboardShortcuts() {
824
+ return {
825
+ "Mod-Alt-ArrowUp": () => runBlockAction(this.editor, "up"),
826
+ "Mod-Alt-ArrowDown": () => runBlockAction(this.editor, "down"),
827
+ Enter: () => {
828
+ if (!this.editor.isEditable) return false;
829
+ const { state, view } = this.editor, { $from, $to } = state.selection;
830
+ if (!$from.sameParent($to) || $from.depth !== 2 || !["paragraph", "heading"].includes($from.parent.type.name))
831
+ return false;
832
+ const next = $from.parentOffset === $from.parent.content.size ? state.schema.nodes.paragraph : $from.parent.type;
833
+ const types = [
834
+ { type: state.schema.nodes.researchBlock, attrs: { id: newBlockId() } },
835
+ { type: next, attrs: $from.parent.attrs }
836
+ ];
837
+ const tr = state.tr.deleteSelection();
838
+ if (!canSplit(tr.doc, tr.selection.from, 2, types)) return false;
839
+ view.dispatch(tr.split(tr.selection.from, 2, types).scrollIntoView());
840
+ return true;
841
+ }
842
+ };
843
+ }
844
+ });
845
+ const Source = Node.create({
846
+ name: "researchSource",
847
+ group: "block",
848
+ atom: true,
849
+ selectable: true,
850
+ addOptions: () => ({ widgets, labels }),
851
+ addAttributes: () => ({ raw: { default: "" }, kind: { default: "source" } }),
852
+ parseHTML: () => [
853
+ {
854
+ tag: "pre[data-research-source]",
855
+ priority: 100,
856
+ preserveWhitespace: "full",
857
+ getAttrs: (element) => ({
858
+ raw: element.getAttribute("data-source") ?? element.textContent ?? "",
859
+ kind: element.getAttribute("data-research-source") ?? "source"
860
+ })
861
+ }
862
+ ],
863
+ renderHTML: ({ node }) => [
864
+ "pre",
865
+ { "data-research-source": node.attrs.kind, "data-source": node.attrs.raw },
866
+ node.attrs.raw
867
+ ],
868
+ addNodeView: () => ReactNodeViewRenderer(SourceView)
869
+ });
870
+ const mathNode = (name) => Node.create({
871
+ name,
872
+ marks: name === "mathInline" ? "bold italic strike link" : "",
873
+ group: name === "mathInline" ? "inline" : "block",
874
+ inline: name === "mathInline",
875
+ atom: true,
876
+ selectable: true,
877
+ addAttributes: () => ({ tex: { default: "" } }),
878
+ parseHTML: () => [
879
+ {
880
+ tag: name === "mathInline" ? "span[data-math-inline]" : "div[data-math-block]",
881
+ getAttrs: (element) => ({ tex: element.getAttribute("data-tex") ?? element.textContent ?? "" })
882
+ }
883
+ ],
884
+ renderHTML: ({ node }) => [
885
+ name === "mathInline" ? "span" : "div",
886
+ {
887
+ [name === "mathInline" ? "data-math-inline" : "data-math-block"]: true,
888
+ "data-tex": String(node.attrs.tex)
889
+ },
890
+ String(node.attrs.tex)
891
+ ],
892
+ addNodeView: () => ReactNodeViewRenderer(MathNodeView, {
893
+ // Shadow-DOM math input is not a native INPUT/TEXTAREA. Letting its keys
894
+ // reach ProseMirror would replace the selected atom with ordinary text.
895
+ stopEvent: ({ event }) => event.target instanceof Element && !!event.target.closest(".prism-math-input")
896
+ }),
897
+ addKeyboardShortcuts() {
898
+ const enter = (backward) => {
899
+ if (!this.editor.isEditable || !this.editor.state.selection.empty) return false;
900
+ const { $from } = this.editor.state.selection;
901
+ const next = backward ? $from.nodeBefore : $from.nodeAfter;
902
+ if (next?.type.name !== "mathInline") return false;
903
+ const pos = backward ? $from.pos - next.nodeSize : $from.pos;
904
+ this.editor.view.dispatch(
905
+ this.editor.state.tr.setSelection(NodeSelection.create(this.editor.state.doc, pos)).setMeta("prismMathEntry", backward ? "end" : "start")
906
+ );
907
+ return true;
908
+ };
909
+ return { ArrowLeft: () => enter(true), ArrowRight: () => enter(false) };
910
+ }
911
+ });
912
+ const Ids = Extension.create({
913
+ name: "researchIds",
914
+ addProseMirrorPlugins() {
915
+ return [
916
+ new Plugin({
917
+ key: mathEntryKey,
918
+ state: { init: () => "end", apply: (tr, value) => tr.getMeta("prismMathEntry") ?? value }
919
+ }),
920
+ new Plugin({
921
+ props: {
922
+ decorations(state) {
923
+ const { from, to, empty } = state.selection;
924
+ const decorations = [];
925
+ state.doc.forEach((node, pos) => {
926
+ if (empty ? from >= pos && from < pos + node.nodeSize : from < pos + node.nodeSize && to > pos)
927
+ decorations.push(
928
+ Decoration.node(pos, pos + node.nodeSize, { class: empty ? "is-current" : "is-selected" })
929
+ );
930
+ });
931
+ return DecorationSet.create(state.doc, decorations);
932
+ }
933
+ },
934
+ filterTransaction(tr) {
935
+ if (!tr.docChanged || !limits) return true;
936
+ try {
937
+ if (snapshot(tr.doc).getSource().length <= limits.source) return true;
938
+ } catch {
939
+ }
940
+ limits.onLimit?.();
941
+ return false;
942
+ },
943
+ appendTransaction(transactions, _old, state) {
944
+ if (!transactions.some((t) => t.docChanged)) return null;
945
+ const seen = /* @__PURE__ */ new Set(), tr = state.tr;
946
+ state.doc.forEach((node, pos) => {
947
+ const id = node.attrs.id;
948
+ if (!id || seen.has(id)) tr.setNodeMarkup(pos, void 0, { ...node.attrs, id: newBlockId() });
949
+ else seen.add(id);
950
+ });
951
+ return tr.docChanged ? tr.setMeta("addToHistory", false) : null;
952
+ }
953
+ })
954
+ ];
955
+ }
956
+ });
957
+ return [
958
+ Doc,
959
+ Block,
960
+ StarterKit.configure({ document: false, underline: false, link: { openOnClick: false }, trailingNode: false }),
961
+ TaskList,
962
+ TaskItem.configure({ nested: true, a11y: { checkboxLabel: (node) => node.textContent } }),
963
+ TableKit.configure({ table: { resizable: false } }),
964
+ Source,
965
+ mathNode("mathInline"),
966
+ mathNode("mathBlock"),
967
+ Ids
968
+ ];
969
+ }
970
+ function BlockControls({
971
+ editor,
972
+ container,
973
+ labels,
974
+ readOnly,
975
+ onInsert,
976
+ onSource
977
+ }) {
978
+ const [anchor, setAnchor] = useState();
979
+ const [menu, setMenu] = useState();
980
+ const [dropTop, setDropTop] = useState();
981
+ const dragged = useRef(void 0);
982
+ useEffect(() => {
983
+ const host = container.current;
984
+ if (!host || readOnly) {
985
+ setAnchor(void 0);
986
+ setMenu(void 0);
987
+ setDropTop(void 0);
988
+ return void 0;
989
+ }
990
+ let hovered;
991
+ let frame = 0;
992
+ const selectedElement = () => {
993
+ const selected3 = currentBlock(editor);
994
+ const element = selected3 && editor.view.nodeDOM(selected3.pos);
995
+ return element instanceof HTMLElement ? element : void 0;
996
+ };
997
+ const update = () => {
998
+ frame = 0;
999
+ const element = hovered && editor.view.dom.contains(hovered) ? hovered : selectedElement();
1000
+ const id = element?.getAttribute("data-research-block");
1001
+ if (!id || !element) {
1002
+ setAnchor(void 0);
1003
+ return;
1004
+ }
1005
+ const top = element.getBoundingClientRect().top - host.getBoundingClientRect().top;
1006
+ setAnchor((old) => old?.id === id && old.top === top ? old : { id, top });
1007
+ };
1008
+ const schedule = () => {
1009
+ if (!frame) frame = requestAnimationFrame(update);
1010
+ };
1011
+ const elementAt = (target) => {
1012
+ const element = target instanceof Element ? target.closest("[data-research-block]") : null;
1013
+ return element && editor.view.dom.contains(element) ? element : void 0;
1014
+ };
1015
+ const pointer = (event) => {
1016
+ if (dragged.current) return;
1017
+ const element = elementAt(event.target);
1018
+ if (element) {
1019
+ hovered = element;
1020
+ schedule();
1021
+ }
1022
+ };
1023
+ const selected2 = () => {
1024
+ hovered = void 0;
1025
+ schedule();
1026
+ };
1027
+ const clearDrop = () => {
1028
+ dragged.current = void 0;
1029
+ setDropTop(void 0);
1030
+ };
1031
+ const destination = (event) => {
1032
+ const element = elementAt(event.target);
1033
+ if (!element) return void 0;
1034
+ const id = element.getAttribute("data-research-block");
1035
+ const block = findBlock(editor.state.doc, id);
1036
+ if (!block) return void 0;
1037
+ const rect = element.getBoundingClientRect();
1038
+ const before = event.clientY < rect.top + rect.height / 2;
1039
+ return {
1040
+ id: before ? id : block.index + 1 < editor.state.doc.childCount ? String(editor.state.doc.child(block.index + 1).attrs.id) : null,
1041
+ top: (before ? rect.top : rect.bottom) - host.getBoundingClientRect().top
1042
+ };
1043
+ };
1044
+ const over = (event) => {
1045
+ if (!dragged.current || !editor.isEditable) return;
1046
+ const target = destination(event);
1047
+ if (!target) {
1048
+ setDropTop(void 0);
1049
+ return;
1050
+ }
1051
+ event.preventDefault();
1052
+ event.stopPropagation();
1053
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "move";
1054
+ setDropTop(target.top);
1055
+ };
1056
+ const drop = (event) => {
1057
+ if (!dragged.current) return;
1058
+ event.preventDefault();
1059
+ event.stopPropagation();
1060
+ const target = destination(event);
1061
+ const tr = editor.isEditable && target && moveBlockTransaction(editor.state, dragged.current, target.id);
1062
+ clearDrop();
1063
+ if (tr) dispatchBlockTransaction(editor, tr);
1064
+ };
1065
+ host.addEventListener("pointermove", pointer);
1066
+ host.addEventListener("dragover", over, true);
1067
+ host.addEventListener("drop", drop, true);
1068
+ window.addEventListener("dragend", clearDrop);
1069
+ editor.on("selectionUpdate", selected2);
1070
+ editor.on("transaction", selected2);
1071
+ const observer = typeof ResizeObserver !== "undefined" ? new ResizeObserver(schedule) : void 0;
1072
+ observer?.observe(host);
1073
+ window.addEventListener("resize", schedule);
1074
+ update();
1075
+ return () => {
1076
+ cancelAnimationFrame(frame);
1077
+ observer?.disconnect();
1078
+ host.removeEventListener("pointermove", pointer);
1079
+ host.removeEventListener("dragover", over, true);
1080
+ host.removeEventListener("drop", drop, true);
1081
+ window.removeEventListener("dragend", clearDrop);
1082
+ window.removeEventListener("resize", schedule);
1083
+ editor.off("selectionUpdate", selected2);
1084
+ editor.off("transaction", selected2);
1085
+ dragged.current = void 0;
1086
+ };
1087
+ }, [editor, container, readOnly]);
1088
+ if (readOnly || !anchor) return null;
1089
+ const selected = menu && findBlock(editor.state.doc, menu.id);
1090
+ function action(kind) {
1091
+ if (menu) runBlockAction(editor, kind, menu.id);
1092
+ setMenu(void 0);
1093
+ }
1094
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1095
+ /* @__PURE__ */ jsxs("div", { className: "prism-science__gutter", style: { top: anchor.top }, role: "group", "aria-label": labels.blockMenu, children: [
1096
+ /* @__PURE__ */ jsx(
1097
+ IconButton,
1098
+ {
1099
+ type: "button",
1100
+ title: labels.insert,
1101
+ "aria-label": labels.insert,
1102
+ onMouseDown: (e) => e.preventDefault(),
1103
+ onClick: () => onInsert(anchor.id),
1104
+ children: /* @__PURE__ */ jsx(AddIcon, { fontSize: "small" })
1105
+ }
1106
+ ),
1107
+ /* @__PURE__ */ jsx(
1108
+ IconButton,
1109
+ {
1110
+ type: "button",
1111
+ title: labels.dragBlock,
1112
+ "aria-label": labels.blockMenu,
1113
+ "aria-haspopup": "menu",
1114
+ "aria-expanded": !!menu,
1115
+ draggable: true,
1116
+ onDragStart: (event) => {
1117
+ if (!editor.isEditable || !findBlock(editor.state.doc, anchor.id)) {
1118
+ event.preventDefault();
1119
+ return;
1120
+ }
1121
+ dragged.current = anchor.id;
1122
+ event.dataTransfer.effectAllowed = "move";
1123
+ event.dataTransfer.setData("application/x-prism-block", anchor.id);
1124
+ setMenu(void 0);
1125
+ },
1126
+ onClick: (event) => setMenu({ id: anchor.id, element: event.currentTarget }),
1127
+ children: /* @__PURE__ */ jsx(DragIcon, { fontSize: "small" })
1128
+ }
1129
+ )
1130
+ ] }),
1131
+ dropTop !== void 0 && /* @__PURE__ */ jsx("div", { className: "prism-science__drop-line", style: { top: dropTop }, "aria-hidden": "true" }),
1132
+ /* @__PURE__ */ jsxs(Menu, { open: !!menu, anchorEl: menu?.element, onClose: () => setMenu(void 0), children: [
1133
+ /* @__PURE__ */ jsx(
1134
+ MenuItem,
1135
+ {
1136
+ disabled: !selected,
1137
+ onClick: () => {
1138
+ if (menu) onSource(menu.id);
1139
+ setMenu(void 0);
1140
+ },
1141
+ children: labels.editBlock
1142
+ }
1143
+ ),
1144
+ /* @__PURE__ */ jsx(MenuItem, { disabled: !selected || selected.index === 0, onClick: () => action("up"), children: labels.up }),
1145
+ /* @__PURE__ */ jsx(
1146
+ MenuItem,
1147
+ {
1148
+ disabled: !selected || selected.index === editor.state.doc.childCount - 1,
1149
+ onClick: () => action("down"),
1150
+ children: labels.down
1151
+ }
1152
+ ),
1153
+ /* @__PURE__ */ jsx(MenuItem, { disabled: !selected, onClick: () => action("duplicate"), children: labels.duplicate }),
1154
+ /* @__PURE__ */ jsx(MenuItem, { disabled: !selected, onClick: () => action("remove"), children: labels.remove })
1155
+ ] })
1156
+ ] });
1157
+ }
1158
+ var defaultResearchEditorLabels = {
1159
+ editor: "Research document",
1160
+ insert: "Insert block",
1161
+ search: "Find a block",
1162
+ source: "Document source",
1163
+ editBlock: "Block source",
1164
+ up: "Move block up",
1165
+ down: "Move block down",
1166
+ duplicate: "Duplicate block",
1167
+ remove: "Delete block",
1168
+ undo: "Undo",
1169
+ redo: "Redo",
1170
+ bold: "Bold",
1171
+ italic: "Italic",
1172
+ link: "Link",
1173
+ inlineMath: "Inline equation",
1174
+ apply: "Apply",
1175
+ cancel: "Cancel",
1176
+ invalid: "This source cannot be applied. Check the document limits and structure.",
1177
+ limit: "Document exceeds the configured source limit.",
1178
+ hint: "Type / for a new block. Ctrl/\u2318 + Shift + M inserts an equation. Arrow keys move between text and math.",
1179
+ paragraph: "Text",
1180
+ heading: "Heading",
1181
+ list: "Bullet list",
1182
+ ordered: "Numbered list",
1183
+ tasks: "Task list",
1184
+ quote: "Quote",
1185
+ code: "Code",
1186
+ equation: "Equation",
1187
+ diagram: "Diagram",
1188
+ table: "Table",
1189
+ widget: "Interactive block",
1190
+ blocks: "blocks",
1191
+ blockMenu: "Block actions",
1192
+ dragBlock: "Drag to move; click for block actions. Keyboard: Ctrl/Command + Alt + Up/Down."
1193
+ };
1194
+ var filterInsertions = createFilterOptions({
1195
+ stringify: (recipe) => [recipe.label, ...recipe.keywords ?? []].join(" ")
1196
+ });
1197
+ var noWidgets = Object.freeze({});
1198
+ var noInsertions = [];
1199
+ function ResearchEditor({
1200
+ initialSource,
1201
+ onChange,
1202
+ ref,
1203
+ readOnly = false,
1204
+ labels: overrides,
1205
+ widgets = noWidgets,
1206
+ previewLabels,
1207
+ insertions = noInsertions,
1208
+ maxSourceLength = researchLimits.source,
1209
+ blockControls = true,
1210
+ math: math2,
1211
+ className,
1212
+ sx
1213
+ }) {
1214
+ const presentation = useMemo(() => ({ widgets, labels: previewLabels }), [widgets, previewLabels]);
1215
+ const labels = useMemo(() => ({ ...defaultResearchEditorLabels, ...overrides }), [overrides]);
1216
+ const workspace = useRef(null);
1217
+ const notify = useRef(onChange);
1218
+ notify.current = onChange;
1219
+ const [palette, setPalette] = useState(false), [dialog, setDialog] = useState(), [invalid, setInvalid] = useState("");
1220
+ const extensions = useMemo(
1221
+ () => researchExtensions(widgets, { source: maxSourceLength, onLimit: () => setInvalid(labels.limit) }, previewLabels),
1222
+ [widgets, maxSourceLength, labels.limit, previewLabels]
1223
+ );
1224
+ const [initial] = useState(() => hydrateSource(initialSource, getSchema(extensions)));
1225
+ const editor = useEditor({
1226
+ extensions,
1227
+ content: initial,
1228
+ editable: !readOnly,
1229
+ immediatelyRender: false,
1230
+ shouldRerenderOnTransaction: false,
1231
+ editorProps: {
1232
+ attributes: { role: "textbox", "aria-multiline": "true", "aria-label": labels.editor },
1233
+ handleKeyDown: (view, event) => {
1234
+ if (!readOnly && !event.isComposing && (event.metaKey || event.ctrlKey) && event.shiftKey && event.key.toLowerCase() === "m") {
1235
+ event.preventDefault();
1236
+ insertInlineMath();
1237
+ return true;
1238
+ }
1239
+ if (event.key === "/" && !event.isComposing && !view.composing && view.state.selection.empty && view.state.selection.$from.parent.type.name === "paragraph" && view.state.selection.$from.parent.content.size === 0) {
1240
+ event.preventDefault();
1241
+ setPalette(true);
1242
+ return true;
1243
+ }
1244
+ return false;
1245
+ }
1246
+ },
1247
+ onUpdate: ({ editor: e }) => {
1248
+ setInvalid("");
1249
+ notify.current?.(snapshot(e.state.doc));
1250
+ }
1251
+ });
1252
+ useImperativeHandle(
1253
+ ref,
1254
+ () => ({
1255
+ getSnapshot: () => editor ? snapshot(editor.state.doc) : { format: "prism-research-source/1", blocks: initial.content?.length ?? 0, getSource: () => initialSource },
1256
+ focus: () => {
1257
+ editor?.commands.focus();
1258
+ }
1259
+ }),
1260
+ [editor, initial, initialSource]
1261
+ );
1262
+ useEffect(() => {
1263
+ editor?.setEditable(!readOnly, false);
1264
+ if (readOnly) {
1265
+ setPalette(false);
1266
+ setDialog(void 0);
1267
+ }
1268
+ }, [editor, readOnly]);
1269
+ const selection = useEditorState({
1270
+ editor,
1271
+ selector: ({ editor: e }) => e ? {
1272
+ bold: e.isActive("bold"),
1273
+ italic: e.isActive("italic"),
1274
+ index: currentBlock(e)?.index ?? 0,
1275
+ blocks: e.state.doc.childCount,
1276
+ undo: e.can().undo(),
1277
+ redo: e.can().redo()
1278
+ } : null
1279
+ });
1280
+ const blockIndex = selection?.index ?? (editor ? currentBlock(editor)?.index : void 0) ?? 0;
1281
+ const blockCount = selection?.blocks ?? editor?.state.doc.childCount ?? initial.content?.length ?? 1;
1282
+ const recipes = useMemo(
1283
+ () => [
1284
+ { id: "text", label: labels.paragraph, source: "" },
1285
+ { id: "heading", label: labels.heading, source: "## " },
1286
+ { id: "list", label: labels.list, source: "- " },
1287
+ { id: "ordered", label: labels.ordered, source: "1. " },
1288
+ { id: "tasks", label: labels.tasks, source: "- [ ] " },
1289
+ { id: "quote", label: labels.quote, source: "> " },
1290
+ { id: "code", label: labels.code, source: "```\n\n```" },
1291
+ { id: "equation", label: labels.equation, source: "$$\n\\placeholder{}\n$$" },
1292
+ { id: "diagram", label: labels.diagram, source: "```mermaid\nflowchart LR\n A[Source] --> B[Question]\n```" },
1293
+ { id: "table", label: labels.table, source: "| A | B |\n| --- | --- |\n| | |" },
1294
+ ...insertions
1295
+ ],
1296
+ [labels, insertions]
1297
+ );
1298
+ function insertInlineMath() {
1299
+ if (!editor?.isEditable) return;
1300
+ if (editor.state.selection instanceof NodeSelection && editor.state.selection.node.type.name === "mathInline")
1301
+ return;
1302
+ const tr = editor.state.tr.replaceSelectionWith(editor.schema.nodes.mathInline.create({ tex: "\\placeholder{}" }));
1303
+ tr.setSelection(NodeSelection.create(tr.doc, tr.selection.from - 1));
1304
+ editor.view.dispatch(tr.setMeta("prismMathEntry", "start"));
1305
+ }
1306
+ function applyDocument(source) {
1307
+ if (!editor) return;
1308
+ if (source.length > maxSourceLength) throw Error("document_limit");
1309
+ const json = hydrateSource(source, editor.schema), doc = editor.schema.nodeFromJSON(json);
1310
+ editor.view.dispatch(
1311
+ editor.state.tr.replaceWith(0, editor.state.doc.content.size, doc.content).setDocAttribute("tail", doc.attrs.tail).scrollIntoView()
1312
+ );
1313
+ }
1314
+ function insert(recipe) {
1315
+ if (!editor?.isEditable) return;
1316
+ const selected = currentBlock(editor), old = selected && editor.state.doc.child(selected.index);
1317
+ const blocks = hydrateSource(recipe.source, editor.schema).content ?? [];
1318
+ const simple = {
1319
+ heading: { type: "heading", attrs: { level: 2 } },
1320
+ list: { type: "bulletList", content: [{ type: "listItem", content: [{ type: "paragraph" }] }] },
1321
+ ordered: { type: "orderedList", content: [{ type: "listItem", content: [{ type: "paragraph" }] }] },
1322
+ tasks: {
1323
+ type: "taskList",
1324
+ content: [{ type: "taskItem", attrs: { checked: false }, content: [{ type: "paragraph" }] }]
1325
+ },
1326
+ quote: { type: "blockquote", content: [{ type: "paragraph" }] }
1327
+ };
1328
+ if (simple[recipe.id] && blocks[0]) {
1329
+ blocks[0].content = [simple[recipe.id]];
1330
+ blocks[0].attrs.baseline = "";
1331
+ }
1332
+ const empty = old?.childCount === 1 && old.firstChild?.type.name === "paragraph" && old.firstChild.content.size === 0;
1333
+ const from = selected ? selected.pos + (empty ? 0 : old.nodeSize) : editor.state.doc.content.size;
1334
+ const to = empty ? from + old.nodeSize : from;
1335
+ const tr = editor.state.tr.replaceWith(
1336
+ from,
1337
+ to,
1338
+ blocks.map((b) => editor.schema.nodeFromJSON(b))
1339
+ );
1340
+ const first = tr.doc.nodeAt(from)?.firstChild;
1341
+ tr.setSelection(
1342
+ first?.isAtom && !first.isTextblock ? NodeSelection.create(tr.doc, from + 1) : TextSelection.near(tr.doc.resolve(Math.min(from + 2, tr.doc.content.size)))
1343
+ );
1344
+ editor.view.dispatch(tr.scrollIntoView());
1345
+ setPalette(false);
1346
+ editor.commands.focus();
1347
+ }
1348
+ function openSource(id) {
1349
+ if (!editor?.isEditable) return;
1350
+ const block = id ? findBlock(editor.state.doc, id) : currentBlock(editor);
1351
+ if (!block) return;
1352
+ setDialog({ kind: "block", pos: block.pos, text: blockSource(editor.state.doc.child(block.index).toJSON()) });
1353
+ }
1354
+ function applyDialog(text) {
1355
+ if (!editor?.isEditable || !dialog) return;
1356
+ try {
1357
+ if (dialog.kind === "document") applyDocument(text);
1358
+ else if (dialog.kind === "block") {
1359
+ const old = editor.state.doc.nodeAt(dialog.pos);
1360
+ if (!old) throw Error("stale_selection");
1361
+ const blocks = hydrateSource(text, editor.schema).content;
1362
+ blocks[0].attrs = { ...blocks[0].attrs, id: old.attrs.id, prefix: old.attrs.prefix };
1363
+ const tr = editor.state.tr.replaceWith(
1364
+ dialog.pos,
1365
+ dialog.pos + old.nodeSize,
1366
+ blocks.map((b) => editor.schema.nodeFromJSON(b))
1367
+ );
1368
+ if (snapshot(tr.doc).getSource().length > maxSourceLength) throw Error("document_limit");
1369
+ editor.view.dispatch(tr.scrollIntoView());
1370
+ } else {
1371
+ if (text && !/^(https?:\/\/|mailto:)/i.test(text)) throw Error("invalid_link");
1372
+ const chain = editor.chain().focus().extendMarkRange("link");
1373
+ if (text) chain.setLink({ href: text }).run();
1374
+ else chain.unsetLink().run();
1375
+ }
1376
+ setDialog(void 0);
1377
+ setInvalid("");
1378
+ editor.commands.focus();
1379
+ } catch {
1380
+ setInvalid(labels.invalid);
1381
+ }
1382
+ }
1383
+ const button = (label, action, disabled = false, pressed, icon) => /* @__PURE__ */ jsx(
1384
+ Button2,
1385
+ {
1386
+ size: "small",
1387
+ startIcon: icon,
1388
+ variant: pressed ? "contained" : "text",
1389
+ type: "button",
1390
+ title: label,
1391
+ "aria-label": label,
1392
+ "aria-pressed": pressed,
1393
+ disabled: readOnly || !editor || disabled,
1394
+ onMouseDown: (e) => e.preventDefault(),
1395
+ onClick: () => action(),
1396
+ children: label
1397
+ }
1398
+ );
1399
+ return /* @__PURE__ */ jsxs(
1400
+ Box,
1401
+ {
1402
+ className: ["prism-science", className].filter(Boolean).join(" "),
1403
+ sx: [
1404
+ (theme) => ({
1405
+ "--prism-editor-border": theme.palette.divider,
1406
+ "--prism-editor-bg": theme.palette.background.paper,
1407
+ "--prism-editor-muted": theme.palette.text.secondary,
1408
+ "--prism-editor-accent": theme.palette.primary.main,
1409
+ "--prism-editor-radius": `${theme.shape.borderRadius}px`
1410
+ }),
1411
+ ...Array.isArray(sx) ? sx : [sx]
1412
+ ],
1413
+ children: [
1414
+ /* @__PURE__ */ jsxs("div", { className: "prism-science__toolbar", role: "toolbar", "aria-label": labels.editor, children: [
1415
+ button(labels.insert, () => setPalette(true), false, void 0, /* @__PURE__ */ jsx(AddIcon, {})),
1416
+ button(
1417
+ labels.bold,
1418
+ () => {
1419
+ editor?.chain().focus().toggleBold().run();
1420
+ },
1421
+ false,
1422
+ selection?.bold
1423
+ ),
1424
+ button(
1425
+ labels.italic,
1426
+ () => {
1427
+ editor?.chain().focus().toggleItalic().run();
1428
+ },
1429
+ false,
1430
+ selection?.italic
1431
+ ),
1432
+ button(labels.link, () => setDialog({ kind: "link", text: String(editor?.getAttributes("link").href ?? "") })),
1433
+ button(labels.inlineMath, insertInlineMath),
1434
+ button(
1435
+ labels.undo,
1436
+ () => {
1437
+ editor?.chain().focus().undo().run();
1438
+ },
1439
+ !selection?.undo
1440
+ ),
1441
+ button(
1442
+ labels.redo,
1443
+ () => {
1444
+ editor?.chain().focus().redo().run();
1445
+ },
1446
+ !selection?.redo
1447
+ ),
1448
+ button(labels.source, () => {
1449
+ if (editor) setDialog({ kind: "document", text: snapshot(editor.state.doc).getSource() });
1450
+ })
1451
+ ] }),
1452
+ /* @__PURE__ */ jsx("p", { className: "prism-science__hint", children: labels.hint }),
1453
+ invalid && !dialog && /* @__PURE__ */ jsx("p", { role: "alert", children: invalid }),
1454
+ /* @__PURE__ */ jsxs(
1455
+ "div",
1456
+ {
1457
+ ref: workspace,
1458
+ className: `prism-science__workspace${blockControls ? " prism-science__workspace--controls" : ""}`,
1459
+ children: [
1460
+ /* @__PURE__ */ jsx(ResearchViewContext.Provider, { value: presentation, children: /* @__PURE__ */ jsx(ResearchMathContext.Provider, { value: { ...math2, readOnly }, children: /* @__PURE__ */ jsx(EditorContent, { editor, className: "prism-science__content" }) }) }),
1461
+ editor && blockControls && /* @__PURE__ */ jsx(
1462
+ BlockControls,
1463
+ {
1464
+ editor,
1465
+ container: workspace,
1466
+ labels,
1467
+ readOnly,
1468
+ onSource: openSource,
1469
+ onInsert: (id) => {
1470
+ if (!editor.isEditable) return;
1471
+ const block = findBlock(editor.state.doc, id);
1472
+ if (block) {
1473
+ editor.commands.setNodeSelection(block.pos);
1474
+ setPalette(true);
1475
+ }
1476
+ }
1477
+ }
1478
+ )
1479
+ ]
1480
+ }
1481
+ ),
1482
+ /* @__PURE__ */ jsxs("div", { className: "prism-science__block-tools", role: "group", "aria-label": labels.editBlock, children: [
1483
+ /* @__PURE__ */ jsxs("span", { "aria-live": "polite", "aria-atomic": "true", children: [
1484
+ blockIndex + 1,
1485
+ " / ",
1486
+ blockCount,
1487
+ " ",
1488
+ labels.blocks
1489
+ ] }),
1490
+ button(labels.editBlock, openSource),
1491
+ button(
1492
+ labels.up,
1493
+ () => {
1494
+ if (editor) runBlockAction(editor, "up");
1495
+ },
1496
+ blockIndex === 0
1497
+ ),
1498
+ button(
1499
+ labels.down,
1500
+ () => {
1501
+ if (editor) runBlockAction(editor, "down");
1502
+ },
1503
+ blockIndex === blockCount - 1
1504
+ ),
1505
+ button(labels.duplicate, () => {
1506
+ if (editor) runBlockAction(editor, "duplicate");
1507
+ }),
1508
+ button(labels.remove, () => {
1509
+ if (editor) runBlockAction(editor, "remove");
1510
+ })
1511
+ ] }),
1512
+ /* @__PURE__ */ jsxs(Dialog, { open: palette, onClose: () => setPalette(false), fullWidth: true, maxWidth: "sm", children: [
1513
+ /* @__PURE__ */ jsx(DialogTitle, { children: labels.insert }),
1514
+ /* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsx(
1515
+ Autocomplete,
1516
+ {
1517
+ value: null,
1518
+ openText: labels.insert,
1519
+ closeText: labels.cancel,
1520
+ options: recipes,
1521
+ filterOptions: filterInsertions,
1522
+ getOptionLabel: (r) => r.label,
1523
+ onChange: (_e, value) => {
1524
+ if (value) insert(value);
1525
+ },
1526
+ renderInput: (params) => /* @__PURE__ */ jsx(TextField2, { ...params, autoFocus: true, label: labels.search, margin: "dense" }),
1527
+ openOnFocus: true,
1528
+ disablePortal: true
1529
+ }
1530
+ ) }),
1531
+ /* @__PURE__ */ jsx(DialogActions, { children: /* @__PURE__ */ jsx(Button2, { onClick: () => setPalette(false), children: labels.cancel }) })
1532
+ ] }),
1533
+ dialog && /* @__PURE__ */ jsx(
1534
+ SourceDialog,
1535
+ {
1536
+ title: dialog.kind === "document" ? labels.source : dialog.kind === "link" ? labels.link : labels.editBlock,
1537
+ initial: dialog.text,
1538
+ maxLength: maxSourceLength,
1539
+ labels,
1540
+ error: invalid,
1541
+ onCancel: () => {
1542
+ setDialog(void 0);
1543
+ setInvalid("");
1544
+ },
1545
+ onApply: applyDialog
1546
+ },
1547
+ `${dialog.kind}:${dialog.pos}`
1548
+ )
1549
+ ]
1550
+ }
1551
+ );
1552
+ }
1553
+ function SourceDialog({
1554
+ title,
1555
+ initial,
1556
+ maxLength,
1557
+ labels,
1558
+ error,
1559
+ onCancel,
1560
+ onApply
1561
+ }) {
1562
+ const [text, setText] = useState(initial);
1563
+ return /* @__PURE__ */ jsxs(Dialog, { open: true, onClose: onCancel, fullWidth: true, maxWidth: "md", children: [
1564
+ /* @__PURE__ */ jsx(DialogTitle, { children: title }),
1565
+ /* @__PURE__ */ jsxs(DialogContent, { children: [
1566
+ /* @__PURE__ */ jsx(
1567
+ TextField2,
1568
+ {
1569
+ label: title,
1570
+ value: text,
1571
+ onChange: (e) => setText(e.target.value),
1572
+ multiline: true,
1573
+ minRows: 5,
1574
+ maxRows: 20,
1575
+ fullWidth: true,
1576
+ autoFocus: true,
1577
+ margin: "dense",
1578
+ slotProps: { htmlInput: { maxLength, spellCheck: false } }
1579
+ }
1580
+ ),
1581
+ error && /* @__PURE__ */ jsx("p", { role: "alert", children: error })
1582
+ ] }),
1583
+ /* @__PURE__ */ jsxs(DialogActions, { children: [
1584
+ /* @__PURE__ */ jsx(Button2, { onClick: onCancel, children: labels.cancel }),
1585
+ /* @__PURE__ */ jsx(Button2, { onClick: () => onApply(text), variant: "contained", children: labels.apply })
1586
+ ] })
1587
+ ] });
1588
+ }
1589
+
1590
+ export { MathInput, ResearchEditor, defaultMathInputLabels, defaultResearchEditorLabels };
1591
+ //# sourceMappingURL=index.js.map
1592
+ //# sourceMappingURL=index.js.map