@truedat/core 8.11.4 → 8.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@truedat/core",
3
- "version": "8.11.4",
3
+ "version": "8.11.5",
4
4
  "description": "Truedat Web Core",
5
5
  "sideEffects": [
6
6
  "**/*.css",
@@ -64,12 +64,12 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@apollo/client": "^3.13.8",
67
- "@tiptap/core": "^3.20.0",
68
- "@tiptap/extension-heading": "^3.20.0",
69
- "@tiptap/extension-link": "^3.20.0",
70
- "@tiptap/pm": "^3.20.0",
71
- "@tiptap/react": "^3.20.0",
72
- "@tiptap/starter-kit": "^3.20.0",
67
+ "@tiptap/core": "3.31.3",
68
+ "@tiptap/extension-heading": "3.31.3",
69
+ "@tiptap/extension-link": "3.31.3",
70
+ "@tiptap/pm": "3.31.3",
71
+ "@tiptap/react": "3.31.3",
72
+ "@tiptap/starter-kit": "3.31.3",
73
73
  "@xyflow/react": "^12.6.4",
74
74
  "axios": "^1.15.0",
75
75
  "dompurify": "^3.4.11",
@@ -98,5 +98,5 @@
98
98
  "swr": "^2.3.3",
99
99
  "turndown": "^7.2.2"
100
100
  },
101
- "gitHead": "a80d09c5a9a8f88a4f20dce8a1c79a6db0d281c4"
101
+ "gitHead": "d7c5a489590a23c1b5722dafbd2e2d5902303ed9"
102
102
  }
@@ -1,7 +1,7 @@
1
1
  import { useRef, useEffect, useState, useCallback } from "react";
2
2
  import PropTypes from "prop-types";
3
3
  import { useIntl } from "react-intl";
4
- import { useEditor, EditorContent } from "@tiptap/react";
4
+ import { useEditor, useEditorState, EditorContent } from "@tiptap/react";
5
5
  import StarterKit from "@tiptap/starter-kit";
6
6
  import Link from "@tiptap/extension-link";
7
7
  import Heading from "@tiptap/extension-heading";
