fix for formatting menu on waypoint click
7 files changed,  +111, -96
M client/lib/es6/src/App.res.mjs
+3, -1
 1@@ -128,7 +128,9 @@ function App(props) {
 2             JsxRuntime.jsx(FloatingMenu.make, {
 3               doc: doc
 4             }),
 5-            JsxRuntime.jsx(FormattingMenu.make, {})
 6+            JsxRuntime.jsx(FormattingMenu.make, {
 7+              doc: doc
 8+            })
 9           ],
10           disabled: Primitive_option.some(minimized),
11           hidden: Primitive_option.some(minimized),
M client/lib/es6/src/ui/FormattingMenu.res.mjs
+47, -56
  1@@ -1,63 +1,16 @@
  2 // Generated by ReScript, PLEASE EDIT WITH CARE
  3 
  4+import * as State from "../State.res.mjs";
  5 import * as LucidePreact from "lucide-preact";
  6 import * as Signals from "@preact/signals";
  7+import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
  8 import * as JsxRuntime from "preact/jsx-runtime";
  9 import * as Menus from "@tiptap/react/menus";
 10 
 11-let state = {
 12-  editor: null,
 13-  range: null
 14-};
 15-
 16-function setEditor(e, from, to) {
 17-  state.editor = e;
 18-  state.range = {
 19-    from: from,
 20-    to: to
 21-  };
 22-  if (to > from && e.isFocused) {
 23-    return e.isEditable;
 24-  } else {
 25-    return false;
 26-  }
 27-}
 28-
 29-function toggle(mark) {
 30-  let match = state.editor;
 31-  let match$1 = state.range;
 32-  if (!(match == null) && !(match$1 == null)) {
 33-    match.commands.toggleMark(mark);
 34-    match.commands.setTextSelection(match$1.to);
 35-    return;
 36-  }
 37-}
 38-
 39-function remove() {
 40-  let match = state.editor;
 41-  let match$1 = state.range;
 42-  if (!(match == null) && !(match$1 == null)) {
 43-    match.commands.unsetAllMarks();
 44-    match.commands.setTextSelection(match$1.to);
 45-    return;
 46-  }
 47-}
 48-
 49-function heading(level) {
 50-  let match = state.editor;
 51-  let match$1 = state.range;
 52-  if (!(match == null) && !(match$1 == null)) {
 53-    match.commands.toggleHeading({
 54-      level: level
 55-    });
 56-    match.commands.setTextSelection(match$1.to);
 57-    return;
 58-  }
 59-}
 60-
 61 let submenu = Signals.signal(false);
 62 
 63 function FormattingMenu(props) {
 64+  let doc = props.doc;
 65   let btn = [
 66     "p-1 bg-[var(--color-paper)]",
 67     "hover:bg-[var(--color-bg-2)]",
 68@@ -65,8 +18,51 @@ function FormattingMenu(props) {
 69     "cursor-pointer",
 70     "text-sm box-border"
 71   ];
 72+  let toggle = mark => {
 73+    let editor = State.Editor.get(doc);
 74+    if (editor === undefined) {
 75+      return;
 76+    }
 77+    let editor$1 = Primitive_option.valFromOption(editor);
 78+    let editorState = editor$1.state;
 79+    let selection = editorState.selection;
 80+    if (!selection.empty) {
 81+      editor$1.commands.toggleMark(mark);
 82+      editor$1.commands.setTextSelection(selection.to);
 83+      return;
 84+    }
 85+  };
 86+  let remove = () => {
 87+    let editor = State.Editor.get(doc);
 88+    if (editor === undefined) {
 89+      return;
 90+    }
 91+    let editor$1 = Primitive_option.valFromOption(editor);
 92+    let editorState = editor$1.state;
 93+    let selection = editorState.selection;
 94+    if (!selection.empty) {
 95+      editor$1.commands.unsetAllMarks();
 96+      editor$1.commands.setTextSelection(selection.to);
 97+      return;
 98+    }
 99+  };
100+  let heading = level => {
101+    let editor = State.Editor.get(doc);
102+    if (editor === undefined) {
103+      return;
104+    }
105+    let editor$1 = Primitive_option.valFromOption(editor);
106+    let editorState = editor$1.state;
107+    let selection = editorState.selection;
108+    if (!selection.empty) {
109+      editor$1.commands.toggleHeading({
110+        level: level
111+      });
112+      editor$1.commands.setTextSelection(selection.to);
113+      return;
114+    }
115+  };
116   return JsxRuntime.jsxs(Menus.BubbleMenu, {
117-    shouldShow: evt => setEditor(evt.editor, evt.from, evt.to),
118     className: "p-[2px] ring-2 ring-[var(--color-blue)] rounded-sm cursor-pointer box-border",
119     children: [
120       JsxRuntime.jsx("button", {
121@@ -156,11 +152,6 @@ function FormattingMenu(props) {
122 let make = FormattingMenu;
123 
124 export {
125-  state,
126-  setEditor,
127-  toggle,
128-  remove,
129-  heading,
130   submenu,
131   make,
132 }
M client/lib/es6/src/ui/Summary.res.mjs
+1, -1
1@@ -233,7 +233,7 @@ function Summary(props) {
2       ],
3       className: "flex flex-row justify-between"
4     }),
5-    className: Preact.cls("fixed box-border bottom-0 left-0 w-full max-w-(--sheet-width) rounded-sm shadow-xl", {
6+    className: Preact.cls("fixed box-border bottom-0 left-0 w-[100cqw] rounded-sm shadow-xl", {
7       "border-t-2 border-gray-300": !Panel.isMinimized.value
8     }),
9     id: `summary-` + doc
M client/src/App.res
+1, -1
1@@ -100,7 +100,7 @@ let make = (~doc: string) => {
2       <Editor doc={doc} disabled={minimized} hidden={minimized} className="w-full h-full">
3         <Editor.Content />
4         <FloatingMenu doc={doc} />
5-        <FormattingMenu />
6+        <FormattingMenu doc={doc} />
7       </Editor>
8     </Panel>
9   </>
M client/src/ffi/Tiptap.res
+13, -0
 1@@ -91,6 +91,19 @@ external isFocused: t => bool = "isFocused"
 2 @get
 3 external isDestroyed: t => bool = "isDestroyed"
 4 
 5+// Access editor state and current selection
 6+// Used by formatting actions that need to read the live selection range
 7+type selection = {
 8+  from: int,
 9+  to: int,
10+  empty: bool,
11+}
12+
13+type editorState = {selection: selection}
14+
15+@get
16+external state: t => editorState = "state"
17+
18 // Focus and blur commands are in the Commands module
19 
20 // Set the editable state
M client/src/ui/FormattingMenu.res
+45, -36
 1@@ -1,42 +1,9 @@
 2-type t = {mutable editor: nullable<Editor.t>, mutable range: nullable<{from: int, to: int}>}
 3-let state = {editor: Nullable.null, range: Nullable.null}
 4-let setEditor = (e: Editor.t, from: int, to: int) => {
 5-  state.editor = Nullable.make(e)
 6-  state.range = Nullable.make({from, to})
 7-  to > from && e->Tiptap.isFocused && e->Tiptap.isEditable
 8-}
 9-let toggle = (mark: string) => {
10-  switch (state.editor->Nullable.toOption, state.range->Nullable.toOption) {
11-  | (Some(e), Some(range)) => {
12-      e->Tiptap.Commands.toggleMark(mark)
13-      e->Tiptap.Commands.setTextSelectionToPosition(range.to)
14-    }
15-  | (_, _) => ()
16-  }
17-}
18-let remove = () => {
19-  switch (state.editor->Nullable.toOption, state.range->Nullable.toOption) {
20-  | (Some(e), Some(range)) => {
21-      e->Tiptap.Commands.unsetAllMarks
22-      e->Tiptap.Commands.setTextSelectionToPosition(range.to)
23-    }
24-  | (_, _) => ()
25-  }
26-}
27-let heading = (level: int) => {
28-  switch (state.editor->Nullable.toOption, state.range->Nullable.toOption) {
29-  | (Some(e), Some(range)) => {
30-      e->Tiptap.Commands.toggleHeading({"level": level})
31-      e->Tiptap.Commands.setTextSelectionToPosition(range.to)
32-    }
33-  | (_, _) => ()
34-  }
35-}
36+open Tiptap
37 
38 let submenu = Preact.Signal.make(false)
39 
40 @jsx.component
41-let make = () => {
42+let make = (~doc: string) => {
43   let btn = [
44     "p-1 bg-[var(--color-paper)]",
45     "hover:bg-[var(--color-bg-2)]",
46@@ -44,8 +11,50 @@ let make = () => {
47     "cursor-pointer",
48     "text-sm box-border",
49   ]
50+
51+  let toggle = (mark: string) => {
52+    switch State.Editor.get(doc) {
53+    | Some(editor) => {
54+        let editorState = editor->Tiptap.state
55+        let selection = editorState.selection
56+        if !selection.empty {
57+          editor->Tiptap.Commands.toggleMark(mark)
58+          editor->Tiptap.Commands.setTextSelectionToPosition(selection.to)
59+        }
60+      }
61+    | None => ()
62+    }
63+  }
64+
65+  let remove = () => {
66+    switch State.Editor.get(doc) {
67+    | Some(editor) => {
68+        let editorState = editor->Tiptap.state
69+        let selection = editorState.selection
70+        if !selection.empty {
71+          editor->Tiptap.Commands.unsetAllMarks
72+          editor->Tiptap.Commands.setTextSelectionToPosition(selection.to)
73+        }
74+      }
75+    | None => ()
76+    }
77+  }
78+
79+  let heading = (level: int) => {
80+    switch State.Editor.get(doc) {
81+    | Some(editor) => {
82+        let editorState = editor->Tiptap.state
83+        let selection = editorState.selection
84+        if !selection.empty {
85+          editor->Tiptap.Commands.toggleHeading({"level": level})
86+          editor->Tiptap.Commands.setTextSelectionToPosition(selection.to)
87+        }
88+      }
89+    | None => ()
90+    }
91+  }
92+
93   <Editor.BubbleMenu
94-    shouldShow={evt => setEditor(evt.editor, evt.from, evt.to)}
95     className="p-[2px] ring-2 ring-[var(--color-blue)] rounded-sm cursor-pointer box-border"
96   >
97     <button
M client/src/ui/Summary.res
+1, -1
1@@ -67,7 +67,7 @@ let make = (~doc: string) => {
2   <div
3     id={`summary-${doc}`}
4     className={Preact.cls(
5-      "fixed box-border bottom-0 left-0 w-full max-w-(--sheet-width) rounded-sm shadow-xl",
6+      "fixed box-border bottom-0 left-0 w-[100cqw] rounded-sm shadow-xl",
7       dict{"border-t-2 border-gray-300": !(Panel.isMinimized->Preact.Signal.get)},
8     )}
9   >