@lalternative/editor 0.8.3 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/editor.css CHANGED
@@ -48,18 +48,21 @@
48
48
  variable; it is 0 whenever nothing covers anything. */
49
49
  .lalt-toolbar {
50
50
  position: fixed;
51
- left: 0.5rem;
52
- right: 0.5rem;
53
- bottom: calc(var(--lalt-keyboard-inset, 0px) + 0.5rem);
51
+ /* Beside the text it acts on, not docked to the bottom. Selecting dismisses
52
+ the keyboard on touch, so there is no strip to dock to and no system bar
53
+ to be drawn under. */
54
+ left: var(--lalt-toolbar-left, 0);
55
+ top: var(--lalt-toolbar-top, 0);
56
+ right: auto;
57
+ bottom: auto;
54
58
  z-index: 100;
55
59
  display: flex;
56
60
  align-items: center;
57
- justify-content: center;
61
+ justify-content: flex-start;
58
62
  gap: 0.125rem;
59
63
  width: fit-content;
60
- max-width: calc(100% - 1rem);
61
- margin: 0 auto;
62
- margin-bottom: env(safe-area-inset-bottom);
64
+ max-width: calc(100vw - 1rem);
65
+ margin: 0;
63
66
  padding: 0.25rem;
64
67
  border-radius: 999px;
65
68
  background: var(--lalt-editor-surface, #ffffff);
@@ -87,7 +90,13 @@
87
90
  top: var(--lalt-toolbar-top, 0);
88
91
  right: auto;
89
92
  bottom: auto;
93
+ /* Back to a pill: the band shape belongs to the strip above a keyboard,
94
+ and there is no keyboard here — the bar appears mid-paragraph. */
95
+ width: fit-content;
90
96
  max-width: none;
97
+ min-height: 0;
98
+ border-radius: 999px;
99
+ border: 1px solid var(--lalt-editor-border, rgb(0 0 0 / 0.1));
91
100
  margin: 0;
92
101
  padding: 0.1875rem;
93
102
  /* Deep enough to read as floating over the text rather than sitting in
package/dist/index.d.ts CHANGED
@@ -73,6 +73,11 @@ interface SelectionInfo {
73
73
  *
74
74
  * Returns null whenever there is nothing to act on, so the caller renders the
75
75
  * toolbar only while it has a target.
76
+ *
77
+ * On a touch device the keyboard is dismissed as soon as a range is selected.
78
+ * There is nothing to type while choosing a format, and the strip above the
79
+ * keyboard belongs to the system — a toolbar left to fight for it is drawn
80
+ * underneath. Out of the way, the toolbar sits by the text it acts on.
76
81
  */
77
82
  declare function useSelectionToolbar(editor: Editor | null): SelectionInfo | null;
78
83
 
package/dist/index.js CHANGED
@@ -201,6 +201,10 @@ function SelectionToolbar({
201
201
 
202
202
  // src/useSelectionToolbar.ts
203
203
  import { useEffect as useEffect3, useState as useState3 } from "react";
204
+ function keyboardIsInTheWay() {
205
+ if (typeof window === "undefined" || !window.matchMedia) return false;
206
+ return window.matchMedia("(hover: none) and (pointer: coarse)").matches;
207
+ }
204
208
  function useSelectionToolbar(editor) {
205
209
  const [selection, setSelection] = useState3(null);
206
210
  useEffect3(() => {
@@ -208,7 +212,7 @@ function useSelectionToolbar(editor) {
208
212
  const read2 = () => {
209
213
  const { state } = editor;
210
214
  const { from, to, empty } = state.selection;
211
- if (empty || !editor.isFocused) {
215
+ if (empty) {
212
216
  setSelection(null);
213
217
  return;
214
218
  }
@@ -217,6 +221,9 @@ function useSelectionToolbar(editor) {
217
221
  setSelection(null);
218
222
  return;
219
223
  }
224
+ if (keyboardIsInTheWay() && editor.isFocused) {
225
+ editor.view.dom.blur();
226
+ }
220
227
  const start = editor.view.coordsAtPos(from);
221
228
  const end = editor.view.coordsAtPos(to);
222
229
  setSelection({
@@ -253,33 +260,41 @@ var defaultFormatLabels = {
253
260
  function blockFormats(editor, labels = defaultFormatLabels) {
254
261
  if (!editor) return [];
255
262
  const mod = isApple() ? "\u2318" : "Ctrl";
263
+ const act = (run) => {
264
+ const focused = editor.isFocused;
265
+ if (focused) return run();
266
+ editor.commands.focus(void 0, { scrollIntoView: false });
267
+ const applied = run();
268
+ editor.view.dom.blur();
269
+ return applied;
270
+ };
256
271
  return [
257
272
  {
258
273
  id: "paragraph",
259
274
  label: labels.paragraph,
260
275
  isActive: editor.isActive("paragraph"),
261
- onSelect: () => editor.chain().focus().setParagraph().run()
276
+ onSelect: () => act(() => editor.chain().setParagraph().run())
262
277
  },
263
278
  ...[1, 2, 3].map((level) => ({
264
279
  id: `heading${level}`,
265
280
  label: labels[`heading${level}`],
266
281
  shortcut: `${mod}+Alt+${level}`,
267
282
  isActive: editor.isActive("heading", { level }),
268
- onSelect: () => editor.chain().focus().toggleHeading({ level }).run()
283
+ onSelect: () => act(() => editor.chain().toggleHeading({ level }).run())
269
284
  })),
270
285
  {
271
286
  id: "orderedList",
272
287
  label: labels.orderedList,
273
288
  shortcut: `${mod}+Shift+7`,
274
289
  isActive: editor.isActive("orderedList"),
275
- onSelect: () => editor.chain().focus().toggleOrderedList().run()
290
+ onSelect: () => act(() => editor.chain().toggleOrderedList().run())
276
291
  },
277
292
  {
278
293
  id: "bulletList",
279
294
  label: labels.bulletList,
280
295
  shortcut: `${mod}+Shift+8`,
281
296
  isActive: editor.isActive("bulletList"),
282
- onSelect: () => editor.chain().focus().toggleBulletList().run()
297
+ onSelect: () => act(() => editor.chain().toggleBulletList().run())
283
298
  }
284
299
  ];
285
300
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/placement.ts","../src/SelectionToolbar.tsx","../src/useViewport.ts","../src/viewport.ts","../src/useSelectionToolbar.ts","../src/formats.ts","../src/EditorScreen.tsx","../src/slash.ts","../src/SlashMenu.tsx","../src/SlashCommands.ts","../src/InlineSuggestions.ts","../src/passage.ts","../src/markdown.ts"],"sourcesContent":["export interface SelectionRect {\n /** Horizontal centre of the selection. */\n x: number;\n top: number;\n bottom: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface PlaceToolbarInput {\n selection: SelectionRect;\n toolbar: Size;\n viewport: Size;\n /** iOS renders its own callout above a selection, which covers a toolbar\n * placed there, so the caller asks for below on that platform. */\n preferBelow?: boolean;\n}\n\nexport interface ToolbarPlacement {\n left: number;\n top: number;\n below: boolean;\n}\n\nconst GAP = 8;\n\n/**\n * placeToolbar positions a floating toolbar against a selection.\n *\n * Kept apart from the component because it is the part that is wrong in subtle\n * ways — off-screen at the edges, hidden under a system callout — and the part\n * a test can pin down without a browser.\n */\nexport function placeToolbar({\n selection,\n toolbar,\n viewport,\n preferBelow = false,\n}: PlaceToolbarInput): ToolbarPlacement {\n const fitsAbove = selection.top - GAP - toolbar.height >= 0;\n const fitsBelow = selection.bottom + GAP + toolbar.height <= viewport.height;\n\n const below = preferBelow ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n\n const top = below\n ? selection.bottom + GAP\n : Math.max(0, selection.top - GAP - toolbar.height);\n\n const centred = selection.x - toolbar.width / 2;\n const left = Math.max(0, Math.min(centred, viewport.width - toolbar.width));\n\n return { left, top, below };\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { placeToolbar, type SelectionRect } from \"./placement\";\nimport { useViewport } from \"./useViewport\";\n\nexport interface ToolbarAction {\n id: string;\n label: string;\n icon?: React.ReactNode;\n onSelect: (selectedText: string) => void;\n}\n\nexport interface BlockFormat {\n id: string;\n label: string;\n shortcut?: string;\n isActive?: boolean;\n onSelect: () => void;\n}\n\nexport interface SelectionToolbarProps {\n selectedText: string;\n selection: SelectionRect;\n actions?: ToolbarAction[];\n /** Heading levels and list kinds, rendered behind a single trigger. */\n formats?: BlockFormat[];\n formatsLabel?: string;\n /** iOS draws its own callout above a selection; the host detects it. */\n preferBelow?: boolean;\n container?: HTMLElement | null;\n}\n\nconst FALLBACK = { width: 320, height: 40 };\n\n// Whether the width used to place the bar is the one it actually has. The\n// fallback is a guess, and placing on a guess puts the bar off-centre by\n// however far the guess is wrong — which is invisible while the bar happens to\n// be 320px wide and obvious as soon as a host adds another action to it.\ntype Measured = { width: number; height: number; real: boolean };\n\nexport function SelectionToolbar({\n selectedText,\n selection,\n actions = [],\n formats = [],\n formatsLabel = \"Format\",\n preferBelow = false,\n container,\n}: SelectionToolbarProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState<Measured>({ ...FALLBACK, real: false });\n const [formatsOpen, setFormatsOpen] = useState(false);\n const viewport = useViewport();\n\n // Measured after paint rather than assumed: the toolbar's width depends on\n // labels the host supplies, and guessing it puts the toolbar off-centre.\n //\n // Observed rather than recomputed from a dependency list: the width follows\n // the rendered labels, and a list naming the things that happen to change it\n // today misses the next one — a longer label, a font that loads late.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n const measure = () => {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0)\n setSize((prev) =>\n prev.real && prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height, real: true },\n );\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (!formatsOpen) return;\n const close = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setFormatsOpen(false);\n };\n document.addEventListener(\"mousedown\", close);\n return () => document.removeEventListener(\"mousedown\", close);\n }, [formatsOpen]);\n\n if (typeof document === \"undefined\") return null;\n\n const place = placeToolbar({\n selection,\n toolbar: size,\n viewport: { width: viewport.width, height: viewport.height },\n preferBelow,\n });\n\n return createPortal(\n <div\n ref={ref}\n role=\"toolbar\"\n className=\"lalt-toolbar\"\n // Custom properties rather than left/top directly: under the mobile\n // breakpoint the stylesheet docks the toolbar to the bottom of the\n // screen and ignores these, which inline left/top would override.\n //\n // The inset is what the docked bar sits on top of. `bottom: 0` means the\n // bottom of the layout viewport, which the on-screen keyboard covers\n // without shrinking — so the bar has to be lifted by the strip the\n // keyboard occupies to stay in sight.\n style={\n {\n \"--lalt-toolbar-left\": `${place.left}px`,\n \"--lalt-toolbar-top\": `${place.top}px`,\n \"--lalt-keyboard-inset\": `${viewport.bottomInset}px`,\n // Hidden for the one frame between mounting and being measured:\n // that first paint places the bar on the fallback width, and showing\n // it means showing the bar jump once the real width arrives.\n // Hidden rather than unmounted — it has to be in the document to\n // have a width at all.\n visibility: size.real ? undefined : \"hidden\",\n } as React.CSSProperties\n }\n >\n {actions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n className=\"lalt-toolbar__button\"\n // The label is what the narrow layout hides, so it has to stay\n // reachable to anyone not reading the icon.\n aria-label={action.icon ? action.label : undefined}\n data-has-icon={action.icon ? \"\" : undefined}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => action.onSelect(selectedText)}\n >\n {action.icon}\n <span className=\"lalt-toolbar__label\">{action.label}</span>\n </button>\n ))}\n\n {formats.length > 0 && (\n <div className=\"lalt-toolbar__formats\">\n <button\n type=\"button\"\n className=\"lalt-toolbar__button\"\n aria-haspopup=\"menu\"\n aria-expanded={formatsOpen}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => setFormatsOpen((open) => !open)}\n >\n {formatsLabel}\n </button>\n {formatsOpen && (\n <div role=\"menu\" className=\"lalt-toolbar__menu\">\n {formats.map((format) => (\n <button\n key={format.id}\n type=\"button\"\n role=\"menuitem\"\n className=\"lalt-toolbar__item\"\n aria-current={format.isActive}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n format.onSelect();\n setFormatsOpen(false);\n }}\n >\n <span>{format.label}</span>\n {format.shortcut && (\n <kbd className=\"lalt-toolbar__shortcut\">{format.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>,\n container ?? document.body,\n );\n}\n","import { useEffect, useState } from \"react\";\n\nimport { readViewport, type ViewportMetrics } from \"./viewport\";\n\nconst SERVER: ViewportMetrics = { width: 0, height: 0, offsetTop: 0, bottomInset: 0 };\n\nfunction read(): ViewportMetrics {\n if (typeof window === \"undefined\") return SERVER;\n return readViewport({\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n visual: window.visualViewport,\n });\n}\n\n/**\n * useViewport tracks the area a fixed element can occupy, keyboard included.\n *\n * The visual viewport's own events are the only ones a raised keyboard fires:\n * `window.resize` does not run on iOS when the keyboard opens, so a component\n * listening to it alone keeps the measurements it took before the keys\n * appeared.\n */\nexport function useViewport(): ViewportMetrics {\n const [metrics, setMetrics] = useState<ViewportMetrics>(read);\n\n useEffect(() => {\n const update = () => setMetrics(read());\n update();\n\n const visual = window.visualViewport;\n visual?.addEventListener(\"resize\", update);\n // iOS scrolls the visual viewport rather than the document to keep the\n // caret above the keyboard, and reports it here and nowhere else.\n visual?.addEventListener(\"scroll\", update);\n window.addEventListener(\"resize\", update);\n window.addEventListener(\"orientationchange\", update);\n\n return () => {\n visual?.removeEventListener(\"resize\", update);\n visual?.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n window.removeEventListener(\"orientationchange\", update);\n };\n }, []);\n\n return metrics;\n}\n","export interface VisualViewportLike {\n width: number;\n height: number;\n offsetTop: number;\n}\n\nexport interface ViewportMetrics {\n width: number;\n height: number;\n offsetTop: number;\n /** How much of the layout viewport's bottom is covered, the on-screen\n * keyboard included. Zero on a desktop browser and wherever the API is\n * missing. */\n bottomInset: number;\n}\n\nexport interface ReadViewportInput {\n innerWidth: number;\n innerHeight: number;\n visual?: VisualViewportLike | null;\n}\n\n/**\n * readViewport reports the area a fixed element can actually occupy.\n *\n * The distinction the on-screen keyboard turns on: `innerHeight` measures the\n * layout viewport, which the keyboard covers without shrinking, so an element\n * pinned to its bottom edge ends up underneath the keys. Only `visualViewport`\n * narrows, and the difference is the inset a docked toolbar has to clear.\n */\nexport function readViewport({\n innerWidth,\n innerHeight,\n visual,\n}: ReadViewportInput): ViewportMetrics {\n if (!visual) {\n return { width: innerWidth, height: innerHeight, offsetTop: 0, bottomInset: 0 };\n }\n\n // offsetTop is what iOS scrolls the visual viewport by; without it the\n // covered strip reads as smaller than it is once the page is scrolled under\n // a raised keyboard. Never negative: a viewport reported as taller than the\n // layout one — Android during a rotation, a browser mid-resize — would\n // otherwise push the toolbar down off the screen.\n const covered = innerHeight - visual.height - visual.offsetTop;\n\n return {\n width: visual.width,\n height: visual.height,\n offsetTop: visual.offsetTop,\n bottomInset: Math.max(0, Math.round(covered)),\n };\n}\n","import { useEffect, useState } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\n\nimport type { SelectionRect } from \"./placement\";\n\nexport interface SelectionInfo {\n text: string;\n rect: SelectionRect;\n}\n\n/**\n * useSelectionToolbar reports the current non-empty selection of an editor.\n *\n * Returns null whenever there is nothing to act on, so the caller renders the\n * toolbar only while it has a target.\n */\nexport function useSelectionToolbar(editor: Editor | null): SelectionInfo | null {\n const [selection, setSelection] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n if (!editor) return;\n\n const read = () => {\n const { state } = editor;\n const { from, to, empty } = state.selection;\n if (empty || !editor.isFocused) {\n setSelection(null);\n return;\n }\n\n const text = state.doc.textBetween(from, to, \" \").trim();\n if (text === \"\") {\n setSelection(null);\n return;\n }\n\n const start = editor.view.coordsAtPos(from);\n const end = editor.view.coordsAtPos(to);\n setSelection({\n text,\n rect: {\n x: (start.left + end.right) / 2,\n top: Math.min(start.top, end.top),\n bottom: Math.max(start.bottom, end.bottom),\n },\n });\n };\n\n read();\n editor.on(\"selectionUpdate\", read);\n editor.on(\"blur\", read);\n editor.on(\"focus\", read);\n return () => {\n editor.off(\"selectionUpdate\", read);\n editor.off(\"blur\", read);\n editor.off(\"focus\", read);\n };\n }, [editor]);\n\n return selection;\n}\n","import type { Editor } from \"@tiptap/react\";\n\n// Type-only: these commands are contributed by StarterKit's extensions, and\n// without the reference TypeScript does not know they exist on a chain. No\n// runtime import, so the host stays in charge of which extensions it mounts —\n// blockFormats simply returns entries whose commands no-op if one is missing.\nimport type {} from \"@tiptap/starter-kit\";\n\nimport type { BlockFormat } from \"./SelectionToolbar\";\n\nexport interface FormatLabels {\n paragraph: string;\n heading1: string;\n heading2: string;\n heading3: string;\n orderedList: string;\n bulletList: string;\n}\n\nexport const defaultFormatLabels: FormatLabels = {\n paragraph: \"Texte\",\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n heading3: \"Titre 3\",\n orderedList: \"Liste numérotée\",\n bulletList: \"Liste à puces\",\n};\n\n/**\n * blockFormats is the set a writer reaches for on a selected passage.\n *\n * Labels are passed in rather than translated here: the package carries no\n * i18n runtime, so the host names things in whatever language it speaks.\n */\nexport function blockFormats(\n editor: Editor | null,\n labels: FormatLabels = defaultFormatLabels,\n): BlockFormat[] {\n if (!editor) return [];\n\n // Shortcuts are the ones TipTap's own extensions bind, read from their\n // source rather than invented: heading binds Mod-Alt-<level>, the list\n // extensions bind Mod-Shift-7/8, and setParagraph binds nothing at all.\n // A label promising a shortcut that does nothing is worse than no label.\n const mod = isApple() ? \"⌘\" : \"Ctrl\";\n\n return [\n {\n id: \"paragraph\",\n label: labels.paragraph,\n isActive: editor.isActive(\"paragraph\"),\n onSelect: () => editor.chain().focus().setParagraph().run(),\n },\n ...([1, 2, 3] as const).map((level) => ({\n id: `heading${level}`,\n label: labels[`heading${level}` as keyof FormatLabels],\n shortcut: `${mod}+Alt+${level}`,\n isActive: editor.isActive(\"heading\", { level }),\n onSelect: () => editor.chain().focus().toggleHeading({ level }).run(),\n })),\n {\n id: \"orderedList\",\n label: labels.orderedList,\n shortcut: `${mod}+Shift+7`,\n isActive: editor.isActive(\"orderedList\"),\n onSelect: () => editor.chain().focus().toggleOrderedList().run(),\n },\n {\n id: \"bulletList\",\n label: labels.bulletList,\n shortcut: `${mod}+Shift+8`,\n isActive: editor.isActive(\"bulletList\"),\n onSelect: () => editor.chain().focus().toggleBulletList().run(),\n },\n ];\n}\n\nfunction isApple(): boolean {\n if (typeof navigator === \"undefined\") return false;\n return /Mac|iPad|iPhone|iPod/.test(navigator.platform || navigator.userAgent);\n}\n","import { useViewport } from \"./useViewport\";\n\nexport type SaveState = \"idle\" | \"saving\" | \"saved\" | \"dirty\";\n\nexport interface EditorScreenProps {\n /** The writing surface. Mounted by the host, which owns the TipTap instance\n * and whichever extensions it needs. */\n children: React.ReactNode;\n title?: string;\n onTitleChange?: (title: string) => void;\n titlePlaceholder?: string;\n titleLabel?: string;\n saveState?: SaveState;\n saveLabels?: SaveLabels;\n /** Drawn at the far left of the bar; a back control belongs here. */\n lead?: React.ReactNode;\n /** Drawn at the far right. Keep it to what a thumb can reach on a phone —\n * one primary control and an overflow, not a row of five. */\n actions?: React.ReactNode;\n /** Sits between the save dot and the actions: a character count, a badge. */\n status?: React.ReactNode;\n /** Between the title and the writing surface — a dictation control, a hint. */\n aside?: React.ReactNode;\n /** Below the writing surface, inside the scrolling column. */\n footer?: React.ReactNode;\n /** Above the writing surface and below the bar, pinned while the page\n * scrolls: tabs over variants of one document belong here. */\n subnav?: React.ReactNode;\n /** Fills its container instead of the viewport, for a host whose shell\n * already owns the page height. */\n fill?: boolean;\n className?: string;\n}\n\nexport interface SaveLabels {\n saving: string;\n saved: string;\n dirty: string;\n}\n\nexport const defaultSaveLabels: SaveLabels = {\n saving: \"Enregistrement en cours\",\n saved: \"Enregistré\",\n dirty: \"Modifications non enregistrées\",\n};\n\n/**\n * EditorScreen is the page a writing surface sits in: a command bar, a title,\n * one column of prose, and slots for whatever the host puts around it.\n *\n * It renders and measures; it never fetches, saves or routes. `saveState` is\n * displayed, not computed — the host owns the debounce and the request, so the\n * same screen serves an autosaving draft and a form with an explicit button.\n *\n * Commands sit at the top rather than in a bar at the bottom. Below the fold\n * they are gone the moment the document runs past one screen, and pinned to\n * the bottom they eat a sixth of what the on-screen keyboard leaves.\n */\nexport function EditorScreen({\n children,\n title,\n onTitleChange,\n titlePlaceholder = \"Titre\",\n titleLabel = \"Titre\",\n saveState,\n saveLabels = defaultSaveLabels,\n lead,\n actions,\n status,\n aside,\n footer,\n subnav,\n fill = false,\n className,\n}: EditorScreenProps) {\n const { bottomInset } = useViewport();\n const withTitle = onTitleChange !== undefined;\n\n return (\n <div\n className={[\n \"lalt-screen\",\n fill ? \"lalt-screen--fill\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The keyboard covers the layout viewport without shrinking it, so the\n // column's last lines sit under the keys unless the strip is padded out.\n style={{ \"--lalt-keyboard\": `${bottomInset}px` } as React.CSSProperties}\n >\n <header className=\"lalt-screen__bar\">\n {lead}\n {saveState && (\n <SaveDot state={saveState} labels={saveLabels} />\n )}\n {status && <div className=\"lalt-screen__status\">{status}</div>}\n {actions && <div className=\"lalt-screen__actions\">{actions}</div>}\n </header>\n\n {subnav && <div className=\"lalt-screen__subnav\">{subnav}</div>}\n\n <div className=\"lalt-screen__scroll\">\n <div className=\"lalt-screen__column\">\n {withTitle && (\n <input\n className=\"lalt-screen__title\"\n value={title ?? \"\"}\n onChange={(event) => onTitleChange(event.target.value)}\n placeholder={titlePlaceholder}\n aria-label={titleLabel}\n />\n )}\n {aside && <div className=\"lalt-screen__aside\">{aside}</div>}\n {children}\n {footer && <div className=\"lalt-screen__footer\">{footer}</div>}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * A dot rather than a word: the state is worth a glance, never a read, and a\n * label that appears and vanishes leaves the author wondering what it meant\n * once it is gone. Screen readers get the sentence instead.\n */\nfunction SaveDot({ state, labels }: { state: SaveState; labels: SaveLabels }) {\n if (state === \"idle\") return <span className=\"lalt-screen__save\" />;\n\n const label =\n state === \"saving\" ? labels.saving : state === \"dirty\" ? labels.dirty : labels.saved;\n\n return (\n <span className=\"lalt-screen__save\" role=\"status\" aria-live=\"polite\">\n <span className={`lalt-screen__dot lalt-screen__dot--${state}`} aria-hidden />\n <span className=\"lalt-screen__sr\">{label}</span>\n </span>\n );\n}\n","import type { Editor } from \"@tiptap/react\";\nimport type { Range } from \"@tiptap/core\";\n\n// Type-only, as in formats.ts: StarterKit contributes these commands to a\n// chain, and without the reference TypeScript does not know they exist. The\n// host stays in charge of which extensions it actually mounts.\nimport type {} from \"@tiptap/starter-kit\";\n\n/**\n * SlashItem is one entry of the `/` menu.\n *\n * icon is a node rather than a component type: the package carries no icon\n * library, so the host renders whichever one it already ships.\n */\nexport interface SlashItem {\n id: string;\n title: string;\n description?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n command: (args: { editor: Editor; range: Range }) => void;\n}\n\nexport interface SlashLabels {\n heading1: string;\n heading2: string;\n bulletList: string;\n orderedList: string;\n blockquote: string;\n codeBlock: string;\n horizontalRule: string;\n}\n\nexport const defaultSlashLabels: SlashLabels = {\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n bulletList: \"Liste à puces\",\n orderedList: \"Liste numérotée\",\n blockquote: \"Citation\",\n codeBlock: \"Bloc de code\",\n horizontalRule: \"Séparateur\",\n};\n\nexport interface SlashDescriptions {\n heading1?: string;\n heading2?: string;\n bulletList?: string;\n orderedList?: string;\n blockquote?: string;\n codeBlock?: string;\n horizontalRule?: string;\n}\n\nexport interface DefaultSlashItemsOptions {\n labels?: SlashLabels;\n descriptions?: SlashDescriptions;\n icons?: Partial<Record<DefaultSlashId, React.ReactNode>>;\n}\n\nexport type DefaultSlashId =\n | \"heading1\"\n | \"heading2\"\n | \"bulletList\"\n | \"orderedList\"\n | \"blockquote\"\n | \"codeBlock\"\n | \"horizontalRule\";\n\n/**\n * defaultSlashItems is the block set every writing surface shares.\n *\n * Returned as data rather than rendered behind flags: a host adds, removes or\n * reorders entries by composing the array, so a surface that needs one more\n * command does not push another boolean into this signature.\n */\nexport function defaultSlashItems({\n labels = defaultSlashLabels,\n descriptions = {},\n icons = {},\n}: DefaultSlashItemsOptions = {}): SlashItem[] {\n const item = (\n id: DefaultSlashId,\n shortcut: string,\n run: (editor: Editor) => void,\n ): SlashItem => ({\n id,\n title: labels[id],\n description: descriptions[id],\n shortcut,\n icon: icons[id],\n command: ({ editor, range }) => {\n editor.chain().focus().deleteRange(range).run();\n run(editor);\n },\n });\n\n return [\n item(\"heading1\", \"#\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 1 }).run(),\n ),\n item(\"heading2\", \"##\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 2 }).run(),\n ),\n item(\"bulletList\", \"-\", (editor) =>\n editor.chain().focus().toggleBulletList().run(),\n ),\n item(\"orderedList\", \"1.\", (editor) =>\n editor.chain().focus().toggleOrderedList().run(),\n ),\n item(\"blockquote\", \">\", (editor) =>\n editor.chain().focus().toggleBlockquote().run(),\n ),\n item(\"codeBlock\", \"```\", (editor) =>\n editor.chain().focus().toggleCodeBlock().run(),\n ),\n item(\"horizontalRule\", \"---\", (editor) =>\n editor.chain().focus().setHorizontalRule().run(),\n ),\n ];\n}\n\n/** filterSlashItems matches the query against titles the way the menu does. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase();\n if (q === \"\") return items;\n return items.filter((item) => item.title.toLowerCase().includes(q));\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { SlashItem } from \"./slash\";\n\nexport interface SlashMenuHandle {\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n}\n\nexport interface SlashMenuProps {\n items: SlashItem[];\n command: (item: SlashItem) => void;\n emptyLabel?: string;\n}\n\nexport const SlashMenu = forwardRef<SlashMenuHandle, SlashMenuProps>(\n function SlashMenu({ items, command, emptyLabel = \"Aucune commande\" }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // The suggestion plugin rebuilds these props on every keystroke, so the\n // imperative handle reads refs rather than a captured render closure —\n // otherwise Enter runs the command the menu showed one keystroke ago.\n const itemsRef = useRef(items);\n itemsRef.current = items;\n const commandRef = useRef(command);\n commandRef.current = command;\n const selectedRef = useRef(selectedIndex);\n selectedRef.current = selectedIndex;\n\n const ids = items.map((item) => item.id).join(\" \");\n useEffect(() => {\n setSelectedIndex(0);\n selectedRef.current = 0;\n }, [ids]);\n\n const selectItem = useCallback((index: number) => {\n const item = itemsRef.current[index];\n if (item) commandRef.current(item);\n }, []);\n\n const move = useCallback((delta: number) => {\n const count = itemsRef.current.length;\n if (count === 0) return;\n const next = (selectedRef.current + delta + count) % count;\n selectedRef.current = next;\n setSelectedIndex(next);\n }, []);\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n if (event.key === \"ArrowUp\") {\n move(-1);\n return true;\n }\n if (event.key === \"ArrowDown\") {\n move(1);\n return true;\n }\n if (event.key === \"Enter\" || event.key === \"Tab\") {\n if (itemsRef.current.length === 0) return false;\n selectItem(selectedRef.current);\n return true;\n }\n return false;\n },\n }));\n\n if (items.length === 0) {\n return <div className=\"lalt-slash lalt-slash--empty\">{emptyLabel}</div>;\n }\n\n return (\n <div className=\"lalt-slash\" role=\"listbox\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={\n index === selectedIndex\n ? \"lalt-slash__item lalt-slash__item--active\"\n : \"lalt-slash__item\"\n }\n // Losing editor focus tears the suggestion plugin down before a\n // click lands, so the item would never run its command.\n onMouseDown={(event) => {\n event.preventDefault();\n selectItem(index);\n }}\n onMouseEnter={() => {\n selectedRef.current = index;\n setSelectedIndex(index);\n }}\n >\n {item.icon && (\n <span className=\"lalt-slash__icon\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"lalt-slash__text\">\n <span className=\"lalt-slash__title\">{item.title}</span>\n {item.description && (\n <span className=\"lalt-slash__description\">{item.description}</span>\n )}\n </span>\n {item.shortcut && (\n <kbd className=\"lalt-slash__shortcut\">{item.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n );\n },\n);\n","import { Extension } from \"@tiptap/core\";\nimport { ReactRenderer } from \"@tiptap/react\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport Suggestion, { type SuggestionOptions, type SuggestionProps } from \"@tiptap/suggestion\";\n\nimport { SlashMenu, type SlashMenuHandle, type SlashMenuProps } from \"./SlashMenu\";\nimport { filterSlashItems, type SlashItem } from \"./slash\";\n\nexport interface SlashCommandsOptions {\n /** Read on every keystroke, so a host can swap the set without remounting\n * TipTap — rebuilding the extensions array wipes the text being typed. */\n items: () => SlashItem[];\n char: string;\n emptyLabel?: string;\n /** Distinguishes concurrently mounted editors: two surfaces sharing one key\n * would answer each other's suggestions. */\n pluginKey: PluginKey;\n}\n\nconst OFFSET = 6;\n\nexport const SlashCommands = Extension.create<SlashCommandsOptions>({\n name: \"laSlashCommands\",\n\n addOptions() {\n return {\n items: () => [],\n char: \"/\",\n pluginKey: new PluginKey(\"laSlashCommands\"),\n };\n },\n\n addProseMirrorPlugins() {\n const { items, char, emptyLabel, pluginKey } = this.options;\n\n const suggestion: Omit<SuggestionOptions<SlashItem>, \"editor\"> = {\n char,\n pluginKey,\n startOfLine: false,\n items: ({ query }) => filterSlashItems(items(), query),\n render: () => {\n let component: ReactRenderer<SlashMenuHandle, SlashMenuProps> | null = null;\n let container: HTMLDivElement | null = null;\n\n const place = (clientRect: SuggestionProps<SlashItem>[\"clientRect\"]) => {\n if (!container || !clientRect) return;\n const rect = clientRect();\n if (!rect) return;\n container.style.top = `${rect.bottom + OFFSET}px`;\n container.style.left = `${rect.left}px`;\n };\n\n const close = () => {\n container?.remove();\n container = null;\n component?.destroy();\n component = null;\n };\n\n return {\n onStart: (props) => {\n component = new ReactRenderer(SlashMenu, {\n props: { ...props, emptyLabel },\n editor: props.editor,\n });\n container = document.createElement(\"div\");\n container.className = \"lalt-slash-anchor\";\n container.appendChild(component.element);\n document.body.appendChild(container);\n place(props.clientRect);\n },\n onUpdate: (props) => {\n component?.updateProps({ ...props, emptyLabel });\n place(props.clientRect);\n },\n onKeyDown: (props) => {\n if (props.event.key === \"Escape\") {\n close();\n return true;\n }\n return component?.ref?.onKeyDown(props) ?? false;\n },\n onExit: close,\n };\n },\n };\n\n return [\n Suggestion({\n editor: this.editor,\n ...suggestion,\n command: ({ editor, range, props }) => props.command({ editor, range }),\n }),\n ];\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\n\nexport interface InlineSuggestion {\n id: string;\n /** Document positions of the passage this replaces. */\n from: number;\n to: number;\n after: string;\n}\n\nexport interface SuggestionActions {\n onAccept: (id: string) => void;\n onReject: (id: string) => void;\n acceptLabel: string;\n rejectLabel: string;\n}\n\nexport const inlineSuggestionsKey = new PluginKey<DecorationSet>(\"lalt-inline-suggestions\");\n\n/**\n * InlineSuggestions renders proposed edits where they belong: over the passage\n * they replace, with the new text right after it.\n *\n * Decorations, never document changes. The editor's content stays exactly what\n * the author wrote until they accept, so the draft is saveable mid-review and\n * a rejected suggestion leaves nothing behind — which a document-level insert\n * could not promise.\n */\nexport const InlineSuggestions = Extension.create<{ actions: SuggestionActions | null }>({\n name: \"lalt-inline-suggestions\",\n\n addOptions() {\n return { actions: null };\n },\n\n addProseMirrorPlugins() {\n const options = this.options;\n\n return [\n new Plugin<DecorationSet>({\n key: inlineSuggestionsKey,\n\n state: {\n init: () => DecorationSet.empty,\n apply(tr, old) {\n const set = tr.getMeta(inlineSuggestionsKey) as InlineSuggestion[] | undefined;\n if (set) return build(tr.doc, set, options.actions);\n // Positions follow the document as the author keeps typing around\n // a pending suggestion.\n return old.map(tr.mapping, tr.doc);\n },\n },\n\n props: {\n decorations(state) {\n return inlineSuggestionsKey.getState(state) ?? DecorationSet.empty;\n },\n },\n }),\n ];\n },\n});\n\nfunction build(\n doc: ProseMirrorNode,\n suggestions: InlineSuggestion[],\n actions: SuggestionActions | null,\n): DecorationSet {\n const decorations: Decoration[] = [];\n\n for (const s of suggestions) {\n if (s.from < 0 || s.to > doc.content.size || s.from >= s.to) continue;\n\n decorations.push(\n Decoration.inline(s.from, s.to, { class: \"lalt-suggestion__old\" }),\n Decoration.widget(s.to, () => renderProposal(s, actions), {\n // The widget belongs to the suggestion that precedes it, so typing at\n // the boundary does not push it around.\n side: 1,\n ignoreSelection: true,\n }),\n );\n }\n return DecorationSet.create(doc, decorations);\n}\n\nfunction renderProposal(s: InlineSuggestion, actions: SuggestionActions | null): HTMLElement {\n const wrap = document.createElement(\"span\");\n wrap.className = \"lalt-suggestion\";\n wrap.setAttribute(\"contenteditable\", \"false\");\n\n const text = document.createElement(\"span\");\n text.className = \"lalt-suggestion__new\";\n text.textContent = s.after;\n wrap.appendChild(text);\n\n if (actions) {\n wrap.appendChild(button(\"reject\", actions.rejectLabel, \"✕\", () => actions.onReject(s.id)));\n wrap.appendChild(button(\"accept\", actions.acceptLabel, \"✓\", () => actions.onAccept(s.id)));\n }\n return wrap;\n}\n\nfunction button(kind: string, label: string, glyph: string, onClick: () => void): HTMLElement {\n const el = document.createElement(\"button\");\n el.type = \"button\";\n el.className = `lalt-suggestion__action lalt-suggestion__action--${kind}`;\n el.setAttribute(\"aria-label\", label);\n el.textContent = glyph;\n // mousedown would move the selection into the widget before the click lands.\n el.addEventListener(\"mousedown\", (e) => e.preventDefault());\n el.addEventListener(\"click\", (e) => {\n e.preventDefault();\n onClick();\n });\n return el;\n}\n","export interface TextRange {\n from: number;\n to: number;\n}\n\nexport interface RevisionPart {\n before: string;\n after: string;\n}\n\n// Letters, digits, and the marks that belong inside a French word.\nconst WORD = /[\\p{L}\\p{N}'’-]/u;\n\n/**\n * expandToWords grows a range out to whole words.\n *\n * A selection dragged across a line lands mid-word more often than not, and a\n * model handed \"ns de production\" answers for a fragment — which then reads as\n * a truncated diff the author has to decode.\n */\nexport function expandToWords(text: string, from: number, to: number): TextRange {\n if (from >= to) return { from, to };\n\n let start = from;\n while (start > 0 && WORD.test(text[start - 1]) && WORD.test(text[start])) start--;\n\n let end = to;\n while (end < text.length && WORD.test(text[end]) && WORD.test(text[end - 1])) end++;\n\n return { from: start, to: end };\n}\n\n/**\n * splitRevision turns one model answer into the decisions it really contains.\n *\n * A three-paragraph rewrite accepted or rejected as a whole forces the author\n * to keep a weak paragraph for a strong one, so each paragraph becomes its own\n * decision — but only when both sides line up. When the model merged or split\n * paragraphs, position no longer maps them, and pairing by index would show a\n * diff that never existed.\n */\nexport function splitRevision(before: string, after: string): RevisionPart[] {\n if (before.trim() === after.trim()) return [];\n\n const olds = paragraphs(before);\n const news = paragraphs(after);\n\n if (olds.length !== news.length || olds.length === 0) {\n return [{ before: before.trim(), after: after.trim() }];\n }\n\n const parts: RevisionPart[] = [];\n for (let i = 0; i < olds.length; i++) {\n if (olds[i] !== news[i]) parts.push({ before: olds[i], after: news[i] });\n }\n return parts;\n}\n\nfunction paragraphs(s: string): string[] {\n return s\n .split(/\\n\\s*\\n/)\n .map((p) => p.trim())\n .filter((p) => p !== \"\");\n}\n","import type { RichDoc, RichNode } from \"./document\";\n\ninterface Mark {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * docToMarkdown renders a TipTap document as the markdown a blog accepts.\n *\n * Written here rather than taken from a library: the editor only ever produces\n * the handful of nodes StarterKit gives it, and a general markdown serializer\n * brings a dependency and its escaping rules for output a human is about to\n * paste into their own site.\n */\nexport function docToMarkdown(doc: RichDoc): string {\n return blocks(doc.content ?? [])\n .filter((b) => b !== \"\")\n .join(\"\\n\\n\");\n}\n\nfunction blocks(nodes: RichNode[]): string[] {\n return nodes.map(block);\n}\n\nfunction block(node: RichNode): string {\n switch (node.type) {\n case \"heading\": {\n const level = Number(node.attrs?.level ?? 2);\n return `${\"#\".repeat(Math.min(6, Math.max(1, level)))} ${inline(node.content)}`;\n }\n case \"blockquote\":\n return blocks(node.content ?? [])\n .filter((b) => b !== \"\")\n .map((b) =>\n b\n .split(\"\\n\")\n .map((line) => `> ${line}`)\n .join(\"\\n\"),\n )\n .join(\"\\n>\\n\");\n case \"codeBlock\": {\n const language = String(node.attrs?.language ?? \"\");\n return `\\`\\`\\`${language}\\n${inline(node.content)}\\n\\`\\`\\``;\n }\n case \"bulletList\":\n return listItems(node).map((item) => `- ${item}`).join(\"\\n\");\n case \"orderedList\":\n return listItems(node)\n .map((item, i) => `${i + 1}. ${item}`)\n .join(\"\\n\");\n case \"horizontalRule\":\n return \"---\";\n default:\n return inline(node.content);\n }\n}\n\nfunction listItems(list: RichNode): string[] {\n return (list.content ?? []).map((item) =>\n blocks(item.content ?? [])\n .filter((b) => b !== \"\")\n // Continuation lines are indented so the item stays one item.\n .join(\"\\n\\n\")\n .split(\"\\n\")\n .join(\"\\n \"),\n );\n}\n\nfunction inline(nodes: RichNode[] | undefined): string {\n return (nodes ?? []).map(text).join(\"\");\n}\n\nfunction text(node: RichNode): string {\n if (node.type === \"hardBreak\") return \"\\n\";\n if (typeof node.text !== \"string\") return inline(node.content);\n\n let out = node.text;\n // Innermost first, so bold inside a link wraps the text and not the URL.\n for (const mark of ((node.marks as Mark[] | undefined) ?? []).slice().reverse()) {\n switch (mark.type) {\n case \"bold\":\n out = `**${out}**`;\n break;\n case \"italic\":\n out = `*${out}*`;\n break;\n case \"strike\":\n out = `~~${out}~~`;\n break;\n case \"code\":\n out = `\\`${out}\\``;\n break;\n case \"link\":\n out = `[${out}](${String(mark.attrs?.href ?? \"\")})`;\n break;\n }\n }\n return out;\n}\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","useEffect","useState","read","text","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","SlashMenu","Extension","PluginKey","text","text"]}
1
+ {"version":3,"sources":["../src/placement.ts","../src/SelectionToolbar.tsx","../src/useViewport.ts","../src/viewport.ts","../src/useSelectionToolbar.ts","../src/formats.ts","../src/EditorScreen.tsx","../src/slash.ts","../src/SlashMenu.tsx","../src/SlashCommands.ts","../src/InlineSuggestions.ts","../src/passage.ts","../src/markdown.ts"],"sourcesContent":["export interface SelectionRect {\n /** Horizontal centre of the selection. */\n x: number;\n top: number;\n bottom: number;\n}\n\nexport interface Size {\n width: number;\n height: number;\n}\n\nexport interface PlaceToolbarInput {\n selection: SelectionRect;\n toolbar: Size;\n viewport: Size;\n /** iOS renders its own callout above a selection, which covers a toolbar\n * placed there, so the caller asks for below on that platform. */\n preferBelow?: boolean;\n}\n\nexport interface ToolbarPlacement {\n left: number;\n top: number;\n below: boolean;\n}\n\nconst GAP = 8;\n\n/**\n * placeToolbar positions a floating toolbar against a selection.\n *\n * Kept apart from the component because it is the part that is wrong in subtle\n * ways — off-screen at the edges, hidden under a system callout — and the part\n * a test can pin down without a browser.\n */\nexport function placeToolbar({\n selection,\n toolbar,\n viewport,\n preferBelow = false,\n}: PlaceToolbarInput): ToolbarPlacement {\n const fitsAbove = selection.top - GAP - toolbar.height >= 0;\n const fitsBelow = selection.bottom + GAP + toolbar.height <= viewport.height;\n\n const below = preferBelow ? fitsBelow || !fitsAbove : !fitsAbove && fitsBelow;\n\n const top = below\n ? selection.bottom + GAP\n : Math.max(0, selection.top - GAP - toolbar.height);\n\n const centred = selection.x - toolbar.width / 2;\n const left = Math.max(0, Math.min(centred, viewport.width - toolbar.width));\n\n return { left, top, below };\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\n\nimport { placeToolbar, type SelectionRect } from \"./placement\";\nimport { useViewport } from \"./useViewport\";\n\nexport interface ToolbarAction {\n id: string;\n label: string;\n icon?: React.ReactNode;\n onSelect: (selectedText: string) => void;\n}\n\nexport interface BlockFormat {\n id: string;\n label: string;\n shortcut?: string;\n isActive?: boolean;\n onSelect: () => void;\n}\n\nexport interface SelectionToolbarProps {\n selectedText: string;\n selection: SelectionRect;\n actions?: ToolbarAction[];\n /** Heading levels and list kinds, rendered behind a single trigger. */\n formats?: BlockFormat[];\n formatsLabel?: string;\n /** iOS draws its own callout above a selection; the host detects it. */\n preferBelow?: boolean;\n container?: HTMLElement | null;\n}\n\nconst FALLBACK = { width: 320, height: 40 };\n\n// Whether the width used to place the bar is the one it actually has. The\n// fallback is a guess, and placing on a guess puts the bar off-centre by\n// however far the guess is wrong — which is invisible while the bar happens to\n// be 320px wide and obvious as soon as a host adds another action to it.\ntype Measured = { width: number; height: number; real: boolean };\n\nexport function SelectionToolbar({\n selectedText,\n selection,\n actions = [],\n formats = [],\n formatsLabel = \"Format\",\n preferBelow = false,\n container,\n}: SelectionToolbarProps) {\n const ref = useRef<HTMLDivElement>(null);\n const [size, setSize] = useState<Measured>({ ...FALLBACK, real: false });\n const [formatsOpen, setFormatsOpen] = useState(false);\n const viewport = useViewport();\n\n // Measured after paint rather than assumed: the toolbar's width depends on\n // labels the host supplies, and guessing it puts the toolbar off-centre.\n //\n // Observed rather than recomputed from a dependency list: the width follows\n // the rendered labels, and a list naming the things that happen to change it\n // today misses the next one — a longer label, a font that loads late.\n useLayoutEffect(() => {\n const el = ref.current;\n if (!el) return;\n const measure = () => {\n const rect = el.getBoundingClientRect();\n if (rect.width > 0)\n setSize((prev) =>\n prev.real && prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height, real: true },\n );\n };\n measure();\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n useEffect(() => {\n if (!formatsOpen) return;\n const close = (e: MouseEvent) => {\n if (ref.current && !ref.current.contains(e.target as Node)) setFormatsOpen(false);\n };\n document.addEventListener(\"mousedown\", close);\n return () => document.removeEventListener(\"mousedown\", close);\n }, [formatsOpen]);\n\n if (typeof document === \"undefined\") return null;\n\n const place = placeToolbar({\n selection,\n toolbar: size,\n viewport: { width: viewport.width, height: viewport.height },\n preferBelow,\n });\n\n return createPortal(\n <div\n ref={ref}\n role=\"toolbar\"\n className=\"lalt-toolbar\"\n // Custom properties rather than left/top directly: under the mobile\n // breakpoint the stylesheet docks the toolbar to the bottom of the\n // screen and ignores these, which inline left/top would override.\n //\n // The inset is what the docked bar sits on top of. `bottom: 0` means the\n // bottom of the layout viewport, which the on-screen keyboard covers\n // without shrinking — so the bar has to be lifted by the strip the\n // keyboard occupies to stay in sight.\n style={\n {\n \"--lalt-toolbar-left\": `${place.left}px`,\n \"--lalt-toolbar-top\": `${place.top}px`,\n \"--lalt-keyboard-inset\": `${viewport.bottomInset}px`,\n // Hidden for the one frame between mounting and being measured:\n // that first paint places the bar on the fallback width, and showing\n // it means showing the bar jump once the real width arrives.\n // Hidden rather than unmounted — it has to be in the document to\n // have a width at all.\n visibility: size.real ? undefined : \"hidden\",\n } as React.CSSProperties\n }\n >\n {actions.map((action) => (\n <button\n key={action.id}\n type=\"button\"\n className=\"lalt-toolbar__button\"\n // The label is what the narrow layout hides, so it has to stay\n // reachable to anyone not reading the icon.\n aria-label={action.icon ? action.label : undefined}\n data-has-icon={action.icon ? \"\" : undefined}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => action.onSelect(selectedText)}\n >\n {action.icon}\n <span className=\"lalt-toolbar__label\">{action.label}</span>\n </button>\n ))}\n\n {formats.length > 0 && (\n <div className=\"lalt-toolbar__formats\">\n <button\n type=\"button\"\n className=\"lalt-toolbar__button\"\n aria-haspopup=\"menu\"\n aria-expanded={formatsOpen}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => setFormatsOpen((open) => !open)}\n >\n {formatsLabel}\n </button>\n {formatsOpen && (\n <div role=\"menu\" className=\"lalt-toolbar__menu\">\n {formats.map((format) => (\n <button\n key={format.id}\n type=\"button\"\n role=\"menuitem\"\n className=\"lalt-toolbar__item\"\n aria-current={format.isActive}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n format.onSelect();\n setFormatsOpen(false);\n }}\n >\n <span>{format.label}</span>\n {format.shortcut && (\n <kbd className=\"lalt-toolbar__shortcut\">{format.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n )}\n </div>,\n container ?? document.body,\n );\n}\n","import { useEffect, useState } from \"react\";\n\nimport { readViewport, type ViewportMetrics } from \"./viewport\";\n\nconst SERVER: ViewportMetrics = { width: 0, height: 0, offsetTop: 0, bottomInset: 0 };\n\nfunction read(): ViewportMetrics {\n if (typeof window === \"undefined\") return SERVER;\n return readViewport({\n innerWidth: window.innerWidth,\n innerHeight: window.innerHeight,\n visual: window.visualViewport,\n });\n}\n\n/**\n * useViewport tracks the area a fixed element can occupy, keyboard included.\n *\n * The visual viewport's own events are the only ones a raised keyboard fires:\n * `window.resize` does not run on iOS when the keyboard opens, so a component\n * listening to it alone keeps the measurements it took before the keys\n * appeared.\n */\nexport function useViewport(): ViewportMetrics {\n const [metrics, setMetrics] = useState<ViewportMetrics>(read);\n\n useEffect(() => {\n const update = () => setMetrics(read());\n update();\n\n const visual = window.visualViewport;\n visual?.addEventListener(\"resize\", update);\n // iOS scrolls the visual viewport rather than the document to keep the\n // caret above the keyboard, and reports it here and nowhere else.\n visual?.addEventListener(\"scroll\", update);\n window.addEventListener(\"resize\", update);\n window.addEventListener(\"orientationchange\", update);\n\n return () => {\n visual?.removeEventListener(\"resize\", update);\n visual?.removeEventListener(\"scroll\", update);\n window.removeEventListener(\"resize\", update);\n window.removeEventListener(\"orientationchange\", update);\n };\n }, []);\n\n return metrics;\n}\n","export interface VisualViewportLike {\n width: number;\n height: number;\n offsetTop: number;\n}\n\nexport interface ViewportMetrics {\n width: number;\n height: number;\n offsetTop: number;\n /** How much of the layout viewport's bottom is covered, the on-screen\n * keyboard included. Zero on a desktop browser and wherever the API is\n * missing. */\n bottomInset: number;\n}\n\nexport interface ReadViewportInput {\n innerWidth: number;\n innerHeight: number;\n visual?: VisualViewportLike | null;\n}\n\n/**\n * readViewport reports the area a fixed element can actually occupy.\n *\n * The distinction the on-screen keyboard turns on: `innerHeight` measures the\n * layout viewport, which the keyboard covers without shrinking, so an element\n * pinned to its bottom edge ends up underneath the keys. Only `visualViewport`\n * narrows, and the difference is the inset a docked toolbar has to clear.\n */\nexport function readViewport({\n innerWidth,\n innerHeight,\n visual,\n}: ReadViewportInput): ViewportMetrics {\n if (!visual) {\n return { width: innerWidth, height: innerHeight, offsetTop: 0, bottomInset: 0 };\n }\n\n // offsetTop is what iOS scrolls the visual viewport by; without it the\n // covered strip reads as smaller than it is once the page is scrolled under\n // a raised keyboard. Never negative: a viewport reported as taller than the\n // layout one — Android during a rotation, a browser mid-resize — would\n // otherwise push the toolbar down off the screen.\n const covered = innerHeight - visual.height - visual.offsetTop;\n\n return {\n width: visual.width,\n height: visual.height,\n offsetTop: visual.offsetTop,\n bottomInset: Math.max(0, Math.round(covered)),\n };\n}\n","import { useEffect, useState } from \"react\";\nimport type { Editor } from \"@tiptap/react\";\n\nimport type { SelectionRect } from \"./placement\";\n\nexport interface SelectionInfo {\n text: string;\n rect: SelectionRect;\n}\n\n// Whether the on-screen keyboard is what stands between a selection and the\n// toolbar acting on it. Touch without a fine pointer: a laptop with a\n// touchscreen has a real keyboard and must keep its focus.\nfunction keyboardIsInTheWay(): boolean {\n if (typeof window === \"undefined\" || !window.matchMedia) return false;\n return window.matchMedia(\"(hover: none) and (pointer: coarse)\").matches;\n}\n\n/**\n * useSelectionToolbar reports the current non-empty selection of an editor.\n *\n * Returns null whenever there is nothing to act on, so the caller renders the\n * toolbar only while it has a target.\n *\n * On a touch device the keyboard is dismissed as soon as a range is selected.\n * There is nothing to type while choosing a format, and the strip above the\n * keyboard belongs to the system — a toolbar left to fight for it is drawn\n * underneath. Out of the way, the toolbar sits by the text it acts on.\n */\nexport function useSelectionToolbar(editor: Editor | null): SelectionInfo | null {\n const [selection, setSelection] = useState<SelectionInfo | null>(null);\n\n useEffect(() => {\n if (!editor) return;\n\n const read = () => {\n const { state } = editor;\n const { from, to, empty } = state.selection;\n // Focus is not required while a range is held. On a phone the toolbar\n // only becomes usable once the keyboard is out of the way, and putting\n // it away blurs the editor — reading focus here would take the toolbar\n // down with the keyboard, which is the thing the author reached for.\n if (empty) {\n setSelection(null);\n return;\n }\n\n const text = state.doc.textBetween(from, to, \" \").trim();\n if (text === \"\") {\n setSelection(null);\n return;\n }\n\n // Blurred once per selection, not on every update: re-blurring while\n // the author drags a handle would fight iOS for the selection.\n if (keyboardIsInTheWay() && editor.isFocused) {\n (editor.view.dom as HTMLElement).blur();\n }\n\n const start = editor.view.coordsAtPos(from);\n const end = editor.view.coordsAtPos(to);\n setSelection({\n text,\n rect: {\n x: (start.left + end.right) / 2,\n top: Math.min(start.top, end.top),\n bottom: Math.max(start.bottom, end.bottom),\n },\n });\n };\n\n read();\n editor.on(\"selectionUpdate\", read);\n editor.on(\"blur\", read);\n editor.on(\"focus\", read);\n return () => {\n editor.off(\"selectionUpdate\", read);\n editor.off(\"blur\", read);\n editor.off(\"focus\", read);\n };\n }, [editor]);\n\n return selection;\n}\n","import type { Editor } from \"@tiptap/react\";\n\n// Type-only: these commands are contributed by StarterKit's extensions, and\n// without the reference TypeScript does not know they exist on a chain. No\n// runtime import, so the host stays in charge of which extensions it mounts —\n// blockFormats simply returns entries whose commands no-op if one is missing.\nimport type {} from \"@tiptap/starter-kit\";\n\nimport type { BlockFormat } from \"./SelectionToolbar\";\n\nexport interface FormatLabels {\n paragraph: string;\n heading1: string;\n heading2: string;\n heading3: string;\n orderedList: string;\n bulletList: string;\n}\n\nexport const defaultFormatLabels: FormatLabels = {\n paragraph: \"Texte\",\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n heading3: \"Titre 3\",\n orderedList: \"Liste numérotée\",\n bulletList: \"Liste à puces\",\n};\n\n/**\n * blockFormats is the set a writer reaches for on a selected passage.\n *\n * Labels are passed in rather than translated here: the package carries no\n * i18n runtime, so the host names things in whatever language it speaks.\n */\nexport function blockFormats(\n editor: Editor | null,\n labels: FormatLabels = defaultFormatLabels,\n): BlockFormat[] {\n if (!editor) return [];\n\n // Shortcuts are the ones TipTap's own extensions bind, read from their\n // source rather than invented: heading binds Mod-Alt-<level>, the list\n // extensions bind Mod-Shift-7/8, and setParagraph binds nothing at all.\n // A label promising a shortcut that does nothing is worse than no label.\n const mod = isApple() ? \"⌘\" : \"Ctrl\";\n\n // focus() is what makes a command apply to the selection rather than to\n // nothing — but on a phone the toolbar is only reachable because the\n // keyboard was dismissed, and focusing calls it straight back. The\n // scrollIntoView: false variant restores the range without the editor\n // asking for the keyboard again.\n const act = (run: () => boolean) => {\n const focused = editor.isFocused;\n if (focused) return run();\n editor.commands.focus(undefined, { scrollIntoView: false });\n const applied = run();\n (editor.view.dom as HTMLElement).blur();\n return applied;\n };\n\n return [\n {\n id: \"paragraph\",\n label: labels.paragraph,\n isActive: editor.isActive(\"paragraph\"),\n onSelect: () => act(() => editor.chain().setParagraph().run()),\n },\n ...([1, 2, 3] as const).map((level) => ({\n id: `heading${level}`,\n label: labels[`heading${level}` as keyof FormatLabels],\n shortcut: `${mod}+Alt+${level}`,\n isActive: editor.isActive(\"heading\", { level }),\n onSelect: () => act(() => editor.chain().toggleHeading({ level }).run()),\n })),\n {\n id: \"orderedList\",\n label: labels.orderedList,\n shortcut: `${mod}+Shift+7`,\n isActive: editor.isActive(\"orderedList\"),\n onSelect: () => act(() => editor.chain().toggleOrderedList().run()),\n },\n {\n id: \"bulletList\",\n label: labels.bulletList,\n shortcut: `${mod}+Shift+8`,\n isActive: editor.isActive(\"bulletList\"),\n onSelect: () => act(() => editor.chain().toggleBulletList().run()),\n },\n ];\n}\n\nfunction isApple(): boolean {\n if (typeof navigator === \"undefined\") return false;\n return /Mac|iPad|iPhone|iPod/.test(navigator.platform || navigator.userAgent);\n}\n","import { useViewport } from \"./useViewport\";\n\nexport type SaveState = \"idle\" | \"saving\" | \"saved\" | \"dirty\";\n\nexport interface EditorScreenProps {\n /** The writing surface. Mounted by the host, which owns the TipTap instance\n * and whichever extensions it needs. */\n children: React.ReactNode;\n title?: string;\n onTitleChange?: (title: string) => void;\n titlePlaceholder?: string;\n titleLabel?: string;\n saveState?: SaveState;\n saveLabels?: SaveLabels;\n /** Drawn at the far left of the bar; a back control belongs here. */\n lead?: React.ReactNode;\n /** Drawn at the far right. Keep it to what a thumb can reach on a phone —\n * one primary control and an overflow, not a row of five. */\n actions?: React.ReactNode;\n /** Sits between the save dot and the actions: a character count, a badge. */\n status?: React.ReactNode;\n /** Between the title and the writing surface — a dictation control, a hint. */\n aside?: React.ReactNode;\n /** Below the writing surface, inside the scrolling column. */\n footer?: React.ReactNode;\n /** Above the writing surface and below the bar, pinned while the page\n * scrolls: tabs over variants of one document belong here. */\n subnav?: React.ReactNode;\n /** Fills its container instead of the viewport, for a host whose shell\n * already owns the page height. */\n fill?: boolean;\n className?: string;\n}\n\nexport interface SaveLabels {\n saving: string;\n saved: string;\n dirty: string;\n}\n\nexport const defaultSaveLabels: SaveLabels = {\n saving: \"Enregistrement en cours\",\n saved: \"Enregistré\",\n dirty: \"Modifications non enregistrées\",\n};\n\n/**\n * EditorScreen is the page a writing surface sits in: a command bar, a title,\n * one column of prose, and slots for whatever the host puts around it.\n *\n * It renders and measures; it never fetches, saves or routes. `saveState` is\n * displayed, not computed — the host owns the debounce and the request, so the\n * same screen serves an autosaving draft and a form with an explicit button.\n *\n * Commands sit at the top rather than in a bar at the bottom. Below the fold\n * they are gone the moment the document runs past one screen, and pinned to\n * the bottom they eat a sixth of what the on-screen keyboard leaves.\n */\nexport function EditorScreen({\n children,\n title,\n onTitleChange,\n titlePlaceholder = \"Titre\",\n titleLabel = \"Titre\",\n saveState,\n saveLabels = defaultSaveLabels,\n lead,\n actions,\n status,\n aside,\n footer,\n subnav,\n fill = false,\n className,\n}: EditorScreenProps) {\n const { bottomInset } = useViewport();\n const withTitle = onTitleChange !== undefined;\n\n return (\n <div\n className={[\n \"lalt-screen\",\n fill ? \"lalt-screen--fill\" : \"\",\n className ?? \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n // The keyboard covers the layout viewport without shrinking it, so the\n // column's last lines sit under the keys unless the strip is padded out.\n style={{ \"--lalt-keyboard\": `${bottomInset}px` } as React.CSSProperties}\n >\n <header className=\"lalt-screen__bar\">\n {lead}\n {saveState && (\n <SaveDot state={saveState} labels={saveLabels} />\n )}\n {status && <div className=\"lalt-screen__status\">{status}</div>}\n {actions && <div className=\"lalt-screen__actions\">{actions}</div>}\n </header>\n\n {subnav && <div className=\"lalt-screen__subnav\">{subnav}</div>}\n\n <div className=\"lalt-screen__scroll\">\n <div className=\"lalt-screen__column\">\n {withTitle && (\n <input\n className=\"lalt-screen__title\"\n value={title ?? \"\"}\n onChange={(event) => onTitleChange(event.target.value)}\n placeholder={titlePlaceholder}\n aria-label={titleLabel}\n />\n )}\n {aside && <div className=\"lalt-screen__aside\">{aside}</div>}\n {children}\n {footer && <div className=\"lalt-screen__footer\">{footer}</div>}\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * A dot rather than a word: the state is worth a glance, never a read, and a\n * label that appears and vanishes leaves the author wondering what it meant\n * once it is gone. Screen readers get the sentence instead.\n */\nfunction SaveDot({ state, labels }: { state: SaveState; labels: SaveLabels }) {\n if (state === \"idle\") return <span className=\"lalt-screen__save\" />;\n\n const label =\n state === \"saving\" ? labels.saving : state === \"dirty\" ? labels.dirty : labels.saved;\n\n return (\n <span className=\"lalt-screen__save\" role=\"status\" aria-live=\"polite\">\n <span className={`lalt-screen__dot lalt-screen__dot--${state}`} aria-hidden />\n <span className=\"lalt-screen__sr\">{label}</span>\n </span>\n );\n}\n","import type { Editor } from \"@tiptap/react\";\nimport type { Range } from \"@tiptap/core\";\n\n// Type-only, as in formats.ts: StarterKit contributes these commands to a\n// chain, and without the reference TypeScript does not know they exist. The\n// host stays in charge of which extensions it actually mounts.\nimport type {} from \"@tiptap/starter-kit\";\n\n/**\n * SlashItem is one entry of the `/` menu.\n *\n * icon is a node rather than a component type: the package carries no icon\n * library, so the host renders whichever one it already ships.\n */\nexport interface SlashItem {\n id: string;\n title: string;\n description?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n command: (args: { editor: Editor; range: Range }) => void;\n}\n\nexport interface SlashLabels {\n heading1: string;\n heading2: string;\n bulletList: string;\n orderedList: string;\n blockquote: string;\n codeBlock: string;\n horizontalRule: string;\n}\n\nexport const defaultSlashLabels: SlashLabels = {\n heading1: \"Titre 1\",\n heading2: \"Titre 2\",\n bulletList: \"Liste à puces\",\n orderedList: \"Liste numérotée\",\n blockquote: \"Citation\",\n codeBlock: \"Bloc de code\",\n horizontalRule: \"Séparateur\",\n};\n\nexport interface SlashDescriptions {\n heading1?: string;\n heading2?: string;\n bulletList?: string;\n orderedList?: string;\n blockquote?: string;\n codeBlock?: string;\n horizontalRule?: string;\n}\n\nexport interface DefaultSlashItemsOptions {\n labels?: SlashLabels;\n descriptions?: SlashDescriptions;\n icons?: Partial<Record<DefaultSlashId, React.ReactNode>>;\n}\n\nexport type DefaultSlashId =\n | \"heading1\"\n | \"heading2\"\n | \"bulletList\"\n | \"orderedList\"\n | \"blockquote\"\n | \"codeBlock\"\n | \"horizontalRule\";\n\n/**\n * defaultSlashItems is the block set every writing surface shares.\n *\n * Returned as data rather than rendered behind flags: a host adds, removes or\n * reorders entries by composing the array, so a surface that needs one more\n * command does not push another boolean into this signature.\n */\nexport function defaultSlashItems({\n labels = defaultSlashLabels,\n descriptions = {},\n icons = {},\n}: DefaultSlashItemsOptions = {}): SlashItem[] {\n const item = (\n id: DefaultSlashId,\n shortcut: string,\n run: (editor: Editor) => void,\n ): SlashItem => ({\n id,\n title: labels[id],\n description: descriptions[id],\n shortcut,\n icon: icons[id],\n command: ({ editor, range }) => {\n editor.chain().focus().deleteRange(range).run();\n run(editor);\n },\n });\n\n return [\n item(\"heading1\", \"#\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 1 }).run(),\n ),\n item(\"heading2\", \"##\", (editor) =>\n editor.chain().focus().setNode(\"heading\", { level: 2 }).run(),\n ),\n item(\"bulletList\", \"-\", (editor) =>\n editor.chain().focus().toggleBulletList().run(),\n ),\n item(\"orderedList\", \"1.\", (editor) =>\n editor.chain().focus().toggleOrderedList().run(),\n ),\n item(\"blockquote\", \">\", (editor) =>\n editor.chain().focus().toggleBlockquote().run(),\n ),\n item(\"codeBlock\", \"```\", (editor) =>\n editor.chain().focus().toggleCodeBlock().run(),\n ),\n item(\"horizontalRule\", \"---\", (editor) =>\n editor.chain().focus().setHorizontalRule().run(),\n ),\n ];\n}\n\n/** filterSlashItems matches the query against titles the way the menu does. */\nexport function filterSlashItems(items: SlashItem[], query: string): SlashItem[] {\n const q = query.trim().toLowerCase();\n if (q === \"\") return items;\n return items.filter((item) => item.title.toLowerCase().includes(q));\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from \"react\";\n\nimport type { SlashItem } from \"./slash\";\n\nexport interface SlashMenuHandle {\n onKeyDown: (props: { event: KeyboardEvent }) => boolean;\n}\n\nexport interface SlashMenuProps {\n items: SlashItem[];\n command: (item: SlashItem) => void;\n emptyLabel?: string;\n}\n\nexport const SlashMenu = forwardRef<SlashMenuHandle, SlashMenuProps>(\n function SlashMenu({ items, command, emptyLabel = \"Aucune commande\" }, ref) {\n const [selectedIndex, setSelectedIndex] = useState(0);\n\n // The suggestion plugin rebuilds these props on every keystroke, so the\n // imperative handle reads refs rather than a captured render closure —\n // otherwise Enter runs the command the menu showed one keystroke ago.\n const itemsRef = useRef(items);\n itemsRef.current = items;\n const commandRef = useRef(command);\n commandRef.current = command;\n const selectedRef = useRef(selectedIndex);\n selectedRef.current = selectedIndex;\n\n const ids = items.map((item) => item.id).join(\" \");\n useEffect(() => {\n setSelectedIndex(0);\n selectedRef.current = 0;\n }, [ids]);\n\n const selectItem = useCallback((index: number) => {\n const item = itemsRef.current[index];\n if (item) commandRef.current(item);\n }, []);\n\n const move = useCallback((delta: number) => {\n const count = itemsRef.current.length;\n if (count === 0) return;\n const next = (selectedRef.current + delta + count) % count;\n selectedRef.current = next;\n setSelectedIndex(next);\n }, []);\n\n useImperativeHandle(ref, () => ({\n onKeyDown: ({ event }) => {\n if (event.key === \"ArrowUp\") {\n move(-1);\n return true;\n }\n if (event.key === \"ArrowDown\") {\n move(1);\n return true;\n }\n if (event.key === \"Enter\" || event.key === \"Tab\") {\n if (itemsRef.current.length === 0) return false;\n selectItem(selectedRef.current);\n return true;\n }\n return false;\n },\n }));\n\n if (items.length === 0) {\n return <div className=\"lalt-slash lalt-slash--empty\">{emptyLabel}</div>;\n }\n\n return (\n <div className=\"lalt-slash\" role=\"listbox\">\n {items.map((item, index) => (\n <button\n key={item.id}\n type=\"button\"\n role=\"option\"\n aria-selected={index === selectedIndex}\n className={\n index === selectedIndex\n ? \"lalt-slash__item lalt-slash__item--active\"\n : \"lalt-slash__item\"\n }\n // Losing editor focus tears the suggestion plugin down before a\n // click lands, so the item would never run its command.\n onMouseDown={(event) => {\n event.preventDefault();\n selectItem(index);\n }}\n onMouseEnter={() => {\n selectedRef.current = index;\n setSelectedIndex(index);\n }}\n >\n {item.icon && (\n <span className=\"lalt-slash__icon\" aria-hidden>\n {item.icon}\n </span>\n )}\n <span className=\"lalt-slash__text\">\n <span className=\"lalt-slash__title\">{item.title}</span>\n {item.description && (\n <span className=\"lalt-slash__description\">{item.description}</span>\n )}\n </span>\n {item.shortcut && (\n <kbd className=\"lalt-slash__shortcut\">{item.shortcut}</kbd>\n )}\n </button>\n ))}\n </div>\n );\n },\n);\n","import { Extension } from \"@tiptap/core\";\nimport { ReactRenderer } from \"@tiptap/react\";\nimport { PluginKey } from \"@tiptap/pm/state\";\nimport Suggestion, { type SuggestionOptions, type SuggestionProps } from \"@tiptap/suggestion\";\n\nimport { SlashMenu, type SlashMenuHandle, type SlashMenuProps } from \"./SlashMenu\";\nimport { filterSlashItems, type SlashItem } from \"./slash\";\n\nexport interface SlashCommandsOptions {\n /** Read on every keystroke, so a host can swap the set without remounting\n * TipTap — rebuilding the extensions array wipes the text being typed. */\n items: () => SlashItem[];\n char: string;\n emptyLabel?: string;\n /** Distinguishes concurrently mounted editors: two surfaces sharing one key\n * would answer each other's suggestions. */\n pluginKey: PluginKey;\n}\n\nconst OFFSET = 6;\n\nexport const SlashCommands = Extension.create<SlashCommandsOptions>({\n name: \"laSlashCommands\",\n\n addOptions() {\n return {\n items: () => [],\n char: \"/\",\n pluginKey: new PluginKey(\"laSlashCommands\"),\n };\n },\n\n addProseMirrorPlugins() {\n const { items, char, emptyLabel, pluginKey } = this.options;\n\n const suggestion: Omit<SuggestionOptions<SlashItem>, \"editor\"> = {\n char,\n pluginKey,\n startOfLine: false,\n items: ({ query }) => filterSlashItems(items(), query),\n render: () => {\n let component: ReactRenderer<SlashMenuHandle, SlashMenuProps> | null = null;\n let container: HTMLDivElement | null = null;\n\n const place = (clientRect: SuggestionProps<SlashItem>[\"clientRect\"]) => {\n if (!container || !clientRect) return;\n const rect = clientRect();\n if (!rect) return;\n container.style.top = `${rect.bottom + OFFSET}px`;\n container.style.left = `${rect.left}px`;\n };\n\n const close = () => {\n container?.remove();\n container = null;\n component?.destroy();\n component = null;\n };\n\n return {\n onStart: (props) => {\n component = new ReactRenderer(SlashMenu, {\n props: { ...props, emptyLabel },\n editor: props.editor,\n });\n container = document.createElement(\"div\");\n container.className = \"lalt-slash-anchor\";\n container.appendChild(component.element);\n document.body.appendChild(container);\n place(props.clientRect);\n },\n onUpdate: (props) => {\n component?.updateProps({ ...props, emptyLabel });\n place(props.clientRect);\n },\n onKeyDown: (props) => {\n if (props.event.key === \"Escape\") {\n close();\n return true;\n }\n return component?.ref?.onKeyDown(props) ?? false;\n },\n onExit: close,\n };\n },\n };\n\n return [\n Suggestion({\n editor: this.editor,\n ...suggestion,\n command: ({ editor, range, props }) => props.command({ editor, range }),\n }),\n ];\n },\n});\n","import { Extension } from \"@tiptap/core\";\nimport { Plugin, PluginKey } from \"@tiptap/pm/state\";\nimport { Decoration, DecorationSet } from \"@tiptap/pm/view\";\nimport type { Node as ProseMirrorNode } from \"@tiptap/pm/model\";\n\nexport interface InlineSuggestion {\n id: string;\n /** Document positions of the passage this replaces. */\n from: number;\n to: number;\n after: string;\n}\n\nexport interface SuggestionActions {\n onAccept: (id: string) => void;\n onReject: (id: string) => void;\n acceptLabel: string;\n rejectLabel: string;\n}\n\nexport const inlineSuggestionsKey = new PluginKey<DecorationSet>(\"lalt-inline-suggestions\");\n\n/**\n * InlineSuggestions renders proposed edits where they belong: over the passage\n * they replace, with the new text right after it.\n *\n * Decorations, never document changes. The editor's content stays exactly what\n * the author wrote until they accept, so the draft is saveable mid-review and\n * a rejected suggestion leaves nothing behind — which a document-level insert\n * could not promise.\n */\nexport const InlineSuggestions = Extension.create<{ actions: SuggestionActions | null }>({\n name: \"lalt-inline-suggestions\",\n\n addOptions() {\n return { actions: null };\n },\n\n addProseMirrorPlugins() {\n const options = this.options;\n\n return [\n new Plugin<DecorationSet>({\n key: inlineSuggestionsKey,\n\n state: {\n init: () => DecorationSet.empty,\n apply(tr, old) {\n const set = tr.getMeta(inlineSuggestionsKey) as InlineSuggestion[] | undefined;\n if (set) return build(tr.doc, set, options.actions);\n // Positions follow the document as the author keeps typing around\n // a pending suggestion.\n return old.map(tr.mapping, tr.doc);\n },\n },\n\n props: {\n decorations(state) {\n return inlineSuggestionsKey.getState(state) ?? DecorationSet.empty;\n },\n },\n }),\n ];\n },\n});\n\nfunction build(\n doc: ProseMirrorNode,\n suggestions: InlineSuggestion[],\n actions: SuggestionActions | null,\n): DecorationSet {\n const decorations: Decoration[] = [];\n\n for (const s of suggestions) {\n if (s.from < 0 || s.to > doc.content.size || s.from >= s.to) continue;\n\n decorations.push(\n Decoration.inline(s.from, s.to, { class: \"lalt-suggestion__old\" }),\n Decoration.widget(s.to, () => renderProposal(s, actions), {\n // The widget belongs to the suggestion that precedes it, so typing at\n // the boundary does not push it around.\n side: 1,\n ignoreSelection: true,\n }),\n );\n }\n return DecorationSet.create(doc, decorations);\n}\n\nfunction renderProposal(s: InlineSuggestion, actions: SuggestionActions | null): HTMLElement {\n const wrap = document.createElement(\"span\");\n wrap.className = \"lalt-suggestion\";\n wrap.setAttribute(\"contenteditable\", \"false\");\n\n const text = document.createElement(\"span\");\n text.className = \"lalt-suggestion__new\";\n text.textContent = s.after;\n wrap.appendChild(text);\n\n if (actions) {\n wrap.appendChild(button(\"reject\", actions.rejectLabel, \"✕\", () => actions.onReject(s.id)));\n wrap.appendChild(button(\"accept\", actions.acceptLabel, \"✓\", () => actions.onAccept(s.id)));\n }\n return wrap;\n}\n\nfunction button(kind: string, label: string, glyph: string, onClick: () => void): HTMLElement {\n const el = document.createElement(\"button\");\n el.type = \"button\";\n el.className = `lalt-suggestion__action lalt-suggestion__action--${kind}`;\n el.setAttribute(\"aria-label\", label);\n el.textContent = glyph;\n // mousedown would move the selection into the widget before the click lands.\n el.addEventListener(\"mousedown\", (e) => e.preventDefault());\n el.addEventListener(\"click\", (e) => {\n e.preventDefault();\n onClick();\n });\n return el;\n}\n","export interface TextRange {\n from: number;\n to: number;\n}\n\nexport interface RevisionPart {\n before: string;\n after: string;\n}\n\n// Letters, digits, and the marks that belong inside a French word.\nconst WORD = /[\\p{L}\\p{N}'’-]/u;\n\n/**\n * expandToWords grows a range out to whole words.\n *\n * A selection dragged across a line lands mid-word more often than not, and a\n * model handed \"ns de production\" answers for a fragment — which then reads as\n * a truncated diff the author has to decode.\n */\nexport function expandToWords(text: string, from: number, to: number): TextRange {\n if (from >= to) return { from, to };\n\n let start = from;\n while (start > 0 && WORD.test(text[start - 1]) && WORD.test(text[start])) start--;\n\n let end = to;\n while (end < text.length && WORD.test(text[end]) && WORD.test(text[end - 1])) end++;\n\n return { from: start, to: end };\n}\n\n/**\n * splitRevision turns one model answer into the decisions it really contains.\n *\n * A three-paragraph rewrite accepted or rejected as a whole forces the author\n * to keep a weak paragraph for a strong one, so each paragraph becomes its own\n * decision — but only when both sides line up. When the model merged or split\n * paragraphs, position no longer maps them, and pairing by index would show a\n * diff that never existed.\n */\nexport function splitRevision(before: string, after: string): RevisionPart[] {\n if (before.trim() === after.trim()) return [];\n\n const olds = paragraphs(before);\n const news = paragraphs(after);\n\n if (olds.length !== news.length || olds.length === 0) {\n return [{ before: before.trim(), after: after.trim() }];\n }\n\n const parts: RevisionPart[] = [];\n for (let i = 0; i < olds.length; i++) {\n if (olds[i] !== news[i]) parts.push({ before: olds[i], after: news[i] });\n }\n return parts;\n}\n\nfunction paragraphs(s: string): string[] {\n return s\n .split(/\\n\\s*\\n/)\n .map((p) => p.trim())\n .filter((p) => p !== \"\");\n}\n","import type { RichDoc, RichNode } from \"./document\";\n\ninterface Mark {\n type?: string;\n attrs?: Record<string, unknown>;\n}\n\n/**\n * docToMarkdown renders a TipTap document as the markdown a blog accepts.\n *\n * Written here rather than taken from a library: the editor only ever produces\n * the handful of nodes StarterKit gives it, and a general markdown serializer\n * brings a dependency and its escaping rules for output a human is about to\n * paste into their own site.\n */\nexport function docToMarkdown(doc: RichDoc): string {\n return blocks(doc.content ?? [])\n .filter((b) => b !== \"\")\n .join(\"\\n\\n\");\n}\n\nfunction blocks(nodes: RichNode[]): string[] {\n return nodes.map(block);\n}\n\nfunction block(node: RichNode): string {\n switch (node.type) {\n case \"heading\": {\n const level = Number(node.attrs?.level ?? 2);\n return `${\"#\".repeat(Math.min(6, Math.max(1, level)))} ${inline(node.content)}`;\n }\n case \"blockquote\":\n return blocks(node.content ?? [])\n .filter((b) => b !== \"\")\n .map((b) =>\n b\n .split(\"\\n\")\n .map((line) => `> ${line}`)\n .join(\"\\n\"),\n )\n .join(\"\\n>\\n\");\n case \"codeBlock\": {\n const language = String(node.attrs?.language ?? \"\");\n return `\\`\\`\\`${language}\\n${inline(node.content)}\\n\\`\\`\\``;\n }\n case \"bulletList\":\n return listItems(node).map((item) => `- ${item}`).join(\"\\n\");\n case \"orderedList\":\n return listItems(node)\n .map((item, i) => `${i + 1}. ${item}`)\n .join(\"\\n\");\n case \"horizontalRule\":\n return \"---\";\n default:\n return inline(node.content);\n }\n}\n\nfunction listItems(list: RichNode): string[] {\n return (list.content ?? []).map((item) =>\n blocks(item.content ?? [])\n .filter((b) => b !== \"\")\n // Continuation lines are indented so the item stays one item.\n .join(\"\\n\\n\")\n .split(\"\\n\")\n .join(\"\\n \"),\n );\n}\n\nfunction inline(nodes: RichNode[] | undefined): string {\n return (nodes ?? []).map(text).join(\"\");\n}\n\nfunction text(node: RichNode): string {\n if (node.type === \"hardBreak\") return \"\\n\";\n if (typeof node.text !== \"string\") return inline(node.content);\n\n let out = node.text;\n // Innermost first, so bold inside a link wraps the text and not the URL.\n for (const mark of ((node.marks as Mark[] | undefined) ?? []).slice().reverse()) {\n switch (mark.type) {\n case \"bold\":\n out = `**${out}**`;\n break;\n case \"italic\":\n out = `*${out}*`;\n break;\n case \"strike\":\n out = `~~${out}~~`;\n break;\n case \"code\":\n out = `\\`${out}\\``;\n break;\n case \"link\":\n out = `[${out}](${String(mark.attrs?.href ?? \"\")})`;\n break;\n }\n }\n return out;\n}\n"],"mappings":";;;;;;;;AA2BA,IAAM,MAAM;AASL,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,cAAc;AAChB,GAAwC;AACtC,QAAM,YAAY,UAAU,MAAM,MAAM,QAAQ,UAAU;AAC1D,QAAM,YAAY,UAAU,SAAS,MAAM,QAAQ,UAAU,SAAS;AAEtE,QAAM,QAAQ,cAAc,aAAa,CAAC,YAAY,CAAC,aAAa;AAEpE,QAAM,MAAM,QACR,UAAU,SAAS,MACnB,KAAK,IAAI,GAAG,UAAU,MAAM,MAAM,QAAQ,MAAM;AAEpD,QAAM,UAAU,UAAU,IAAI,QAAQ,QAAQ;AAC9C,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,SAAS,SAAS,QAAQ,QAAQ,KAAK,CAAC;AAE1E,SAAO,EAAE,MAAM,KAAK,MAAM;AAC5B;;;ACvDA,SAAS,aAAAA,YAAW,iBAAiB,QAAQ,YAAAC,iBAAgB;AAC7D,SAAS,oBAAoB;;;ACD7B,SAAS,WAAW,gBAAgB;;;AC8B7B,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AACF,GAAuC;AACrC,MAAI,CAAC,QAAQ;AACX,WAAO,EAAE,OAAO,YAAY,QAAQ,aAAa,WAAW,GAAG,aAAa,EAAE;AAAA,EAChF;AAOA,QAAM,UAAU,cAAc,OAAO,SAAS,OAAO;AAErD,SAAO;AAAA,IACL,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,aAAa,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC;AAAA,EAC9C;AACF;;;ADhDA,IAAM,SAA0B,EAAE,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,EAAE;AAEpF,SAAS,OAAwB;AAC/B,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO,aAAa;AAAA,IAClB,YAAY,OAAO;AAAA,IACnB,aAAa,OAAO;AAAA,IACpB,QAAQ,OAAO;AAAA,EACjB,CAAC;AACH;AAUO,SAAS,cAA+B;AAC7C,QAAM,CAAC,SAAS,UAAU,IAAI,SAA0B,IAAI;AAE5D,YAAU,MAAM;AACd,UAAM,SAAS,MAAM,WAAW,KAAK,CAAC;AACtC,WAAO;AAEP,UAAM,SAAS,OAAO;AACtB,YAAQ,iBAAiB,UAAU,MAAM;AAGzC,YAAQ,iBAAiB,UAAU,MAAM;AACzC,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,iBAAiB,qBAAqB,MAAM;AAEnD,WAAO,MAAM;AACX,cAAQ,oBAAoB,UAAU,MAAM;AAC5C,cAAQ,oBAAoB,UAAU,MAAM;AAC5C,aAAO,oBAAoB,UAAU,MAAM;AAC3C,aAAO,oBAAoB,qBAAqB,MAAM;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;;;AD+EQ,SAYE,KAZF;AA7FR,IAAM,WAAW,EAAE,OAAO,KAAK,QAAQ,GAAG;AAQnC,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA,UAAU,CAAC;AAAA,EACX,UAAU,CAAC;AAAA,EACX,eAAe;AAAA,EACf,cAAc;AAAA,EACd;AACF,GAA0B;AACxB,QAAM,MAAM,OAAuB,IAAI;AACvC,QAAM,CAAC,MAAM,OAAO,IAAIC,UAAmB,EAAE,GAAG,UAAU,MAAM,MAAM,CAAC;AACvE,QAAM,CAAC,aAAa,cAAc,IAAIA,UAAS,KAAK;AACpD,QAAM,WAAW,YAAY;AAQ7B,kBAAgB,MAAM;AACpB,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,UAAU,MAAM;AACpB,YAAM,OAAO,GAAG,sBAAsB;AACtC,UAAI,KAAK,QAAQ;AACf;AAAA,UAAQ,CAAC,SACP,KAAK,QAAQ,KAAK,UAAU,KAAK,SAAS,KAAK,WAAW,KAAK,SAC3D,OACA,EAAE,OAAO,KAAK,OAAO,QAAQ,KAAK,QAAQ,MAAM,KAAK;AAAA,QAC3D;AAAA,IACJ;AACA,YAAQ;AACR,QAAI,OAAO,mBAAmB,YAAa;AAC3C,UAAM,WAAW,IAAI,eAAe,OAAO;AAC3C,aAAS,QAAQ,EAAE;AACnB,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,CAAC;AAEL,EAAAC,WAAU,MAAM;AACd,QAAI,CAAC,YAAa;AAClB,UAAM,QAAQ,CAAC,MAAkB;AAC/B,UAAI,IAAI,WAAW,CAAC,IAAI,QAAQ,SAAS,EAAE,MAAc,EAAG,gBAAe,KAAK;AAAA,IAClF;AACA,aAAS,iBAAiB,aAAa,KAAK;AAC5C,WAAO,MAAM,SAAS,oBAAoB,aAAa,KAAK;AAAA,EAC9D,GAAG,CAAC,WAAW,CAAC;AAEhB,MAAI,OAAO,aAAa,YAAa,QAAO;AAE5C,QAAM,QAAQ,aAAa;AAAA,IACzB;AAAA,IACA,SAAS;AAAA,IACT,UAAU,EAAE,OAAO,SAAS,OAAO,QAAQ,SAAS,OAAO;AAAA,IAC3D;AAAA,EACF,CAAC;AAED,SAAO;AAAA,IACL;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,WAAU;AAAA,QASV,OACE;AAAA,UACE,uBAAuB,GAAG,MAAM,IAAI;AAAA,UACpC,sBAAsB,GAAG,MAAM,GAAG;AAAA,UAClC,yBAAyB,GAAG,SAAS,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAMhD,YAAY,KAAK,OAAO,SAAY;AAAA,QACtC;AAAA,QAGD;AAAA,kBAAQ,IAAI,CAAC,WACZ;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,WAAU;AAAA,cAGV,cAAY,OAAO,OAAO,OAAO,QAAQ;AAAA,cACzC,iBAAe,OAAO,OAAO,KAAK;AAAA,cAClC,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,cACrC,SAAS,MAAM,OAAO,SAAS,YAAY;AAAA,cAE1C;AAAA,uBAAO;AAAA,gBACR,oBAAC,UAAK,WAAU,uBAAuB,iBAAO,OAAM;AAAA;AAAA;AAAA,YAX/C,OAAO;AAAA,UAYd,CACD;AAAA,UAEA,QAAQ,SAAS,KAChB,qBAAC,SAAI,WAAU,yBACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,iBAAc;AAAA,gBACd,iBAAe;AAAA,gBACf,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,gBACrC,SAAS,MAAM,eAAe,CAAC,SAAS,CAAC,IAAI;AAAA,gBAE5C;AAAA;AAAA,YACH;AAAA,YACC,eACC,oBAAC,SAAI,MAAK,QAAO,WAAU,sBACxB,kBAAQ,IAAI,CAAC,WACZ;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAK;AAAA,gBACL,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,gBAAc,OAAO;AAAA,gBACrB,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,gBACrC,SAAS,MAAM;AACb,yBAAO,SAAS;AAChB,iCAAe,KAAK;AAAA,gBACtB;AAAA,gBAEA;AAAA,sCAAC,UAAM,iBAAO,OAAM;AAAA,kBACnB,OAAO,YACN,oBAAC,SAAI,WAAU,0BAA0B,iBAAO,UAAS;AAAA;AAAA;AAAA,cAbtD,OAAO;AAAA,YAed,CACD,GACH;AAAA,aAEJ;AAAA;AAAA;AAAA,IAEJ;AAAA,IACA,aAAa,SAAS;AAAA,EACxB;AACF;;;AGtLA,SAAS,aAAAC,YAAW,YAAAC,iBAAgB;AAapC,SAAS,qBAA8B;AACrC,MAAI,OAAO,WAAW,eAAe,CAAC,OAAO,WAAY,QAAO;AAChE,SAAO,OAAO,WAAW,qCAAqC,EAAE;AAClE;AAaO,SAAS,oBAAoB,QAA6C;AAC/E,QAAM,CAAC,WAAW,YAAY,IAAIA,UAA+B,IAAI;AAErE,EAAAD,WAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AAEb,UAAME,QAAO,MAAM;AACjB,YAAM,EAAE,MAAM,IAAI;AAClB,YAAM,EAAE,MAAM,IAAI,MAAM,IAAI,MAAM;AAKlC,UAAI,OAAO;AACT,qBAAa,IAAI;AACjB;AAAA,MACF;AAEA,YAAMC,QAAO,MAAM,IAAI,YAAY,MAAM,IAAI,GAAG,EAAE,KAAK;AACvD,UAAIA,UAAS,IAAI;AACf,qBAAa,IAAI;AACjB;AAAA,MACF;AAIA,UAAI,mBAAmB,KAAK,OAAO,WAAW;AAC5C,QAAC,OAAO,KAAK,IAAoB,KAAK;AAAA,MACxC;AAEA,YAAM,QAAQ,OAAO,KAAK,YAAY,IAAI;AAC1C,YAAM,MAAM,OAAO,KAAK,YAAY,EAAE;AACtC,mBAAa;AAAA,QACX,MAAAA;AAAA,QACA,MAAM;AAAA,UACJ,IAAI,MAAM,OAAO,IAAI,SAAS;AAAA,UAC9B,KAAK,KAAK,IAAI,MAAM,KAAK,IAAI,GAAG;AAAA,UAChC,QAAQ,KAAK,IAAI,MAAM,QAAQ,IAAI,MAAM;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH;AAEA,IAAAD,MAAK;AACL,WAAO,GAAG,mBAAmBA,KAAI;AACjC,WAAO,GAAG,QAAQA,KAAI;AACtB,WAAO,GAAG,SAASA,KAAI;AACvB,WAAO,MAAM;AACX,aAAO,IAAI,mBAAmBA,KAAI;AAClC,aAAO,IAAI,QAAQA,KAAI;AACvB,aAAO,IAAI,SAASA,KAAI;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAEX,SAAO;AACT;;;AChEO,IAAM,sBAAoC;AAAA,EAC/C,WAAW;AAAA,EACX,UAAU;AAAA,EACV,UAAU;AAAA,EACV,UAAU;AAAA,EACV,aAAa;AAAA,EACb,YAAY;AACd;AAQO,SAAS,aACd,QACA,SAAuB,qBACR;AACf,MAAI,CAAC,OAAQ,QAAO,CAAC;AAMrB,QAAM,MAAM,QAAQ,IAAI,WAAM;AAO9B,QAAM,MAAM,CAAC,QAAuB;AAClC,UAAM,UAAU,OAAO;AACvB,QAAI,QAAS,QAAO,IAAI;AACxB,WAAO,SAAS,MAAM,QAAW,EAAE,gBAAgB,MAAM,CAAC;AAC1D,UAAM,UAAU,IAAI;AACpB,IAAC,OAAO,KAAK,IAAoB,KAAK;AACtC,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,OAAO;AAAA,MACd,UAAU,OAAO,SAAS,WAAW;AAAA,MACrC,UAAU,MAAM,IAAI,MAAM,OAAO,MAAM,EAAE,aAAa,EAAE,IAAI,CAAC;AAAA,IAC/D;AAAA,IACA,GAAI,CAAC,GAAG,GAAG,CAAC,EAAY,IAAI,CAAC,WAAW;AAAA,MACtC,IAAI,UAAU,KAAK;AAAA,MACnB,OAAO,OAAO,UAAU,KAAK,EAAwB;AAAA,MACrD,UAAU,GAAG,GAAG,QAAQ,KAAK;AAAA,MAC7B,UAAU,OAAO,SAAS,WAAW,EAAE,MAAM,CAAC;AAAA,MAC9C,UAAU,MAAM,IAAI,MAAM,OAAO,MAAM,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC;AAAA,IACzE,EAAE;AAAA,IACF;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,OAAO;AAAA,MACd,UAAU,GAAG,GAAG;AAAA,MAChB,UAAU,OAAO,SAAS,aAAa;AAAA,MACvC,UAAU,MAAM,IAAI,MAAM,OAAO,MAAM,EAAE,kBAAkB,EAAE,IAAI,CAAC;AAAA,IACpE;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,OAAO;AAAA,MACd,UAAU,GAAG,GAAG;AAAA,MAChB,UAAU,OAAO,SAAS,YAAY;AAAA,MACtC,UAAU,MAAM,IAAI,MAAM,OAAO,MAAM,EAAE,iBAAiB,EAAE,IAAI,CAAC;AAAA,IACnE;AAAA,EACF;AACF;AAEA,SAAS,UAAmB;AAC1B,MAAI,OAAO,cAAc,YAAa,QAAO;AAC7C,SAAO,uBAAuB,KAAK,UAAU,YAAY,UAAU,SAAS;AAC9E;;;ACHM,SAGI,OAAAE,MAHJ,QAAAC,aAAA;AAnDC,IAAM,oBAAgC;AAAA,EAC3C,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,OAAO;AACT;AAcO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,mBAAmB;AAAA,EACnB,aAAa;AAAA,EACb;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAAsB;AACpB,QAAM,EAAE,YAAY,IAAI,YAAY;AACpC,QAAM,YAAY,kBAAkB;AAEpC,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,OAAO,sBAAsB;AAAA,QAC7B,aAAa;AAAA,MACf,EACG,OAAO,OAAO,EACd,KAAK,GAAG;AAAA,MAGX,OAAO,EAAE,mBAAmB,GAAG,WAAW,KAAK;AAAA,MAE/C;AAAA,wBAAAA,MAAC,YAAO,WAAU,oBACf;AAAA;AAAA,UACA,aACC,gBAAAD,KAAC,WAAQ,OAAO,WAAW,QAAQ,YAAY;AAAA,UAEhD,UAAU,gBAAAA,KAAC,SAAI,WAAU,uBAAuB,kBAAO;AAAA,UACvD,WAAW,gBAAAA,KAAC,SAAI,WAAU,wBAAwB,mBAAQ;AAAA,WAC7D;AAAA,QAEC,UAAU,gBAAAA,KAAC,SAAI,WAAU,uBAAuB,kBAAO;AAAA,QAExD,gBAAAA,KAAC,SAAI,WAAU,uBACb,0BAAAC,MAAC,SAAI,WAAU,uBACZ;AAAA,uBACC,gBAAAD;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,OAAO,SAAS;AAAA,cAChB,UAAU,CAAC,UAAU,cAAc,MAAM,OAAO,KAAK;AAAA,cACrD,aAAa;AAAA,cACb,cAAY;AAAA;AAAA,UACd;AAAA,UAED,SAAS,gBAAAA,KAAC,SAAI,WAAU,sBAAsB,iBAAM;AAAA,UACpD;AAAA,UACA,UAAU,gBAAAA,KAAC,SAAI,WAAU,uBAAuB,kBAAO;AAAA,WAC1D,GACF;AAAA;AAAA;AAAA,EACF;AAEJ;AAOA,SAAS,QAAQ,EAAE,OAAO,OAAO,GAA6C;AAC5E,MAAI,UAAU,OAAQ,QAAO,gBAAAA,KAAC,UAAK,WAAU,qBAAoB;AAEjE,QAAM,QACJ,UAAU,WAAW,OAAO,SAAS,UAAU,UAAU,OAAO,QAAQ,OAAO;AAEjF,SACE,gBAAAC,MAAC,UAAK,WAAU,qBAAoB,MAAK,UAAS,aAAU,UAC1D;AAAA,oBAAAD,KAAC,UAAK,WAAW,sCAAsC,KAAK,IAAI,eAAW,MAAC;AAAA,IAC5E,gBAAAA,KAAC,UAAK,WAAU,mBAAmB,iBAAM;AAAA,KAC3C;AAEJ;;;AC1GO,IAAM,qBAAkC;AAAA,EAC7C,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,gBAAgB;AAClB;AAkCO,SAAS,kBAAkB;AAAA,EAChC,SAAS;AAAA,EACT,eAAe,CAAC;AAAA,EAChB,QAAQ,CAAC;AACX,IAA8B,CAAC,GAAgB;AAC7C,QAAM,OAAO,CACX,IACA,UACA,SACe;AAAA,IACf;AAAA,IACA,OAAO,OAAO,EAAE;AAAA,IAChB,aAAa,aAAa,EAAE;AAAA,IAC5B;AAAA,IACA,MAAM,MAAM,EAAE;AAAA,IACd,SAAS,CAAC,EAAE,QAAQ,MAAM,MAAM;AAC9B,aAAO,MAAM,EAAE,MAAM,EAAE,YAAY,KAAK,EAAE,IAAI;AAC9C,UAAI,MAAM;AAAA,IACZ;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,MAAK;AAAA,MAAY;AAAA,MAAK,CAAC,WACrB,OAAO,MAAM,EAAE,MAAM,EAAE,QAAQ,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI;AAAA,IAC9D;AAAA,IACA;AAAA,MAAK;AAAA,MAAY;AAAA,MAAM,CAAC,WACtB,OAAO,MAAM,EAAE,MAAM,EAAE,QAAQ,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI;AAAA,IAC9D;AAAA,IACA;AAAA,MAAK;AAAA,MAAc;AAAA,MAAK,CAAC,WACvB,OAAO,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI;AAAA,IAChD;AAAA,IACA;AAAA,MAAK;AAAA,MAAe;AAAA,MAAM,CAAC,WACzB,OAAO,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI;AAAA,IACjD;AAAA,IACA;AAAA,MAAK;AAAA,MAAc;AAAA,MAAK,CAAC,WACvB,OAAO,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI;AAAA,IAChD;AAAA,IACA;AAAA,MAAK;AAAA,MAAa;AAAA,MAAO,CAAC,WACxB,OAAO,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,IAAI;AAAA,IAC/C;AAAA,IACA;AAAA,MAAK;AAAA,MAAkB;AAAA,MAAO,CAAC,WAC7B,OAAO,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI;AAAA,IACjD;AAAA,EACF;AACF;AAGO,SAAS,iBAAiB,OAAoB,OAA4B;AAC/E,QAAM,IAAI,MAAM,KAAK,EAAE,YAAY;AACnC,MAAI,MAAM,GAAI,QAAO;AACrB,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,MAAM,YAAY,EAAE,SAAS,CAAC,CAAC;AACpE;;;AC9HA;AAAA,EACE;AAAA,EACA;AAAA,EACA,aAAAE;AAAA,EACA;AAAA,EACA,UAAAC;AAAA,EACA,YAAAC;AAAA,OACK;AAmEM,gBAAAC,MAgCD,QAAAC,aAhCC;AArDN,IAAM,YAAY;AAAA,EACvB,SAASC,WAAU,EAAE,OAAO,SAAS,aAAa,kBAAkB,GAAG,KAAK;AAC1E,UAAM,CAAC,eAAe,gBAAgB,IAAIH,UAAS,CAAC;AAKpD,UAAM,WAAWD,QAAO,KAAK;AAC7B,aAAS,UAAU;AACnB,UAAM,aAAaA,QAAO,OAAO;AACjC,eAAW,UAAU;AACrB,UAAM,cAAcA,QAAO,aAAa;AACxC,gBAAY,UAAU;AAEtB,UAAM,MAAM,MAAM,IAAI,CAAC,SAAS,KAAK,EAAE,EAAE,KAAK,GAAG;AACjD,IAAAD,WAAU,MAAM;AACd,uBAAiB,CAAC;AAClB,kBAAY,UAAU;AAAA,IACxB,GAAG,CAAC,GAAG,CAAC;AAER,UAAM,aAAa,YAAY,CAAC,UAAkB;AAChD,YAAM,OAAO,SAAS,QAAQ,KAAK;AACnC,UAAI,KAAM,YAAW,QAAQ,IAAI;AAAA,IACnC,GAAG,CAAC,CAAC;AAEL,UAAM,OAAO,YAAY,CAAC,UAAkB;AAC1C,YAAM,QAAQ,SAAS,QAAQ;AAC/B,UAAI,UAAU,EAAG;AACjB,YAAM,QAAQ,YAAY,UAAU,QAAQ,SAAS;AACrD,kBAAY,UAAU;AACtB,uBAAiB,IAAI;AAAA,IACvB,GAAG,CAAC,CAAC;AAEL,wBAAoB,KAAK,OAAO;AAAA,MAC9B,WAAW,CAAC,EAAE,MAAM,MAAM;AACxB,YAAI,MAAM,QAAQ,WAAW;AAC3B,eAAK,EAAE;AACP,iBAAO;AAAA,QACT;AACA,YAAI,MAAM,QAAQ,aAAa;AAC7B,eAAK,CAAC;AACN,iBAAO;AAAA,QACT;AACA,YAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,OAAO;AAChD,cAAI,SAAS,QAAQ,WAAW,EAAG,QAAO;AAC1C,qBAAW,YAAY,OAAO;AAC9B,iBAAO;AAAA,QACT;AACA,eAAO;AAAA,MACT;AAAA,IACF,EAAE;AAEF,QAAI,MAAM,WAAW,GAAG;AACtB,aAAO,gBAAAG,KAAC,SAAI,WAAU,gCAAgC,sBAAW;AAAA,IACnE;AAEA,WACE,gBAAAA,KAAC,SAAI,WAAU,cAAa,MAAK,WAC9B,gBAAM,IAAI,CAAC,MAAM,UAChB,gBAAAC;AAAA,MAAC;AAAA;AAAA,QAEC,MAAK;AAAA,QACL,MAAK;AAAA,QACL,iBAAe,UAAU;AAAA,QACzB,WACE,UAAU,gBACN,8CACA;AAAA,QAIN,aAAa,CAAC,UAAU;AACtB,gBAAM,eAAe;AACrB,qBAAW,KAAK;AAAA,QAClB;AAAA,QACA,cAAc,MAAM;AAClB,sBAAY,UAAU;AACtB,2BAAiB,KAAK;AAAA,QACxB;AAAA,QAEC;AAAA,eAAK,QACJ,gBAAAD,KAAC,UAAK,WAAU,oBAAmB,eAAW,MAC3C,eAAK,MACR;AAAA,UAEF,gBAAAC,MAAC,UAAK,WAAU,oBACd;AAAA,4BAAAD,KAAC,UAAK,WAAU,qBAAqB,eAAK,OAAM;AAAA,YAC/C,KAAK,eACJ,gBAAAA,KAAC,UAAK,WAAU,2BAA2B,eAAK,aAAY;AAAA,aAEhE;AAAA,UACC,KAAK,YACJ,gBAAAA,KAAC,SAAI,WAAU,wBAAwB,eAAK,UAAS;AAAA;AAAA;AAAA,MAhClD,KAAK;AAAA,IAkCZ,CACD,GACH;AAAA,EAEJ;AACF;;;ACxHA,SAAS,iBAAiB;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,iBAAiB;AAC1B,OAAO,gBAAkE;AAgBzE,IAAM,SAAS;AAER,IAAM,gBAAgB,UAAU,OAA6B;AAAA,EAClE,MAAM;AAAA,EAEN,aAAa;AACX,WAAO;AAAA,MACL,OAAO,MAAM,CAAC;AAAA,MACd,MAAM;AAAA,MACN,WAAW,IAAI,UAAU,iBAAiB;AAAA,IAC5C;AAAA,EACF;AAAA,EAEA,wBAAwB;AACtB,UAAM,EAAE,OAAO,MAAM,YAAY,UAAU,IAAI,KAAK;AAEpD,UAAM,aAA2D;AAAA,MAC/D;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,OAAO,CAAC,EAAE,MAAM,MAAM,iBAAiB,MAAM,GAAG,KAAK;AAAA,MACrD,QAAQ,MAAM;AACZ,YAAI,YAAmE;AACvE,YAAI,YAAmC;AAEvC,cAAM,QAAQ,CAAC,eAAyD;AACtE,cAAI,CAAC,aAAa,CAAC,WAAY;AAC/B,gBAAM,OAAO,WAAW;AACxB,cAAI,CAAC,KAAM;AACX,oBAAU,MAAM,MAAM,GAAG,KAAK,SAAS,MAAM;AAC7C,oBAAU,MAAM,OAAO,GAAG,KAAK,IAAI;AAAA,QACrC;AAEA,cAAM,QAAQ,MAAM;AAClB,qBAAW,OAAO;AAClB,sBAAY;AACZ,qBAAW,QAAQ;AACnB,sBAAY;AAAA,QACd;AAEA,eAAO;AAAA,UACL,SAAS,CAAC,UAAU;AAClB,wBAAY,IAAI,cAAc,WAAW;AAAA,cACvC,OAAO,EAAE,GAAG,OAAO,WAAW;AAAA,cAC9B,QAAQ,MAAM;AAAA,YAChB,CAAC;AACD,wBAAY,SAAS,cAAc,KAAK;AACxC,sBAAU,YAAY;AACtB,sBAAU,YAAY,UAAU,OAAO;AACvC,qBAAS,KAAK,YAAY,SAAS;AACnC,kBAAM,MAAM,UAAU;AAAA,UACxB;AAAA,UACA,UAAU,CAAC,UAAU;AACnB,uBAAW,YAAY,EAAE,GAAG,OAAO,WAAW,CAAC;AAC/C,kBAAM,MAAM,UAAU;AAAA,UACxB;AAAA,UACA,WAAW,CAAC,UAAU;AACpB,gBAAI,MAAM,MAAM,QAAQ,UAAU;AAChC,oBAAM;AACN,qBAAO;AAAA,YACT;AACA,mBAAO,WAAW,KAAK,UAAU,KAAK,KAAK;AAAA,UAC7C;AAAA,UACA,QAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,MACL,WAAW;AAAA,QACT,QAAQ,KAAK;AAAA,QACb,GAAG;AAAA,QACH,SAAS,CAAC,EAAE,QAAQ,OAAO,MAAM,MAAM,MAAM,QAAQ,EAAE,QAAQ,MAAM,CAAC;AAAA,MACxE,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;;;AC/FD,SAAS,aAAAG,kBAAiB;AAC1B,SAAS,QAAQ,aAAAC,kBAAiB;AAClC,SAAS,YAAY,qBAAqB;AAkBnC,IAAM,uBAAuB,IAAIA,WAAyB,yBAAyB;AAWnF,IAAM,oBAAoBD,WAAU,OAA8C;AAAA,EACvF,MAAM;AAAA,EAEN,aAAa;AACX,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AAAA,EAEA,wBAAwB;AACtB,UAAM,UAAU,KAAK;AAErB,WAAO;AAAA,MACL,IAAI,OAAsB;AAAA,QACxB,KAAK;AAAA,QAEL,OAAO;AAAA,UACL,MAAM,MAAM,cAAc;AAAA,UAC1B,MAAM,IAAI,KAAK;AACb,kBAAM,MAAM,GAAG,QAAQ,oBAAoB;AAC3C,gBAAI,IAAK,QAAO,MAAM,GAAG,KAAK,KAAK,QAAQ,OAAO;AAGlD,mBAAO,IAAI,IAAI,GAAG,SAAS,GAAG,GAAG;AAAA,UACnC;AAAA,QACF;AAAA,QAEA,OAAO;AAAA,UACL,YAAY,OAAO;AACjB,mBAAO,qBAAqB,SAAS,KAAK,KAAK,cAAc;AAAA,UAC/D;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF,CAAC;AAED,SAAS,MACP,KACA,aACA,SACe;AACf,QAAM,cAA4B,CAAC;AAEnC,aAAW,KAAK,aAAa;AAC3B,QAAI,EAAE,OAAO,KAAK,EAAE,KAAK,IAAI,QAAQ,QAAQ,EAAE,QAAQ,EAAE,GAAI;AAE7D,gBAAY;AAAA,MACV,WAAW,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,uBAAuB,CAAC;AAAA,MACjE,WAAW,OAAO,EAAE,IAAI,MAAM,eAAe,GAAG,OAAO,GAAG;AAAA;AAAA;AAAA,QAGxD,MAAM;AAAA,QACN,iBAAiB;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,cAAc,OAAO,KAAK,WAAW;AAC9C;AAEA,SAAS,eAAe,GAAqB,SAAgD;AAC3F,QAAM,OAAO,SAAS,cAAc,MAAM;AAC1C,OAAK,YAAY;AACjB,OAAK,aAAa,mBAAmB,OAAO;AAE5C,QAAME,QAAO,SAAS,cAAc,MAAM;AAC1C,EAAAA,MAAK,YAAY;AACjB,EAAAA,MAAK,cAAc,EAAE;AACrB,OAAK,YAAYA,KAAI;AAErB,MAAI,SAAS;AACX,SAAK,YAAY,OAAO,UAAU,QAAQ,aAAa,UAAK,MAAM,QAAQ,SAAS,EAAE,EAAE,CAAC,CAAC;AACzF,SAAK,YAAY,OAAO,UAAU,QAAQ,aAAa,UAAK,MAAM,QAAQ,SAAS,EAAE,EAAE,CAAC,CAAC;AAAA,EAC3F;AACA,SAAO;AACT;AAEA,SAAS,OAAO,MAAc,OAAe,OAAe,SAAkC;AAC5F,QAAM,KAAK,SAAS,cAAc,QAAQ;AAC1C,KAAG,OAAO;AACV,KAAG,YAAY,oDAAoD,IAAI;AACvE,KAAG,aAAa,cAAc,KAAK;AACnC,KAAG,cAAc;AAEjB,KAAG,iBAAiB,aAAa,CAAC,MAAM,EAAE,eAAe,CAAC;AAC1D,KAAG,iBAAiB,SAAS,CAAC,MAAM;AAClC,MAAE,eAAe;AACjB,YAAQ;AAAA,EACV,CAAC;AACD,SAAO;AACT;;;AC5GA,IAAM,OAAO;AASN,SAAS,cAAcC,OAAc,MAAc,IAAuB;AAC/E,MAAI,QAAQ,GAAI,QAAO,EAAE,MAAM,GAAG;AAElC,MAAI,QAAQ;AACZ,SAAO,QAAQ,KAAK,KAAK,KAAKA,MAAK,QAAQ,CAAC,CAAC,KAAK,KAAK,KAAKA,MAAK,KAAK,CAAC,EAAG;AAE1E,MAAI,MAAM;AACV,SAAO,MAAMA,MAAK,UAAU,KAAK,KAAKA,MAAK,GAAG,CAAC,KAAK,KAAK,KAAKA,MAAK,MAAM,CAAC,CAAC,EAAG;AAE9E,SAAO,EAAE,MAAM,OAAO,IAAI,IAAI;AAChC;AAWO,SAAS,cAAc,QAAgB,OAA+B;AAC3E,MAAI,OAAO,KAAK,MAAM,MAAM,KAAK,EAAG,QAAO,CAAC;AAE5C,QAAM,OAAO,WAAW,MAAM;AAC9B,QAAM,OAAO,WAAW,KAAK;AAE7B,MAAI,KAAK,WAAW,KAAK,UAAU,KAAK,WAAW,GAAG;AACpD,WAAO,CAAC,EAAE,QAAQ,OAAO,KAAK,GAAG,OAAO,MAAM,KAAK,EAAE,CAAC;AAAA,EACxD;AAEA,QAAM,QAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,QAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAG,OAAM,KAAK,EAAE,QAAQ,KAAK,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,CAAC;AAAA,EACzE;AACA,SAAO;AACT;AAEA,SAAS,WAAW,GAAqB;AACvC,SAAO,EACJ,MAAM,SAAS,EACf,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,MAAM,EAAE;AAC3B;;;AChDO,SAAS,cAAc,KAAsB;AAClD,SAAO,OAAO,IAAI,WAAW,CAAC,CAAC,EAC5B,OAAO,CAAC,MAAM,MAAM,EAAE,EACtB,KAAK,MAAM;AAChB;AAEA,SAAS,OAAO,OAA6B;AAC3C,SAAO,MAAM,IAAI,KAAK;AACxB;AAEA,SAAS,MAAM,MAAwB;AACrC,UAAQ,KAAK,MAAM;AAAA,IACjB,KAAK,WAAW;AACd,YAAM,QAAQ,OAAO,KAAK,OAAO,SAAS,CAAC;AAC3C,aAAO,GAAG,IAAI,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,KAAK,OAAO,CAAC;AAAA,IAC/E;AAAA,IACA,KAAK;AACH,aAAO,OAAO,KAAK,WAAW,CAAC,CAAC,EAC7B,OAAO,CAAC,MAAM,MAAM,EAAE,EACtB;AAAA,QAAI,CAAC,MACJ,EACG,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EACzB,KAAK,IAAI;AAAA,MACd,EACC,KAAK,OAAO;AAAA,IACjB,KAAK,aAAa;AAChB,YAAM,WAAW,OAAO,KAAK,OAAO,YAAY,EAAE;AAClD,aAAO,SAAS,QAAQ;AAAA,EAAK,OAAO,KAAK,OAAO,CAAC;AAAA;AAAA,IACnD;AAAA,IACA,KAAK;AACH,aAAO,UAAU,IAAI,EAAE,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,EAAE,KAAK,IAAI;AAAA,IAC7D,KAAK;AACH,aAAO,UAAU,IAAI,EAClB,IAAI,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,EACpC,KAAK,IAAI;AAAA,IACd,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO,OAAO,KAAK,OAAO;AAAA,EAC9B;AACF;AAEA,SAAS,UAAU,MAA0B;AAC3C,UAAQ,KAAK,WAAW,CAAC,GAAG;AAAA,IAAI,CAAC,SAC/B,OAAO,KAAK,WAAW,CAAC,CAAC,EACtB,OAAO,CAAC,MAAM,MAAM,EAAE,EAEtB,KAAK,MAAM,EACX,MAAM,IAAI,EACV,KAAK,MAAM;AAAA,EAChB;AACF;AAEA,SAAS,OAAO,OAAuC;AACrD,UAAQ,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE,KAAK,EAAE;AACxC;AAEA,SAAS,KAAK,MAAwB;AACpC,MAAI,KAAK,SAAS,YAAa,QAAO;AACtC,MAAI,OAAO,KAAK,SAAS,SAAU,QAAO,OAAO,KAAK,OAAO;AAE7D,MAAI,MAAM,KAAK;AAEf,aAAW,SAAU,KAAK,SAAgC,CAAC,GAAG,MAAM,EAAE,QAAQ,GAAG;AAC/E,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK;AACH,cAAM,KAAK,GAAG;AACd;AAAA,MACF,KAAK;AACH,cAAM,IAAI,GAAG;AACb;AAAA,MACF,KAAK;AACH,cAAM,KAAK,GAAG;AACd;AAAA,MACF,KAAK;AACH,cAAM,KAAK,GAAG;AACd;AAAA,MACF,KAAK;AACH,cAAM,IAAI,GAAG,KAAK,OAAO,KAAK,OAAO,QAAQ,EAAE,CAAC;AAChD;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;","names":["useEffect","useState","useState","useEffect","useEffect","useState","read","text","jsx","jsxs","useEffect","useRef","useState","jsx","jsxs","SlashMenu","Extension","PluginKey","text","text"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lalternative/editor",
3
- "version": "0.8.3",
3
+ "version": "0.8.5",
4
4
  "description": "Shared TipTap editor for L'Alternative apps: rich text, format menu, selection toolbar and inline AI revisions",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",