@bendyline/squisq-editor-react 1.6.1 → 2.0.0

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 (208) hide show
  1. package/README.md +12 -3
  2. package/dist/index.d.ts +1304 -208
  3. package/dist/index.js +22838 -14494
  4. package/dist/index.js.map +1 -1
  5. package/dist/monaco.d.ts +1 -0
  6. package/dist/monaco.js +4 -0
  7. package/dist/monaco.js.map +1 -0
  8. package/dist/styles/index.css +2384 -408
  9. package/package.json +9 -5
  10. package/src/BlockPropertiesPopover.tsx +48 -17
  11. package/src/DocumentSettingsDialog.tsx +13 -21
  12. package/src/EditorContext.tsx +199 -42
  13. package/src/EditorShell.tsx +403 -270
  14. package/src/ImageEditor.tsx +22 -1
  15. package/src/InlinePreviewGutter.tsx +16 -8
  16. package/src/MediaBin.tsx +276 -40
  17. package/src/OutlinePanel.tsx +202 -7
  18. package/src/PlainHtmlPreview.tsx +40 -1
  19. package/src/PreviewControls.tsx +466 -93
  20. package/src/PreviewPanel.tsx +313 -77
  21. package/src/RawEditor.tsx +13 -2
  22. package/src/StatusBar.tsx +5 -2
  23. package/src/TemplateAnnotation.ts +34 -10
  24. package/src/TemplateContentPreview.tsx +56 -0
  25. package/src/TemplatePicker.tsx +355 -134
  26. package/src/ThemeCustomizerPanel.tsx +30 -15
  27. package/src/ThemePicker.tsx +10 -5
  28. package/src/TimelineBlockPreview.tsx +1 -1
  29. package/src/Toolbar.tsx +719 -238
  30. package/src/TransitionPicker.tsx +44 -5
  31. package/src/ViewMenuPanel.tsx +12 -14
  32. package/src/ViewSwitcher.tsx +4 -4
  33. package/src/WysiwygEditor.tsx +167 -92
  34. package/src/__tests__/blockPropertiesPopoverTheme.test.tsx +33 -0
  35. package/src/__tests__/blockTagActivity.test.ts +183 -0
  36. package/src/__tests__/buildDocumentPreviewMarkdown.test.ts +75 -0
  37. package/src/__tests__/buildPreviewDocContent.test.ts +48 -0
  38. package/src/__tests__/buildPreviewDocTransition.test.ts +22 -3
  39. package/src/__tests__/canvasSurfaceTextEditing.test.tsx +60 -0
  40. package/src/__tests__/customLayoutManagerTheme.test.tsx +34 -0
  41. package/src/__tests__/documentSettingsDialog.test.tsx +29 -1
  42. package/src/__tests__/editorScrollHandoff.test.tsx +102 -0
  43. package/src/__tests__/editorScrollSync.test.ts +65 -0
  44. package/src/__tests__/editorShellProps.test.tsx +390 -1
  45. package/src/__tests__/emojiPicker.test.tsx +2 -42
  46. package/src/__tests__/headingTransition.test.ts +53 -15
  47. package/src/__tests__/imageEditAffordance.test.tsx +5 -1
  48. package/src/__tests__/imageEditModalContract.test.tsx +119 -0
  49. package/src/__tests__/imageEditorShell.test.tsx +38 -1
  50. package/src/__tests__/jsonEditor.test.tsx +56 -1
  51. package/src/__tests__/layersPanel.test.tsx +75 -5
  52. package/src/__tests__/majorApiTypes.test.ts +26 -0
  53. package/src/__tests__/mediaBinDrop.test.tsx +90 -0
  54. package/src/__tests__/mediaReferences.test.ts +82 -0
  55. package/src/__tests__/narrationSave.test.ts +148 -0
  56. package/src/__tests__/outlinePanel.test.tsx +93 -3
  57. package/src/__tests__/outlineSource.test.ts +217 -0
  58. package/src/__tests__/plainHtmlPreview.test.tsx +17 -1
  59. package/src/__tests__/presentationMode.test.tsx +290 -0
  60. package/src/__tests__/previewControls.test.tsx +312 -6
  61. package/src/__tests__/previewPanelAudioFailure.test.tsx +46 -0
  62. package/src/__tests__/previewPanelPresentation.test.tsx +126 -0
  63. package/src/__tests__/previewPanelTransforms.test.tsx +117 -0
  64. package/src/__tests__/rawEditorModelIsolation.test.ts +19 -0
  65. package/src/__tests__/resolveBlockVisual.test.ts +43 -0
  66. package/src/__tests__/statusBar.test.tsx +27 -0
  67. package/src/__tests__/teleprompter.test.tsx +190 -0
  68. package/src/__tests__/templateAnnotationRoundTrip.test.ts +49 -2
  69. package/src/__tests__/templateContentPreview.test.ts +101 -0
  70. package/src/__tests__/templatePickerPortal.test.tsx +62 -0
  71. package/src/__tests__/tiptapBridge.test.ts +70 -0
  72. package/src/__tests__/tiptapCodeBlockRoundTrip.test.ts +109 -0
  73. package/src/__tests__/transformStyleId.test.ts +13 -0
  74. package/src/__tests__/useImageEditor.test.tsx +67 -0
  75. package/src/__tests__/useMediaRecorder.test.ts +75 -0
  76. package/src/__tests__/viewMenuPanel.test.tsx +130 -0
  77. package/src/__tests__/wysiwygImageUpload.test.ts +69 -0
  78. package/src/__tests__/wysiwygTemplateBadgeFocus.test.tsx +56 -0
  79. package/src/asciiDiagram/AsciiDiagramExtension.ts +343 -0
  80. package/src/asciiDiagram/AsciiDiagramWidget.tsx +224 -0
  81. package/src/asciiDiagram/RepairableDiagramExtension.ts +200 -0
  82. package/src/asciiDiagram/__tests__/AsciiDiagramExtension.test.ts +145 -0
  83. package/src/asciiDiagram/__tests__/RepairableDiagramExtension.test.ts +159 -0
  84. package/src/asciiDiagram/__tests__/asciiDiagramCommands.test.ts +227 -0
  85. package/src/asciiDiagram/__tests__/asciiDiagramOps.test.ts +114 -0
  86. package/src/asciiDiagram/__tests__/asciiPaste.test.ts +43 -0
  87. package/src/asciiDiagram/asciiDiagramCommands.ts +148 -0
  88. package/src/asciiDiagram/asciiDiagramData.ts +116 -0
  89. package/src/asciiDiagram/asciiDiagramOps.ts +219 -0
  90. package/src/asciiDiagram/asciiPaste.ts +19 -0
  91. package/src/blockTagActivity.ts +233 -0
  92. package/src/buildDocumentPreviewMarkdown.ts +168 -0
  93. package/src/buildPreviewDoc.ts +19 -12
  94. package/src/customTemplates/CustomLayoutManager.tsx +3 -2
  95. package/src/customTemplates/CustomTemplateContext.tsx +6 -0
  96. package/src/customTemplates/TemplateDesigner.tsx +7 -4
  97. package/src/customTemplates/__tests__/library.test.ts +8 -0
  98. package/src/customTemplates/designer.css +158 -99
  99. package/src/customTemplates/library.ts +16 -3
  100. package/src/customThemes/CustomThemeContext.tsx +6 -0
  101. package/src/customThemes/CustomThemeDialog.tsx +35 -5
  102. package/src/customThemes/ImportThemeSection.tsx +178 -0
  103. package/src/customThemes/__tests__/customThemeLibrary.test.ts +9 -0
  104. package/src/customThemes/__tests__/importThemeSection.test.tsx +192 -0
  105. package/src/customThemes/customThemeLibrary.ts +7 -4
  106. package/src/customThemes/index.ts +7 -1
  107. package/src/customThemes/themeDraft.ts +23 -7
  108. package/src/diagram/DiagramCanvas.tsx +9 -4
  109. package/src/diagram/types.ts +35 -0
  110. package/src/editorScrollSync.ts +73 -0
  111. package/src/emojiData.ts +3 -23
  112. package/src/headingTransition.ts +85 -25
  113. package/src/imageEditor/CanvasSurface.tsx +29 -23
  114. package/src/imageEditor/LayersPanel.tsx +78 -3
  115. package/src/imageEditor/Toolbar.tsx +5 -1
  116. package/src/imageEditor/icons.tsx +4 -0
  117. package/src/imageEditor/image-editor.css +75 -0
  118. package/src/imageEditor/layers/SelectionHandles.tsx +1 -1
  119. package/src/imageEditor/useImageEditor.ts +71 -14
  120. package/src/index.ts +193 -20
  121. package/src/jsonEditor/JsonEditorContext.tsx +12 -6
  122. package/src/jsonEditor/RenderNode.tsx +24 -3
  123. package/src/jsonEditor/editors.tsx +86 -19
  124. package/src/mediaEntries.ts +12 -0
  125. package/src/mediaReferences.ts +299 -0
  126. package/src/monaco.ts +35 -0
  127. package/src/monacoWorkers.ts +89 -0
  128. package/src/outlineSource.ts +171 -0
  129. package/src/presentation/PresentationMode.tsx +596 -0
  130. package/src/rawEditorIsolation.ts +18 -0
  131. package/src/recorder/hooks/useMediaRecorder.ts +97 -53
  132. package/src/resolveBlockVisual.ts +10 -4
  133. package/src/scene/Scene.tsx +101 -25
  134. package/src/scene/SceneBlockExtension.ts +17 -10
  135. package/src/scene/SceneBlockWidget.tsx +11 -4
  136. package/src/scene/SceneSelection.tsx +19 -15
  137. package/src/scene/SceneSideToolbar.tsx +89 -0
  138. package/src/scene/SceneViewport.tsx +7 -3
  139. package/src/scene/ShapePalette.tsx +17 -2
  140. package/src/scene/__tests__/DiagramAdapter.test.ts +86 -0
  141. package/src/scene/__tests__/SceneBlockExtension.test.ts +33 -0
  142. package/src/scene/__tests__/sceneIsolation.test.tsx +93 -0
  143. package/src/scene/adapters/DiagramAdapter.ts +12 -101
  144. package/src/scene/commands/SceneCommand.ts +4 -1
  145. package/src/scene/index.ts +1 -6
  146. package/src/scene/layers/DiagramEdges.tsx +24 -9
  147. package/src/scene/layers/edgeGeometry.ts +31 -4
  148. package/src/scene/layers/nodeCard.tsx +27 -8
  149. package/src/scene/scene.css +145 -2
  150. package/src/scene/text/SceneTextOverlay.tsx +5 -5
  151. package/src/scene/text/sceneTextChannel.ts +26 -20
  152. package/src/scene/text/sceneTextConfig.ts +4 -0
  153. package/src/scene/tools/ConnectTool.ts +126 -28
  154. package/src/scene/tools/DrawingConnectTool.ts +86 -16
  155. package/src/scene/tools/SceneTool.ts +10 -0
  156. package/src/scene/tools/SelectTool.ts +34 -18
  157. package/src/scene/tools/ShapeTool.ts +2 -3
  158. package/src/styles/ascii-diagram.css +79 -0
  159. package/src/styles/ascii-timeline.css +499 -0
  160. package/src/styles/editor.css +1566 -219
  161. package/src/styles/index.css +3 -0
  162. package/src/styles/tree-view.css +139 -0
  163. package/src/teleprompter/TeleprompterControls.tsx +218 -0
  164. package/src/teleprompter/TeleprompterSelfView.tsx +22 -0
  165. package/src/teleprompter/TeleprompterSurface.tsx +267 -0
  166. package/src/teleprompter/TeleprompterView.tsx +338 -0
  167. package/src/teleprompter/canvasRenderer.ts +161 -0
  168. package/src/teleprompter/floatingWindow.ts +352 -0
  169. package/src/teleprompter/index.ts +61 -0
  170. package/src/teleprompter/pcmWorklet.ts +62 -0
  171. package/src/teleprompter/recording/insertPreamble.ts +80 -0
  172. package/src/teleprompter/recording/narrationSave.ts +134 -0
  173. package/src/teleprompter/recording/useNarrationRecorder.ts +367 -0
  174. package/src/teleprompter/scrollModel.ts +85 -0
  175. package/src/teleprompter/teleprompterTheme.ts +303 -0
  176. package/src/teleprompter/types.ts +38 -0
  177. package/src/teleprompter/useFloatingWindow.ts +74 -0
  178. package/src/teleprompter/useMicAnalysis.ts +211 -0
  179. package/src/teleprompter/useTeleprompter.ts +421 -0
  180. package/src/templateContentPreviewResolver.ts +360 -0
  181. package/src/timeline/TimelineEditorWidget.tsx +702 -0
  182. package/src/timeline/TimelineViewExtension.ts +252 -0
  183. package/src/timeline/__tests__/TimelineEditorWidget.test.tsx +355 -0
  184. package/src/timeline/__tests__/TimelineViewExtension.test.ts +163 -0
  185. package/src/timeline/__tests__/timelineCommands.test.ts +327 -0
  186. package/src/timeline/__tests__/timelineOps.test.ts +222 -0
  187. package/src/timeline/__tests__/timelinePaste.test.ts +34 -0
  188. package/src/timeline/timelineCommands.ts +367 -0
  189. package/src/timeline/timelineData.ts +54 -0
  190. package/src/timeline/timelineOps.ts +277 -0
  191. package/src/timeline/timelinePaste.ts +8 -0
  192. package/src/tiptapBridge.ts +84 -49
  193. package/src/transformStyleId.ts +17 -0
  194. package/src/treeview/TreeOutlineWidget.tsx +240 -0
  195. package/src/treeview/TreeViewExtension.ts +250 -0
  196. package/src/treeview/__tests__/TreeViewExtension.test.ts +122 -0
  197. package/src/treeview/__tests__/treeOps.test.ts +125 -0
  198. package/src/treeview/__tests__/treePaste.test.ts +40 -0
  199. package/src/treeview/__tests__/treeViewCommands.test.ts +163 -0
  200. package/src/treeview/treeOps.ts +172 -0
  201. package/src/treeview/treePaste.ts +13 -0
  202. package/src/treeview/treeViewCommands.ts +94 -0
  203. package/src/treeview/treeViewData.ts +41 -0
  204. package/src/useMonacoLoader.ts +11 -34
  205. package/src/wysiwygImageUpload.ts +113 -0
  206. package/src/diagram/DiagramExtension.ts +0 -209
  207. package/src/diagram/DiagramWidget.tsx +0 -270
  208. package/src/diagram/useDiagramData.ts +0 -126
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Monaco language-service worker wiring.
3
+ *
4
+ * Monaco offloads its heavy language services — css / html / json / typescript
5
+ * IntelliSense — plus a base editor service (word-based completions, link
6
+ * detection, diffing) to web workers. Those worker bundles must be produced by
7
+ * the HOST application's bundler: the mechanisms for it (Vite's `?worker`
8
+ * import suffix, `new Worker(new URL(...))`, webpack loaders) are all
9
+ * bundler-specific and cannot live inside this tsup-built library.
10
+ *
11
+ * So the division of labor is: the host supplies the five worker constructors
12
+ * (one line each with Vite's `?worker`), and this helper owns the
13
+ * `label → worker` mapping — the part that's fiddly and easy to get wrong.
14
+ *
15
+ * Call once, before the first editor mounts (typically in the app entry):
16
+ *
17
+ * ```ts
18
+ * import { configureMonacoWorkers } from '@bendyline/squisq-editor-react';
19
+ * import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
20
+ * import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
21
+ * import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
22
+ * import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
23
+ * import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
24
+ *
25
+ * configureMonacoWorkers({
26
+ * editor: EditorWorker, json: JsonWorker, css: CssWorker,
27
+ * html: HtmlWorker, ts: TsWorker,
28
+ * });
29
+ * ```
30
+ *
31
+ * This is purely additive: without it, highlighting, editing, and custom
32
+ * completion providers (e.g. the `{[template]}` typeahead) still work — they
33
+ * run on the main thread. Only the language-service IntelliSense is dormant
34
+ * until the workers are wired.
35
+ */
36
+
37
+ /** Zero-arg worker constructor, as produced by Vite's `?worker` import. */
38
+ export type MonacoWorkerConstructor = new () => Worker;
39
+
40
+ export interface MonacoWorkerConstructors {
41
+ /** Base editor worker (word completions, links, diff). Required. */
42
+ editor: MonacoWorkerConstructor;
43
+ /** JSON language service. */
44
+ json?: MonacoWorkerConstructor;
45
+ /** CSS/SCSS/LESS language service. */
46
+ css?: MonacoWorkerConstructor;
47
+ /** HTML/Handlebars/Razor language service. */
48
+ html?: MonacoWorkerConstructor;
49
+ /** TypeScript service — also handles JavaScript. */
50
+ ts?: MonacoWorkerConstructor;
51
+ }
52
+
53
+ type MonacoEnvironmentHost = typeof globalThis & {
54
+ MonacoEnvironment?: { getWorker(workerId: string, label: string): Worker };
55
+ };
56
+
57
+ /**
58
+ * Install `globalThis.MonacoEnvironment.getWorker` so Monaco routes each
59
+ * language to the matching worker, falling back to the base editor worker for
60
+ * any label without a dedicated service (which is every plain language — its
61
+ * grammar-based highlighting needs no worker).
62
+ */
63
+ export function configureMonacoWorkers(workers: MonacoWorkerConstructors): void {
64
+ const host = globalThis as MonacoEnvironmentHost;
65
+ host.MonacoEnvironment = {
66
+ getWorker(_workerId: string, label: string): Worker {
67
+ switch (label) {
68
+ case 'json':
69
+ if (workers.json) return new workers.json();
70
+ break;
71
+ case 'css':
72
+ case 'scss':
73
+ case 'less':
74
+ if (workers.css) return new workers.css();
75
+ break;
76
+ case 'html':
77
+ case 'handlebars':
78
+ case 'razor':
79
+ if (workers.html) return new workers.html();
80
+ break;
81
+ case 'typescript':
82
+ case 'javascript':
83
+ if (workers.ts) return new workers.ts();
84
+ break;
85
+ }
86
+ return new workers.editor();
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Source-preserving section moves for the document outline.
3
+ *
4
+ * Markdown headings form a flat AST, so a section extends from its heading
5
+ * through the next heading of equal or shallower depth. Reordering only direct
6
+ * siblings keeps heading levels and hierarchy intact; promote/demote remains a
7
+ * separate outline operation.
8
+ */
9
+
10
+ import { parseMarkdown } from '@bendyline/squisq/markdown';
11
+
12
+ export type OutlineDropPlacement = 'before' | 'after';
13
+
14
+ interface SourceHeading {
15
+ line: number;
16
+ depth: number;
17
+ parentLine: number | null;
18
+ start: number;
19
+ end: number;
20
+ }
21
+
22
+ interface SectionSlot extends SourceHeading {
23
+ content: string;
24
+ gap: string;
25
+ }
26
+
27
+ /**
28
+ * Move one heading-rooted section before or after a sibling section.
29
+ *
30
+ * The current source is parsed inside the helper so fenced-code lookalikes and
31
+ * stale/non-heading line numbers are rejected. The moved section includes its
32
+ * body and every descendant heading. Returns `null` for an invalid or no-op
33
+ * move.
34
+ */
35
+ export function moveHeadingSectionInSource(
36
+ source: string,
37
+ fromHeadingLine: number,
38
+ targetHeadingLine: number,
39
+ placement: OutlineDropPlacement,
40
+ ): string | null {
41
+ if (fromHeadingLine === targetHeadingLine || (placement !== 'before' && placement !== 'after')) {
42
+ return null;
43
+ }
44
+
45
+ const headings = readSourceHeadings(source);
46
+ if (!headings) return null;
47
+
48
+ const from = headings.find((heading) => heading.line === fromHeadingLine);
49
+ const target = headings.find((heading) => heading.line === targetHeadingLine);
50
+ if (!from || !target) return null;
51
+
52
+ // A reorder must not silently promote, demote, or re-parent a section.
53
+ if (from.depth !== target.depth || from.parentLine !== target.parentLine) return null;
54
+
55
+ const siblings = headings.filter((heading) => heading.parentLine === from.parentLine);
56
+ const fromIndex = siblings.findIndex((heading) => heading.line === fromHeadingLine);
57
+ const targetIndex = siblings.findIndex((heading) => heading.line === targetHeadingLine);
58
+ if (fromIndex < 0 || targetIndex < 0) return null;
59
+
60
+ // Direct sibling ranges must be contiguous. Aborting here is safer than
61
+ // rewriting source if a parser ever reports an unexpected position shape.
62
+ for (let i = 0; i < siblings.length - 1; i++) {
63
+ if (siblings[i].end !== siblings[i + 1].start) return null;
64
+ }
65
+
66
+ const slots: SectionSlot[] = siblings.map((heading) => {
67
+ const raw = source.slice(heading.start, heading.end);
68
+ const { content, gap } = splitTrailingLineGap(raw);
69
+ return { ...heading, content, gap };
70
+ });
71
+
72
+ const reordered = [...slots];
73
+ const [moved] = reordered.splice(fromIndex, 1);
74
+ const targetAfterRemoval = reordered.findIndex((heading) => heading.line === targetHeadingLine);
75
+ if (!moved || targetAfterRemoval < 0) return null;
76
+
77
+ const insertionIndex = targetAfterRemoval + (placement === 'after' ? 1 : 0);
78
+ reordered.splice(insertionIndex, 0, moved);
79
+
80
+ if (reordered.every((heading, index) => heading.line === slots[index].line)) return null;
81
+
82
+ // Whitespace belongs to positions, not to the section being moved. Keeping
83
+ // the original slot gaps preserves blank-line style, CRLF, and the exact
84
+ // final-newline state even when the former EOF section moves earlier.
85
+ const replacement = reordered
86
+ .map((heading, index) => heading.content + slots[index].gap)
87
+ .join('');
88
+ const rangeStart = slots[0].start;
89
+ const rangeEnd = slots[slots.length - 1].end;
90
+ const next = source.slice(0, rangeStart) + replacement + source.slice(rangeEnd);
91
+
92
+ // The move should never change the number of authored headings. This also
93
+ // catches any unforeseen boundary issue before the editor source is updated.
94
+ const nextHeadings = readSourceHeadings(next);
95
+ if (!nextHeadings || nextHeadings.length !== headings.length) return null;
96
+
97
+ return next;
98
+ }
99
+
100
+ /** Parse source headings and derive their parent + complete-section range. */
101
+ function readSourceHeadings(source: string): SourceHeading[] | null {
102
+ try {
103
+ const document = parseMarkdown(source);
104
+ const lineStarts = sourceLineStarts(source);
105
+ const headings: SourceHeading[] = [];
106
+ const stack: SourceHeading[] = [];
107
+
108
+ for (const node of document.children) {
109
+ if (node.type !== 'heading' || !node.position) continue;
110
+
111
+ const line = node.position.start.line;
112
+ const start = lineStarts[line - 1];
113
+ if (start == null) return null;
114
+
115
+ while (stack.length > 0 && stack[stack.length - 1].depth >= node.depth) {
116
+ stack.pop();
117
+ }
118
+
119
+ const heading: SourceHeading = {
120
+ line,
121
+ depth: node.depth,
122
+ parentLine: stack[stack.length - 1]?.line ?? null,
123
+ start,
124
+ end: source.length,
125
+ };
126
+ headings.push(heading);
127
+ stack.push(heading);
128
+ }
129
+
130
+ for (let i = 0; i < headings.length; i++) {
131
+ const heading = headings[i];
132
+ for (let j = i + 1; j < headings.length; j++) {
133
+ if (headings[j].depth <= heading.depth) {
134
+ heading.end = headings[j].start;
135
+ break;
136
+ }
137
+ }
138
+ }
139
+
140
+ return headings;
141
+ } catch (err: unknown) {
142
+ // Source mutations are best-effort UI operations. A parse failure leaves
143
+ // the current markdown untouched; EditorContext owns user-facing errors.
144
+ void err;
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /** 0-based offsets of every physical line, preserving mixed EOL sequences. */
150
+ function sourceLineStarts(source: string): number[] {
151
+ const starts = [0];
152
+ const eol = /\r\n|\r|\n/g;
153
+ let match: RegExpExecArray | null;
154
+ while ((match = eol.exec(source)) != null) {
155
+ starts.push(match.index + match[0].length);
156
+ }
157
+ return starts;
158
+ }
159
+
160
+ /**
161
+ * Split the line-ending separator from a section without stealing spaces
162
+ * before the first newline (which may be an authored Markdown hard break).
163
+ */
164
+ function splitTrailingLineGap(value: string): { content: string; gap: string } {
165
+ const match = value.match(/(?:\r\n|\r|\n)(?:[ \t]*(?:\r\n|\r|\n))*[ \t]*$/);
166
+ if (!match || match.index == null) return { content: value, gap: '' };
167
+ return {
168
+ content: value.slice(0, match.index),
169
+ gap: value.slice(match.index),
170
+ };
171
+ }