@xenide-io/the-old-ui-theme 0.9.1 → 0.9.2

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.
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  suiteThemeBootScript
3
- } from "./chunk-BS7BZI5W.mjs";
4
- import "./chunk-IULORI3A.mjs";
3
+ } from "./chunk-VA343JBR.mjs";
5
4
  import "./chunk-FWCSY2DS.mjs";
6
5
  export {
7
6
  suiteThemeBootScript
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenide-io/the-old-ui-theme",
3
- "version": "0.9.1",
3
+ "version": "0.9.2",
4
4
  "description": "Props-driven React components and theme tokens inspired by classic interface design — optimized for Next.js apps and internal tools.",
5
5
  "author": "Xenide",
6
6
  "license": "MIT",
@@ -128,11 +128,11 @@
128
128
  "access": "public"
129
129
  },
130
130
  "dependencies": {
131
- "@blocknote/core": "0.52.1",
132
- "@blocknote/react": "0.52.1",
133
131
  "@radix-ui/react-dialog": "^1.1.23",
134
132
  "@radix-ui/react-dropdown-menu": "^2.1.24",
135
133
  "clsx": "^2.1.1",
134
+ "react-markdown": "^10.1.0",
135
+ "remark-gfm": "^4.0.1",
136
136
  "tailwind-merge": "^3.6.0"
137
137
  }
138
138
  }
@@ -666,28 +666,79 @@ button:hover > .ph-card {
666
666
  }
667
667
 
668
668
  /* ── Ask AI answer content ───────────────────────────────────────────── */