@@ -151,7 +151,7 @@ export function MarkdownEditor({
151
151
  const { formatMessage } = useIntl();
152
152
  const [linkModalOpen, setLinkModalOpen] = useState(false);
153
153
  const [linkUrl, setLinkUrl] = useState("");
154
- const [hasSelection, setHasSelection] = useState(false);
154
+ const [isFocused, setIsFocused] = useState(false);
155
155
  const lastEmittedMarkdownRef = useRef(value);
156
156
  const editorRef = useRef(null);
157
157
  const initialContent = markdownToHtml(value);
@@ -161,6 +161,8 @@ export function MarkdownEditor({
161
161
  content: initialContent,
162
162
  editable: !disabled,
163
163
  immediatelyRender: false,
164
+ onFocus: () => setIsFocused(true),
165
+ onBlur: () => setIsFocused(false),
164
166
  editorProps: {
165
167
  handlePaste(view, event) {
166
168
  const editorInstance = editorRef.current;
@@ -216,18 +218,23 @@ export function MarkdownEditor({
216
218
  editor.setEditable(!disabled);
217
219
  }, [disabled, editor]);
218
220
 
219
- useEffect(() => {
220
- if (!editor) return;
221
- const updateSelection = () => {
222
- const { from, to } = editor.state.selection;
223
- setHasSelection(from !== to);
224
- };
225
- updateSelection();
226
- editor.on("selectionUpdate", updateSelection);
227
- return () => {
228
- editor.off("selectionUpdate", updateSelection);
229
- };
230
- }, [editor]);
221
+ const toolbarState = useEditorState({
222
+ editor,
223
+ selector: ({ editor: ed }) => {
224
+ if (!ed) return null;
225
+ return {
226
+ bold: ed.isActive("bold"),
227
+ italic: ed.isActive("italic"),
228
+ underline: ed.isActive("underline"),
229
+ heading2: ed.isActive("heading", { level: 2 }),
230
+ heading3: ed.isActive("heading", { level: 3 }),
231
+ bulletList: ed.isActive("bulletList"),
232
+ orderedList: ed.isActive("orderedList"),
233
+ link: ed.isActive("link"),
234
+ hasSelection: !ed.state.selection.empty,
235
+ };
236
+ },
237
+ });
231
238
 
232
239
  const openLinkModal = useCallback(
233
240
  (url = null) => {
@@ -279,7 +286,7 @@ export function MarkdownEditor({
279
286
  [editor],
280
287
  );
281
288
 
282
- if (!editor) {
289
+ if (!editor || !toolbarState) {
283
290
  return null;
284
291
  }
285
292
 
@@ -292,7 +299,7 @@ export function MarkdownEditor({
292
299
  ) : null}
293
300
  <Menu size="small" attached="top" className="markdown-editor-toolbar">
294
301
  <Menu.Item
295
- active={editor.isActive("bold")}
302
+ active={isFocused && toolbarState.bold}
296
303
  onMouseDown={(e) => {
297
304
  e.preventDefault();
298
305
  editor.chain().focus().toggleBold().run();
@@ -301,7 +308,7 @@ export function MarkdownEditor({
301
308
  <Icon name="bold" />
302
309
  </Menu.Item>
303
310
  <Menu.Item
304
- active={editor.isActive("italic")}
311
+ active={isFocused && toolbarState.italic}
305
312
  onMouseDown={(e) => {
306
313
  e.preventDefault();
307
314
  editor.chain().focus().toggleItalic().run();
@@ -310,7 +317,7 @@ export function MarkdownEditor({
310
317
  <Icon name="italic" />
311
318
  </Menu.Item>
312
319
  <Menu.Item
313
- active={editor.isActive("underline")}
320
+ active={isFocused && toolbarState.underline}
314
321
  onMouseDown={(e) => {
315
322
  e.preventDefault();
316
323
  editor.chain().focus().toggleUnderline().run();
@@ -319,6 +326,7 @@ export function MarkdownEditor({
319
326
  <Icon name="underline" />
320
327
  </Menu.Item>
321
328
  <Menu.Item
329
+ active={isFocused && toolbarState.heading2}
322
330
  onMouseDown={(e) => {
323
331
  e.preventDefault();
324
332
  editor.chain().focus().toggleHeading({ level: 2 }).run();
@@ -327,6 +335,7 @@ export function MarkdownEditor({
327
335
  <Icon name="heading" size="large" />
328
336
  </Menu.Item>
329
337
  <Menu.Item
338
+ active={isFocused && toolbarState.heading3}
330
339
  onMouseDown={(e) => {
331
340
  e.preventDefault();
332
341
  editor.chain().focus().toggleHeading({ level: 3 }).run();
@@ -335,7 +344,7 @@ export function MarkdownEditor({
335
344
  <Icon name="heading" size="small" />
336
345
  </Menu.Item>
337
346
  <Menu.Item
338
- active={editor.isActive("bulletList")}
347
+ active={isFocused && toolbarState.bulletList}
339
348
  onMouseDown={(e) => {
340
349
  e.preventDefault();
341
350
  editor.chain().focus().toggleBulletList().run();
@@ -344,7 +353,7 @@ export function MarkdownEditor({
344
353
  <Icon name="list ul" />
345
354
  </Menu.Item>
346
355
  <Menu.Item
347
- active={editor.isActive("orderedList")}
356
+ active={isFocused && toolbarState.orderedList}
348
357
  onMouseDown={(e) => {
349
358
  e.preventDefault();
350
359
  editor.chain().focus().toggleOrderedList().run();
@@ -353,11 +362,11 @@ export function MarkdownEditor({
353
362
  <Icon name="list ol" />
354
363
  </Menu.Item>
355
364
  <Menu.Item
356
- active={editor.isActive("link")}
357
- disabled={!hasSelection && !editor.isActive("link")}
365
+ active={isFocused && toolbarState.link}
366
+ disabled={!toolbarState.hasSelection && !toolbarState.link}
358
367
  onMouseDown={(e) => {
359
368
  e.preventDefault();
360
- if (hasSelection || editor.isActive("link")) {
369
+ if (toolbarState.hasSelection || toolbarState.link) {
361
370
  openLinkModal();
362
371
  }
363
372
  }}
@@ -374,7 +383,7 @@ export function MarkdownEditor({
374
383
  onAccept={handleLinkAccept}
375
384
  validate={validateLinkUrl}
376
385
  initialValue={linkUrl}
377
- allowEmpty={editor.isActive("link")}
386
+ allowEmpty={toolbarState.link}
378
387
  header={
379
388
  <>
380
389
  <Icon name="linkify" />
@@ -1,4 +1,4 @@
1
- import { waitFor } from "@testing-library/react";
1
+ import { act, fireEvent, waitFor } from "@testing-library/react";
2
2
  import { render } from "@truedat/test/render";
3
3
  import {
4
4
  MarkdownReader,
@@ -165,6 +165,60 @@ describe("<MarkdownReader />", () => {
165
165
  });
166
166
 
167
167
  describe("<MarkdownEditor />", () => {
168
+ it.each([
169
+ ["bold", 0],
170
+ ["italic", 1],
171
+ ["underline", 2],
172
+ ["heading 2", 3],
173
+ ["heading 3", 4],
174
+ ["bullet list", 5],
175
+ ["ordered list", 6],
176
+ ])("updates %s highlighting without typing", async (_, index) => {
177
+ const { container } = render(<MarkdownEditor value="Hello" />);
178
+ await waitFor(() =>
179
+ expect(container.querySelector(".tiptap")).toBeInTheDocument(),
180
+ );
181
+ const button = container.querySelectorAll(".markdown-editor-toolbar .item")[
182
+ index
183
+ ];
184
+ expect(button).not.toHaveClass("active");
185
+ fireEvent.focus(container.querySelector(".tiptap"));
186
+ fireEvent.mouseDown(button);
187
+ expect(button).toHaveClass("active");
188
+ fireEvent.mouseDown(button);
189
+ expect(button).not.toHaveClass("active");
190
+ });
191
+
192
+ it("updates highlighting when the cursor moves between formatted and plain text", async () => {
193
+ const { container } = render(<MarkdownEditor value="**Bold** plain" />);
194
+ await waitFor(() =>
195
+ expect(container.querySelector(".tiptap")).toBeInTheDocument(),
196
+ );
197
+ const editor = container.querySelector(".tiptap").editor;
198
+ const bold = container.querySelector(".markdown-editor-toolbar .item");
199
+ fireEvent.focus(container.querySelector(".tiptap"));
200
+ act(() => editor.commands.setTextSelection(2));
201
+ expect(bold).toHaveClass("active");
202
+ act(() => editor.commands.setTextSelection(8));
203
+ expect(bold).not.toHaveClass("active");
204
+ });
205
+
206
+ it("only highlights initial formatting while the editor is focused", async () => {
207
+ const { container } = render(<MarkdownEditor value="**Bold** text" />);
208
+ await waitFor(() =>
209
+ expect(container.querySelector(".tiptap")).toBeInTheDocument(),
210
+ );
211
+ const editable = container.querySelector(".tiptap");
212
+ const bold = container.querySelector(".markdown-editor-toolbar .item");
213
+ expect(bold).not.toHaveClass("active");
214
+ fireEvent.focus(editable);
215
+ expect(bold).toHaveClass("active");
216
+ fireEvent.blur(editable);
217
+ expect(bold).not.toHaveClass("active");
218
+ fireEvent.focus(editable);
219
+ expect(bold).toHaveClass("active");
220
+ });
221
+
168
222
  it("matches the latest snapshot when editor is ready", async () => {
169
223
  const rendered = render(<MarkdownEditor />);
170
224
  await waitFor(() => {
@@ -114,7 +114,7 @@ exports[`<MarkdownEditor /> matches the latest snapshot with label and value 1`]
114
114
  class="ui small top attached markdown-editor-toolbar menu"
115
115
  >
116
116
  <div
117
- class="active item"
117
+ class="item"
118
118
  >
119
119
  <i
120
120
  aria-hidden="true"