@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.
- package/dist/ai-message-markdown-TSPYON2F.mjs +22 -0
- package/dist/{chunk-BS7BZI5W.mjs → chunk-VA343JBR.mjs} +5 -3
- package/dist/suite.js +385 -452
- package/dist/suite.mjs +612 -385
- package/dist/theme-boot.mjs +1 -2
- package/package.json +3 -3
- package/src/styles/suite-skin.css +62 -11
- package/src/suite/components/{ai-message-blocknote.test.tsx → ai-message-markdown.test.tsx} +10 -9
- package/src/suite/components/ai-message-markdown.tsx +21 -0
- package/src/suite/components/ai-panel.tsx +2 -2
- package/src/suite/components/command-palette-host.tsx +1 -1
- package/src/suite/components/suite-app-layout.tsx +1 -1
- package/dist/ai-message-blocknote-D4Z6DUCJ.mjs +0 -69
- package/dist/chunk-APKRZQRO.mjs +0 -251
- package/dist/chunk-IULORI3A.mjs +0 -8
- package/src/suite/components/ai-message-blocknote.tsx +0 -72
package/dist/theme-boot.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenide-io/the-old-ui-theme",
|
|
3
|
-
"version": "0.9.
|
|
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-
|
|
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-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
.suite-ai-
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
.suite-ai-
|
|
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-
|
|
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
|
|
1
|
+
import { cleanup, render, screen } from "@testing-library/react";
|
|
2
2
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
|
|
4
|
-
import
|
|
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("
|
|
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
|
|
26
|
+
it("renders safe, semantic Markdown", () => {
|
|
27
27
|
const { container } = render(
|
|
28
28
|
<SuiteThemeProvider config={themeConfig}>
|
|
29
|
-
<
|
|
29
|
+
<SuiteAiMarkdownMessage
|
|
30
|
+
markdown={"## Summary\n\n- First item\n\n"}
|
|
31
|
+
/>
|
|
30
32
|
</SuiteThemeProvider>,
|
|
31
33
|
);
|
|
32
34
|
|
|
33
|
-
|
|
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(
|
|
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
|
|
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
|
-
<
|
|
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
|
|
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
|
|
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
|
-
};
|
package/dist/chunk-APKRZQRO.mjs
DELETED
|
@@ -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
|
-
};
|
package/dist/chunk-IULORI3A.mjs
DELETED
|
@@ -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
|
-
}
|