669
- .suite-ai-blocknote .bn-editor {
670
- min-height: 0;
669
+ .suite-ai-markdown {
671
670
  padding: 0;
672
- background: transparent;
673
671
  color: var(--ph-text-primary);
674
672
  font-family: inherit;
675
673
  font-size: 0.875rem;
674
+ line-height: 1.5;
676
675
  overflow-wrap: anywhere;
677
676
  }
678
- .suite-ai-blocknote .bn-block-content {
679
- padding-block: 2px;
680
- }
681
- .suite-ai-blocknote [data-content-type="heading"] {
682
- padding-top: 8px;
683
- }
684
- .suite-ai-blocknote [data-content-type="codeBlock"] {
677
+ .suite-ai-markdown > :first-child {
678
+ margin-top: 0;
679
+ }
680
+ .suite-ai-markdown > :last-child {
681
+ margin-bottom: 0;
682
+ }
683
+ .suite-ai-markdown h1,
684
+ .suite-ai-markdown h2,
685
+ .suite-ai-markdown h3,
686
+ .suite-ai-markdown h4 {
687
+ margin: 0.75rem 0 0.25rem;
688
+ font-weight: 600;
689
+ line-height: 1.25;
690
+ }
691
+ .suite-ai-markdown h1 { font-size: 1.25rem; }
692
+ .suite-ai-markdown h2 { font-size: 1.125rem; }
693
+ .suite-ai-markdown h3,
694
+ .suite-ai-markdown h4 { font-size: 1rem; }
695
+ .suite-ai-markdown p,
696
+ .suite-ai-markdown ul,
697
+ .suite-ai-markdown ol,
698
+ .suite-ai-markdown blockquote,
699
+ .suite-ai-markdown pre,
700
+ .suite-ai-markdown table {
701
+ margin: 0.5rem 0;
702
+ }
703
+ .suite-ai-markdown ul,
704
+ .suite-ai-markdown ol {
705
+ padding-left: 1.25rem;
706
+ }
707
+ .suite-ai-markdown ul { list-style: disc; }
708
+ .suite-ai-markdown ol { list-style: decimal; }
709
+ .suite-ai-markdown li + li { margin-top: 0.125rem; }
710
+ .suite-ai-markdown pre {
711
+ padding: 0.5rem;
685
712
  overflow-x: auto;
686
713
  border: 1px solid var(--ph-border);
687
714
  border-radius: 6px;
688
715
  background: var(--ph-surface);
689
716
  }
690
- .suite-ai-blocknote a {
717
+ .suite-ai-markdown code {
718
+ font-size: 0.8125rem;
719
+ }
720
+ .suite-ai-markdown :not(pre) > code {
721
+ padding: 0.1rem 0.25rem;
722
+ border-radius: 4px;
723
+ background: var(--ph-surface);
724
+ }
725
+ .suite-ai-markdown blockquote {
726
+ padding-left: 0.75rem;
727
+ border-left: 2px solid var(--ph-border);
728
+ }
729
+ .suite-ai-markdown table {
730
+ display: block;
731
+ max-width: 100%;
732
+ overflow-x: auto;
733
+ border-collapse: collapse;
734
+ }
735
+ .suite-ai-markdown th,
736
+ .suite-ai-markdown td {
737
+ padding: 0.375rem 0.5rem;
738
+ border: 1px solid var(--ph-border);
739
+ text-align: left;
740
+ }
741
+ .suite-ai-markdown a {
691
742
  color: var(--ph-brand);
692
743
  text-decoration: underline;
693
744
  text-underline-offset: 2px;
@@ -1,7 +1,7 @@
1
- import { cleanup, render, screen, waitFor } from "@testing-library/react";
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
2
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
3
3
 
4
- import SuiteAiBlockNoteMessage from "./ai-message-blocknote";
4
+ import SuiteAiMarkdownMessage from "./ai-message-markdown";
5
5
  import { SuiteThemeProvider } from "./theme-provider";
6
6
 
7
7
  const themeConfig = {
@@ -11,7 +11,7 @@ const themeConfig = {
11
11
  fallbackTheme: "light" as const,
12
12
  };
13
13
 
14
- describe("SuiteAiBlockNoteMessage", () => {
14
+ describe("SuiteAiMarkdownMessage", () => {
15
15
  beforeEach(() => {
16
16
  vi.stubGlobal("localStorage", {
17
17
  getItem: () => "light",
@@ -23,18 +23,19 @@ describe("SuiteAiBlockNoteMessage", () => {
23
23
  vi.unstubAllGlobals();
24
24
  });
25
25
 
26
- it("renders Markdown as restricted read-only BlockNote content", async () => {
26
+ it("renders safe, semantic Markdown", () => {
27
27
  const { container } = render(
28
28
  <SuiteThemeProvider config={themeConfig}>
29
- <SuiteAiBlockNoteMessage markdown={"## Summary\n\n- First item"} />
29
+ <SuiteAiMarkdownMessage
30
+ markdown={"## Summary\n\n- First item\n\n![Ignored](https://example.test/image.png)"}
31
+ />
30
32
  </SuiteThemeProvider>,
31
33
  );
32
34
 
33
- await waitFor(() =>
34
- expect(screen.getByText("Summary")).toBeInTheDocument(),
35
- );
35
+ expect(screen.getByRole("heading", { name: "Summary" })).toBeInTheDocument();
36
36
  expect(screen.getByText("First item")).toBeInTheDocument();
37
- expect(container.querySelector('[contenteditable="false"]')).toBeTruthy();
37
+ expect(container.querySelector("ul")).toBeTruthy();
38
+ expect(container.querySelector('[contenteditable]')).toBeNull();
38
39
  expect(container.querySelector("img")).toBeNull();
39
40
  });
40
41
  });
@@ -0,0 +1,21 @@
1
+ "use client";
2
+
3
+ import ReactMarkdown from "react-markdown";
4
+ import remarkGfm from "remark-gfm";
5
+
6
+ export default function SuiteAiMarkdownMessage({
7
+ markdown,
8
+ }: {
9
+ markdown: string;
10
+ }) {
11
+ return (
12
+ <div className="suite-ai-markdown">
13
+ <ReactMarkdown
14
+ remarkPlugins={[remarkGfm]}
15
+ components={{ img: () => null }}
16
+ >
17
+ {markdown}
18
+ </ReactMarkdown>
19
+ </div>
20
+ );
21
+ }
@@ -28,7 +28,7 @@ import {
28
28
  type SuiteAppSlug,
29
29
  } from "../lib/apps";
30
30
 
31
- const SuiteAiBlockNoteMessage = lazy(() => import("./ai-message-blocknote"));
31
+ const SuiteAiMarkdownMessage = lazy(() => import("./ai-message-markdown"));
32
32
 
33
33
  export const SUITE_OPEN_ASK_AI_EVENT = "shellstack:open-ask-ai";
34
34
  export const SUITE_ASK_AI_OPEN_KEY = "shellstack:shelly-open";
@@ -542,7 +542,7 @@ export function SuiteAiPanel({
542
542
  </div>
543
543
  }
544
544
  >
545
- <SuiteAiBlockNoteMessage markdown={message.content} />
545
+ <SuiteAiMarkdownMessage markdown={message.content} />
546
546
  </Suspense>
547
547
  </div>
548
548
  {(message.actions ?? []).map((action) => (
@@ -48,7 +48,7 @@ export function CommandPaletteHost({
48
48
  useEffect(() => {
49
49
  const onKey = (e: KeyboardEvent) => {
50
50
  if (!((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k")) return;
51
- // Leave editors (BlockNote uses Cmd/Ctrl+K for links) alone unless the palette is open.
51
+ // Leave editable fields alone unless the palette is open.
52
52
  if (!open && isEditableTarget()) return;
53
53
  e.preventDefault();
54
54
  setOpen((o) => !o);
@@ -63,7 +63,7 @@ export function SuiteAppLayout({
63
63
  style={{ width: `${sidebarWidth}px` }}
64
64
  data-collapsed={collapsed ? "true" : "false"}
65
65
  >
66
- {/* ponytail: isolate so Ask AI / BlockNote z-index cannot cover the handle */}
66
+ {/* ponytail: isolate so Ask AI cannot cover the resize handle */}
67
67
  <div className="relative z-0 h-full min-h-0 isolate">{sidebar}</div>
68
68
  {onStartResize ? (
69
69
  <div
@@ -1,69 +0,0 @@
1
- "use client";
2
- import {
3
- useSuiteTheme
4
- } from "./chunk-APKRZQRO.mjs";
5
- import "./chunk-IULORI3A.mjs";
6
- import "./chunk-FWCSY2DS.mjs";
7
-
8
- // src/suite/components/ai-message-blocknote.tsx
9
- import { useEffect, useLayoutEffect } from "react";
10
- import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
11
- import { BlockNoteViewRaw, useCreateBlockNote } from "@blocknote/react";
12
- import "@blocknote/react/style.css";
13
- import { jsx } from "react/jsx-runtime";
14
- var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
15
- var assistantSchema = BlockNoteSchema.create({
16
- blockSpecs: {
17
- paragraph: defaultBlockSpecs.paragraph,
18
- heading: defaultBlockSpecs.heading,
19
- bulletListItem: defaultBlockSpecs.bulletListItem,
20
- numberedListItem: defaultBlockSpecs.numberedListItem,
21
- checkListItem: defaultBlockSpecs.checkListItem,
22
- codeBlock: defaultBlockSpecs.codeBlock,
23
- quote: defaultBlockSpecs.quote,
24
- table: defaultBlockSpecs.table,
25
- divider: defaultBlockSpecs.divider
26
- }
27
- });
28
- function SuiteAiBlockNoteMessage({
29
- markdown
30
- }) {
31
- const { resolvedTheme } = useSuiteTheme();
32
- const editor = useCreateBlockNote({ schema: assistantSchema });
33
- useIsomorphicLayoutEffect(() => {
34
- try {
35
- const blocks = editor.tryParseMarkdownToBlocks(markdown);
36
- editor.replaceBlocks(
37
- editor.document,
38
- blocks.length > 0 ? blocks : [{ type: "paragraph", content: markdown }]
39
- );
40
- } catch (e) {
41
- editor.replaceBlocks(editor.document, [
42
- { type: "paragraph", content: markdown }
43
- ]);
44
- }
45
- }, [editor, markdown]);
46
- useEffect(() => {
47
- const el = editor.domElement;
48
- el == null ? void 0 : el.setAttribute("aria-label", "Assistant message");
49
- }, [editor]);
50
- return /* @__PURE__ */ jsx(
51
- BlockNoteViewRaw,
52
- {
53
- editor,
54
- editable: false,
55
- theme: resolvedTheme,
56
- className: "suite-ai-blocknote",
57
- formattingToolbar: false,
58
- sideMenu: false,
59
- slashMenu: false,
60
- linkToolbar: false,
61
- filePanel: false,
62
- tableHandles: false,
63
- comments: false
64
- }
65
- );
66
- }
67
- export {
68
- SuiteAiBlockNoteMessage as default
69
- };
@@ -1,251 +0,0 @@
1
- import {
2
- SUITE_THEME_CHANNEL,
3
- SUITE_THEME_COOKIE
4
- } from "./chunk-IULORI3A.mjs";
5
- import {
6
- __spreadProps,
7
- __spreadValues
8
- } from "./chunk-FWCSY2DS.mjs";
9
-
10
- // src/suite/components/theme-provider.tsx
11
- import {
12
- createContext,
13
- useCallback,
14
- useContext,
15
- useEffect,
16
- useMemo,
17
- useRef,
18
- useState
19
- } from "react";
20
-
21
- // src/suite/lib/cookies.ts
22
- function suiteCookieDomainAttr() {
23
- var _a;
24
- const host = window.location.hostname;
25
- if (host === "localhost" || /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host)) {
26
- return "";
27
- }
28
- if (host.endsWith(".localhost")) {
29
- return "; Domain=.localhost";
30
- }
31
- const known = /* @__PURE__ */ new Set([
32
- "tides",
33
- "kraken",
34
- "turtletime",
35
- "time",
36
- "shelly",
37
- "app",
38
- "shellstack",
39
- "portal"
40
- ]);
41
- const parts = host.split(".");
42
- if (parts.length >= 3 && known.has((_a = parts[0]) != null ? _a : "")) {
43
- return `; Domain=.${parts.slice(1).join(".")}`;
44
- }
45
- if (parts.length >= 2) return `; Domain=.${parts.slice(1).join(".")}`;
46
- return "";
47
- }
48
- function readCookie(name) {
49
- const prefix = `${name}=`;
50
- for (const part of document.cookie.split(";")) {
51
- const trimmed = part.trim();
52
- if (trimmed.startsWith(prefix)) {
53
- return decodeURIComponent(trimmed.slice(prefix.length));
54
- }
55
- }
56
- return null;
57
- }
58
- function writeCookie(name, value) {
59
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
60
- document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; Max-Age=31536000; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
61
- }
62
- function clearCookie(name) {
63
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
64
- document.cookie = `${name}=; Path=/; Max-Age=0; SameSite=Lax${suiteCookieDomainAttr()}${secure}`;
65
- }
66
-
67
- // src/suite/components/theme-provider.tsx
68
- import { jsx } from "react/jsx-runtime";
69
- var SuiteThemeContext = createContext(null);
70
- var suiteThemeChannel = null;
71
- function broadcastTheme(theme) {
72
- if (typeof BroadcastChannel === "undefined") return;
73
- try {
74
- suiteThemeChannel != null ? suiteThemeChannel : suiteThemeChannel = new BroadcastChannel(SUITE_THEME_CHANNEL);
75
- suiteThemeChannel.postMessage(theme);
76
- } catch (e) {
77
- }
78
- }
79
- function readSharedTheme() {
80
- if (typeof window === "undefined") return null;
81
- try {
82
- const value = readCookie(SUITE_THEME_COOKIE);
83
- return value === "light" || value === "dark" || value === "system" ? value : null;
84
- } catch (e) {
85
- return null;
86
- }
87
- }
88
- function writeSharedTheme(theme) {
89
- try {
90
- writeCookie(SUITE_THEME_COOKIE, theme);
91
- } catch (e) {
92
- }
93
- }
94
- function systemTheme(fallback) {
95
- if (typeof window === "undefined") return fallback;
96
- return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
97
- }
98
- function resolveTheme(theme, fallback) {
99
- return theme === "system" ? systemTheme(fallback) : theme;
100
- }
101
- function applyTheme(theme, config) {
102
- const resolved = resolveTheme(theme, config.fallbackTheme);
103
- if (typeof document === "undefined") return resolved;
104
- document.documentElement.setAttribute(
105
- "data-theme",
106
- resolved === "dark" ? config.darkThemeId : config.lightThemeId
107
- );
108
- document.documentElement.classList.toggle("dark", resolved === "dark");
109
- if (config.themeColorLight && config.themeColorDark) {
110
- const meta = document.getElementById(
111
- "theme-color-meta"
112
- );
113
- if (meta) {
114
- meta.content = resolved === "light" ? config.themeColorLight : config.themeColorDark;
115
- }
116
- }
117
- return resolved;
118
- }
119
- function readStoredTheme(storageKey) {
120
- if (typeof window === "undefined") return "system";
121
- const shared = readSharedTheme();
122
- if (shared) return shared;
123
- try {
124
- const stored = localStorage.getItem(storageKey);
125
- if (stored === "light" || stored === "dark" || stored === "system")
126
- return stored;
127
- } catch (e) {
128
- }
129
- return "system";
130
- }
131
- function SuiteThemeProvider({
132
- config,
133
- children
134
- }) {
135
- const [state, setState] = useState(() => {
136
- const theme = readStoredTheme(config.storageKey);
137
- if (typeof document === "undefined") {
138
- return { theme, resolvedTheme: config.fallbackTheme };
139
- }
140
- const domTheme = document.documentElement.getAttribute("data-theme");
141
- const resolvedTheme = domTheme === config.darkThemeId ? "dark" : domTheme === config.lightThemeId ? "light" : resolveTheme(theme, config.fallbackTheme);
142
- return { theme, resolvedTheme };
143
- });
144
- const themeRef = useRef(state.theme);
145
- themeRef.current = state.theme;
146
- useEffect(() => {
147
- applyTheme(state.theme, config);
148
- }, [state.theme, config]);
149
- useEffect(() => {
150
- if (readSharedTheme() == null) writeSharedTheme(state.theme);
151
- }, []);
152
- useEffect(() => {
153
- if (state.theme !== "system") return;
154
- const media = window.matchMedia("(prefers-color-scheme: dark)");
155
- const update = () => {
156
- const resolved = applyTheme("system", config);
157
- setState((current) => __spreadProps(__spreadValues({}, current), { resolvedTheme: resolved }));
158
- };
159
- media.addEventListener("change", update);
160
- return () => media.removeEventListener("change", update);
161
- }, [state.theme, config]);
162
- useEffect(() => {
163
- let channel = null;
164
- try {
165
- channel = new BroadcastChannel(SUITE_THEME_CHANNEL);
166
- } catch (e) {
167
- channel = null;
168
- }
169
- const adopt = (next) => {
170
- if (next !== "light" && next !== "dark" && next !== "system") return;
171
- if (next === themeRef.current) return;
172
- const resolved = applyTheme(next, config);
173
- try {
174
- localStorage.setItem(config.storageKey, next);
175
- } catch (e) {
176
- }
177
- setState({ theme: next, resolvedTheme: resolved });
178
- };
179
- const readShared = () => adopt(readSharedTheme());
180
- const onChannel = (event) => adopt(event.data);
181
- const onStorage = (event) => {
182
- if (event.key === config.storageKey) adopt(event.newValue);
183
- };
184
- let poll = null;
185
- const startPolling = () => {
186
- if (poll == null) poll = setInterval(readShared, 2e3);
187
- };
188
- const stopPolling = () => {
189
- if (poll != null) {
190
- clearInterval(poll);
191
- poll = null;
192
- }
193
- };
194
- const onVisibility = () => {
195
- if (document.visibilityState === "visible") {
196
- readShared();
197
- startPolling();
198
- } else {
199
- stopPolling();
200
- }
201
- };
202
- channel == null ? void 0 : channel.addEventListener("message", onChannel);
203
- window.addEventListener("storage", onStorage);
204
- window.addEventListener("focus", readShared);
205
- document.addEventListener("visibilitychange", onVisibility);
206
- if (document.visibilityState === "visible") startPolling();
207
- return () => {
208
- channel == null ? void 0 : channel.removeEventListener("message", onChannel);
209
- channel == null ? void 0 : channel.close();
210
- window.removeEventListener("storage", onStorage);
211
- window.removeEventListener("focus", readShared);
212
- document.removeEventListener("visibilitychange", onVisibility);
213
- stopPolling();
214
- };
215
- }, [config]);
216
- const setTheme = useCallback(
217
- (next) => {
218
- const resolved = applyTheme(next, config);
219
- setState({ theme: next, resolvedTheme: resolved });
220
- try {
221
- localStorage.setItem(config.storageKey, next);
222
- } catch (e) {
223
- }
224
- writeSharedTheme(next);
225
- broadcastTheme(next);
226
- },
227
- [config]
228
- );
229
- const toggleTheme = useCallback(() => {
230
- const next = resolveTheme(themeRef.current, config.fallbackTheme) === "dark" ? "light" : "dark";
231
- setTheme(next);
232
- }, [config, setTheme]);
233
- const value = useMemo(
234
- () => ({ theme: state.theme, resolvedTheme: state.resolvedTheme, setTheme, toggleTheme }),
235
- [state.theme, state.resolvedTheme, setTheme, toggleTheme]
236
- );
237
- return /* @__PURE__ */ jsx(SuiteThemeContext.Provider, { value, children });
238
- }
239
- function useSuiteTheme() {
240
- const ctx = useContext(SuiteThemeContext);
241
- if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
242
- return ctx;
243
- }
244
-
245
- export {
246
- readCookie,
247
- writeCookie,
248
- clearCookie,
249
- SuiteThemeProvider,
250
- useSuiteTheme
251
- };
@@ -1,8 +0,0 @@
1
- // src/suite/lib/theme-cookie.ts
2
- var SUITE_THEME_COOKIE = "xenide-suite-theme";
3
- var SUITE_THEME_CHANNEL = "xenide-suite-theme";
4
-
5
- export {
6
- SUITE_THEME_COOKIE,
7
- SUITE_THEME_CHANNEL
8
- };
@@ -1,72 +0,0 @@
1
- "use client";
2
-
3
- import { useEffect, useLayoutEffect } from "react";
4
- import { BlockNoteSchema, defaultBlockSpecs } from "@blocknote/core";
5
- import { BlockNoteViewRaw, useCreateBlockNote } from "@blocknote/react";
6
- import "@blocknote/react/style.css";
7
-
8
- import { useSuiteTheme } from "./theme-provider";
9
-
10
- // Parse the message before first paint so the editor never flashes empty.
11
- const useIsomorphicLayoutEffect =
12
- typeof window !== "undefined" ? useLayoutEffect : useEffect;
13
-
14
- const assistantSchema = BlockNoteSchema.create({
15
- blockSpecs: {
16
- paragraph: defaultBlockSpecs.paragraph,
17
- heading: defaultBlockSpecs.heading,
18
- bulletListItem: defaultBlockSpecs.bulletListItem,
19
- numberedListItem: defaultBlockSpecs.numberedListItem,
20
- checkListItem: defaultBlockSpecs.checkListItem,
21
- codeBlock: defaultBlockSpecs.codeBlock,
22
- quote: defaultBlockSpecs.quote,
23
- table: defaultBlockSpecs.table,
24
- divider: defaultBlockSpecs.divider,
25
- },
26
- });
27
-
28
- export default function SuiteAiBlockNoteMessage({
29
- markdown,
30
- }: {
31
- markdown: string;
32
- }) {
33
- const { resolvedTheme } = useSuiteTheme();
34
- const editor = useCreateBlockNote({ schema: assistantSchema });
35
-
36
- useIsomorphicLayoutEffect(() => {
37
- try {
38
- const blocks = editor.tryParseMarkdownToBlocks(markdown);
39
- editor.replaceBlocks(
40
- editor.document,
41
- blocks.length > 0 ? blocks : [{ type: "paragraph", content: markdown }],
42
- );
43
- } catch {
44
- editor.replaceBlocks(editor.document, [
45
- { type: "paragraph", content: markdown },
46
- ]);
47
- }
48
- }, [editor, markdown]);
49
-
50
- // BlockNote renders the body as a ProseMirror contenteditable (.tiptap);
51
- // give that node an accessible name so axe's aria-input-field-name passes.
52
- useEffect(() => {
53
- const el = (editor as unknown as { domElement?: HTMLElement }).domElement;
54
- el?.setAttribute("aria-label", "Assistant message");
55
- }, [editor]);
56
-
57
- return (
58
- <BlockNoteViewRaw
59
- editor={editor}
60
- editable={false}
61
- theme={resolvedTheme}
62
- className="suite-ai-blocknote"
63
- formattingToolbar={false}
64
- sideMenu={false}
65
- slashMenu={false}
66
- linkToolbar={false}
67
- filePanel={false}
68
- tableHandles={false}
69
- comments={false}
70
- />
71
- );
72
- }