@btst/stack 3.1.2 → 4.1.0
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/README.md +2 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.cjs +112 -103
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-input.mjs +112 -103
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.cjs +5 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-interface.mjs +5 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.cjs +99 -88
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-layout.mjs +99 -88
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.cjs +12 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-message.mjs +12 -3
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.cjs +6 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/components/chat-sidebar.mjs +6 -2
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.cjs +5 -5
- package/dist/packages/stack/src/plugins/ai-chat/client/hooks/chat-hooks.mjs +5 -5
- package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.cjs +3 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/page-wrapper.mjs +3 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.cjs +81 -52
- package/dist/packages/stack/src/plugins/blog/client/components/shared/post-card.mjs +82 -53
- package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.cjs +3 -2
- package/dist/packages/stack/src/plugins/blog/client/components/shared/posts-list.mjs +3 -2
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.cjs +95 -75
- package/dist/packages/stack/src/plugins/cms/client/components/pages/content-list-page.internal.mjs +95 -75
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.cjs +16 -3
- package/dist/packages/stack/src/plugins/cms/client/components/pages/dashboard-page.internal.mjs +16 -3
- package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.cjs +3 -2
- package/dist/packages/stack/src/plugins/cms/client/components/shared/page-wrapper.mjs +3 -2
- package/dist/packages/stack/src/plugins/cms/utils.cjs +6 -2
- package/dist/packages/stack/src/plugins/cms/utils.mjs +6 -3
- package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.cjs +14 -8
- package/dist/packages/stack/src/plugins/form-builder/client/components/forms/form-renderer.mjs +14 -8
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.cjs +94 -87
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/form-list-page.internal.mjs +94 -87
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.cjs +81 -74
- package/dist/packages/stack/src/plugins/form-builder/client/components/pages/submissions-page.internal.mjs +81 -74
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.cjs +3 -2
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/page-wrapper.mjs +3 -2
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.cjs +25 -16
- package/dist/packages/stack/src/plugins/form-builder/client/components/shared/pagination.mjs +25 -16
- package/dist/packages/stack/src/plugins/form-builder/utils.cjs +6 -2
- package/dist/packages/stack/src/plugins/form-builder/utils.mjs +6 -3
- package/dist/packages/stack/src/plugins/kanban/api/getters.cjs +7 -5
- package/dist/packages/stack/src/plugins/kanban/api/getters.mjs +7 -5
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +10 -8
- package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +10 -8
- package/dist/plugins/ai-chat/client/components/index.d.cts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.mts +1 -1
- package/dist/plugins/ai-chat/client/components/index.d.ts +1 -1
- package/dist/plugins/ai-chat/client/index.d.cts +2 -2
- package/dist/plugins/ai-chat/client/index.d.mts +2 -2
- package/dist/plugins/ai-chat/client/index.d.ts +2 -2
- package/dist/plugins/blog/api/index.d.cts +2 -2
- package/dist/plugins/blog/api/index.d.mts +2 -2
- package/dist/plugins/blog/api/index.d.ts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +20 -2
- package/dist/plugins/blog/client/index.d.mts +20 -2
- package/dist/plugins/blog/client/index.d.ts +20 -2
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/cms/api/index.d.cts +1 -1
- package/dist/plugins/cms/api/index.d.mts +1 -1
- package/dist/plugins/cms/api/index.d.ts +1 -1
- package/dist/plugins/cms/client/index.d.cts +18 -0
- package/dist/plugins/cms/client/index.d.mts +18 -0
- package/dist/plugins/cms/client/index.d.ts +18 -0
- package/dist/plugins/cms/query-keys.d.cts +1 -1
- package/dist/plugins/cms/query-keys.d.mts +1 -1
- package/dist/plugins/cms/query-keys.d.ts +1 -1
- package/dist/plugins/form-builder/client/index.d.cts +16 -0
- package/dist/plugins/form-builder/client/index.d.mts +16 -0
- package/dist/plugins/form-builder/client/index.d.ts +16 -0
- package/dist/plugins/ui-builder/client/hooks/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/hooks/index.d.ts +1 -1
- package/dist/plugins/ui-builder/client/index.d.cts +1 -1
- package/dist/plugins/ui-builder/client/index.d.mts +1 -1
- package/dist/plugins/ui-builder/client/index.d.ts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.cts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.mts +1 -1
- package/dist/plugins/ui-builder/query-keys.d.ts +1 -1
- package/dist/shared/{stack.BTStHVTy.d.cts → stack.BGzgFx3R.d.cts} +5 -5
- package/dist/shared/{stack.CtrnoQgH.d.cts → stack.BOZRaVpu.d.cts} +35 -35
- package/dist/shared/{stack.94kN3pEK.d.mts → stack.CjCSDfWy.d.mts} +5 -5
- package/dist/shared/{stack.lPKwd_v0.d.ts → stack.DlBoNqYu.d.ts} +35 -35
- package/dist/shared/{stack.DfrRYOIl.d.ts → stack.Dpg_JjIo.d.ts} +5 -5
- package/dist/shared/{stack.BggTHKlo.d.mts → stack.Dvy7FmaA.d.mts} +35 -35
- package/dist/shared/{stack.5_zzCejP.d.cts → stack.rVF0f509.d.cts} +20 -0
- package/dist/shared/{stack.5_zzCejP.d.mts → stack.rVF0f509.d.mts} +20 -0
- package/dist/shared/{stack.5_zzCejP.d.ts → stack.rVF0f509.d.ts} +20 -0
- package/package.json +11 -7
- package/src/__tests__/package-metadata.test.ts +4 -4
- package/src/plugins/ai-chat/__tests__/classnames-overrides.test.tsx +248 -0
- package/src/plugins/ai-chat/__tests__/hooks.test.tsx +70 -54
- package/src/plugins/ai-chat/client/components/chat-input.tsx +6 -1
- package/src/plugins/ai-chat/client/components/chat-interface.tsx +3 -0
- package/src/plugins/ai-chat/client/components/chat-layout.tsx +9 -2
- package/src/plugins/ai-chat/client/components/chat-message.tsx +13 -5
- package/src/plugins/ai-chat/client/components/chat-sidebar.tsx +6 -2
- package/src/plugins/ai-chat/client/hooks/chat-hooks.tsx +6 -9
- package/src/plugins/ai-chat/client/overrides.ts +21 -0
- package/src/plugins/blog/__tests__/classnames-overrides.test.tsx +206 -0
- package/src/plugins/blog/client/components/shared/page-wrapper.tsx +3 -2
- package/src/plugins/blog/client/components/shared/post-card.tsx +32 -12
- package/src/plugins/blog/client/components/shared/posts-list.tsx +3 -2
- package/src/plugins/blog/client/overrides.ts +18 -0
- package/src/plugins/cms/__tests__/classnames-overrides.test.tsx +200 -0
- package/src/plugins/cms/client/components/pages/content-list-page.internal.tsx +17 -4
- package/src/plugins/cms/client/components/pages/dashboard-page.internal.tsx +14 -3
- package/src/plugins/cms/client/components/shared/page-wrapper.tsx +3 -2
- package/src/plugins/cms/client/overrides.ts +19 -0
- package/src/plugins/form-builder/__tests__/classnames-overrides.test.tsx +312 -0
- package/src/plugins/form-builder/client/components/forms/form-renderer.tsx +14 -9
- package/src/plugins/form-builder/client/components/pages/form-list-page.internal.tsx +9 -5
- package/src/plugins/form-builder/client/components/pages/submissions-page.internal.tsx +9 -5
- package/src/plugins/form-builder/client/components/shared/page-wrapper.tsx +3 -2
- package/src/plugins/form-builder/client/components/shared/pagination.tsx +7 -1
- package/src/plugins/form-builder/client/overrides.ts +17 -0
- package/src/plugins/kanban/__tests__/getters.test.ts +14 -1
- package/src/plugins/kanban/api/getters.ts +9 -7
- package/src/plugins/kanban/types.ts +4 -6
- package/src/plugins/media/client/hooks/use-media.tsx +7 -8
- package/dist/shared/{stack.lsbdKRj7.d.mts → stack.B8PG21zX.d.mts} +16 -16
- package/dist/shared/{stack.CKc_Bais.d.cts → stack.Csi-4Mgz.d.cts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.mts → stack.Csi-4Mgz.d.mts} +7 -7
- package/dist/shared/{stack.CKc_Bais.d.ts → stack.Csi-4Mgz.d.ts} +7 -7
- package/dist/shared/{stack.BMlTZnZp.d.ts → stack.CyalJ0j6.d.ts} +16 -16
- package/dist/shared/{stack.BV0uNV4f.d.cts → stack.D6Pg-ufX.d.cts} +16 -16
|
@@ -2,7 +2,7 @@ import * as _btst_stack_plugins_client from '@btst/stack/plugins/client';
|
|
|
2
2
|
import { createApiClient } from '@btst/stack/plugins/client';
|
|
3
3
|
import * as zod from 'zod';
|
|
4
4
|
import { z } from 'zod';
|
|
5
|
-
import { c as createPostSchema, u as updatePostSchema } from './stack.
|
|
5
|
+
import { c as createPostSchema, u as updatePostSchema } from './stack.Csi-4Mgz.mjs';
|
|
6
6
|
import { P as Post, T as Tag, S as SerializedPost, a as SerializedTag } from './stack.Dsatjh0B.mjs';
|
|
7
7
|
import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
|
|
8
8
|
import { DeepReadonly, OperationContext, OperationErrorContext } from '@btst/stack/plugins/api';
|
|
@@ -89,12 +89,6 @@ declare const NextPreviousPostsQuerySchema: z.ZodObject<{
|
|
|
89
89
|
date: z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>;
|
|
90
90
|
}, z.core.$strip>;
|
|
91
91
|
declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
92
|
-
slug: z.ZodOptional<z.ZodString>;
|
|
93
|
-
title: z.ZodString;
|
|
94
|
-
content: z.ZodString;
|
|
95
|
-
excerpt: z.ZodString;
|
|
96
|
-
image: z.ZodOptional<z.ZodString>;
|
|
97
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
98
92
|
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
99
93
|
name: z.ZodString;
|
|
100
94
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -102,6 +96,12 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
|
102
96
|
name: z.ZodString;
|
|
103
97
|
slug: z.ZodString;
|
|
104
98
|
}, z.core.$strip>]>>>>;
|
|
99
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
100
|
+
title: z.ZodString;
|
|
101
|
+
content: z.ZodString;
|
|
102
|
+
excerpt: z.ZodString;
|
|
103
|
+
image: z.ZodOptional<z.ZodString>;
|
|
104
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
105
105
|
publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
106
106
|
createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
107
107
|
updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
@@ -109,12 +109,6 @@ declare const CreatePostOperationInputSchema: z.ZodObject<{
|
|
|
109
109
|
declare const UpdatePostOperationInputSchema: z.ZodObject<{
|
|
110
110
|
id: z.ZodString;
|
|
111
111
|
data: z.ZodObject<{
|
|
112
|
-
slug: z.ZodString;
|
|
113
|
-
title: z.ZodString;
|
|
114
|
-
content: z.ZodString;
|
|
115
|
-
excerpt: z.ZodString;
|
|
116
|
-
image: z.ZodOptional<z.ZodString>;
|
|
117
|
-
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
118
112
|
tags: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
119
113
|
name: z.ZodString;
|
|
120
114
|
}, z.core.$strip>, z.ZodObject<{
|
|
@@ -122,6 +116,12 @@ declare const UpdatePostOperationInputSchema: z.ZodObject<{
|
|
|
122
116
|
name: z.ZodString;
|
|
123
117
|
slug: z.ZodString;
|
|
124
118
|
}, z.core.$strip>]>>>>;
|
|
119
|
+
slug: z.ZodString;
|
|
120
|
+
title: z.ZodString;
|
|
121
|
+
content: z.ZodString;
|
|
122
|
+
excerpt: z.ZodString;
|
|
123
|
+
image: z.ZodOptional<z.ZodString>;
|
|
124
|
+
published: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
125
125
|
publishedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
126
126
|
createdAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
127
127
|
updatedAt: z.ZodOptional<z.ZodPipe<z.ZodCoercedDate<unknown>, z.ZodTransform<string, Date>>>;
|
|
@@ -386,9 +386,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
386
386
|
title: string;
|
|
387
387
|
content: string;
|
|
388
388
|
excerpt: string;
|
|
389
|
-
slug?: string | undefined;
|
|
390
|
-
image?: string | undefined;
|
|
391
|
-
published?: boolean | undefined;
|
|
392
389
|
tags?: ({
|
|
393
390
|
name: string;
|
|
394
391
|
} | {
|
|
@@ -396,6 +393,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
396
393
|
name: string;
|
|
397
394
|
slug: string;
|
|
398
395
|
})[] | undefined;
|
|
396
|
+
slug?: string | undefined;
|
|
397
|
+
image?: string | undefined;
|
|
398
|
+
published?: boolean | undefined;
|
|
399
399
|
publishedAt?: unknown;
|
|
400
400
|
createdAt?: unknown;
|
|
401
401
|
updatedAt?: unknown;
|
|
@@ -403,9 +403,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
403
403
|
title: string;
|
|
404
404
|
content: string;
|
|
405
405
|
excerpt: string;
|
|
406
|
-
slug?: string | undefined;
|
|
407
|
-
image?: string | undefined;
|
|
408
|
-
published?: boolean | undefined;
|
|
409
406
|
tags?: ({
|
|
410
407
|
name: string;
|
|
411
408
|
} | {
|
|
@@ -413,6 +410,9 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
413
410
|
name: string;
|
|
414
411
|
slug: string;
|
|
415
412
|
})[] | undefined;
|
|
413
|
+
slug?: string | undefined;
|
|
414
|
+
image?: string | undefined;
|
|
415
|
+
published?: boolean | undefined;
|
|
416
416
|
publishedAt?: unknown;
|
|
417
417
|
createdAt?: unknown;
|
|
418
418
|
updatedAt?: unknown;
|
|
@@ -448,8 +448,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
448
448
|
title: string;
|
|
449
449
|
content: string;
|
|
450
450
|
excerpt: string;
|
|
451
|
-
image?: string | undefined;
|
|
452
|
-
published?: boolean | undefined;
|
|
453
451
|
tags?: ({
|
|
454
452
|
name: string;
|
|
455
453
|
} | {
|
|
@@ -457,6 +455,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
457
455
|
name: string;
|
|
458
456
|
slug: string;
|
|
459
457
|
})[] | undefined;
|
|
458
|
+
image?: string | undefined;
|
|
459
|
+
published?: boolean | undefined;
|
|
460
460
|
publishedAt?: unknown;
|
|
461
461
|
createdAt?: unknown;
|
|
462
462
|
updatedAt?: unknown;
|
|
@@ -465,8 +465,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
465
465
|
title: string;
|
|
466
466
|
content: string;
|
|
467
467
|
excerpt: string;
|
|
468
|
-
image?: string | undefined;
|
|
469
|
-
published?: boolean | undefined;
|
|
470
468
|
tags?: ({
|
|
471
469
|
name: string;
|
|
472
470
|
} | {
|
|
@@ -474,6 +472,8 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
474
472
|
name: string;
|
|
475
473
|
slug: string;
|
|
476
474
|
})[] | undefined;
|
|
475
|
+
image?: string | undefined;
|
|
476
|
+
published?: boolean | undefined;
|
|
477
477
|
publishedAt?: unknown;
|
|
478
478
|
createdAt?: unknown;
|
|
479
479
|
updatedAt?: unknown;
|
|
@@ -627,12 +627,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
627
627
|
total: number;
|
|
628
628
|
}, false>;
|
|
629
629
|
readonly createPost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
|
|
630
|
-
slug: zod.ZodOptional<zod.ZodString>;
|
|
631
|
-
title: zod.ZodString;
|
|
632
|
-
content: zod.ZodString;
|
|
633
|
-
excerpt: zod.ZodString;
|
|
634
|
-
image: zod.ZodOptional<zod.ZodString>;
|
|
635
|
-
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
636
630
|
tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
|
|
637
631
|
name: zod.ZodString;
|
|
638
632
|
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
@@ -640,6 +634,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
640
634
|
name: zod.ZodString;
|
|
641
635
|
slug: zod.ZodString;
|
|
642
636
|
}, zod_v4_core.$strip>]>>>>;
|
|
637
|
+
slug: zod.ZodOptional<zod.ZodString>;
|
|
638
|
+
title: zod.ZodString;
|
|
639
|
+
content: zod.ZodString;
|
|
640
|
+
excerpt: zod.ZodString;
|
|
641
|
+
image: zod.ZodOptional<zod.ZodString>;
|
|
642
|
+
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
643
643
|
publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
644
644
|
createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
645
645
|
updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
@@ -673,12 +673,6 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
673
673
|
readonly updatePost: _btst_stack_plugins_api.Operation<zod.ZodObject<{
|
|
674
674
|
id: zod.ZodString;
|
|
675
675
|
data: zod.ZodObject<{
|
|
676
|
-
slug: zod.ZodString;
|
|
677
|
-
title: zod.ZodString;
|
|
678
|
-
content: zod.ZodString;
|
|
679
|
-
excerpt: zod.ZodString;
|
|
680
|
-
image: zod.ZodOptional<zod.ZodString>;
|
|
681
|
-
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
682
676
|
tags: zod.ZodDefault<zod.ZodOptional<zod.ZodArray<zod.ZodUnion<readonly [zod.ZodObject<{
|
|
683
677
|
name: zod.ZodString;
|
|
684
678
|
}, zod_v4_core.$strip>, zod.ZodObject<{
|
|
@@ -686,6 +680,12 @@ declare const blogBackendPlugin: (options?: BlogBackendOptions) => _btst_stack_p
|
|
|
686
680
|
name: zod.ZodString;
|
|
687
681
|
slug: zod.ZodString;
|
|
688
682
|
}, zod_v4_core.$strip>]>>>>;
|
|
683
|
+
slug: zod.ZodString;
|
|
684
|
+
title: zod.ZodString;
|
|
685
|
+
content: zod.ZodString;
|
|
686
|
+
excerpt: zod.ZodString;
|
|
687
|
+
image: zod.ZodOptional<zod.ZodString>;
|
|
688
|
+
published: zod.ZodDefault<zod.ZodOptional<zod.ZodBoolean>>;
|
|
689
689
|
publishedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
690
690
|
createdAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
691
691
|
updatedAt: zod.ZodOptional<zod.ZodPipe<zod.ZodCoercedDate<unknown>, zod.ZodTransform<string, Date>>>;
|
|
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
|
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
toolRenderers?: Record<string, ToolCallRenderer>;
|
|
207
|
+
/**
|
|
208
|
+
* Optional class names merged onto specific UI elements.
|
|
209
|
+
* Use these for targeted styling without replacing whole components.
|
|
210
|
+
*/
|
|
211
|
+
classNames?: {
|
|
212
|
+
/** Root of `ChatLayout` (full-page or widget mode) */
|
|
213
|
+
container?: string;
|
|
214
|
+
/** Conversation sidebar root */
|
|
215
|
+
sidebar?: string;
|
|
216
|
+
/** Wrapper around the list of messages */
|
|
217
|
+
messageList?: string;
|
|
218
|
+
/** Each message row (user and assistant) */
|
|
219
|
+
message?: string;
|
|
220
|
+
/** The bubble of a user message */
|
|
221
|
+
userMessage?: string;
|
|
222
|
+
/** The bubble of an assistant message */
|
|
223
|
+
assistantMessage?: string;
|
|
224
|
+
/** The composer form at the bottom of the chat */
|
|
225
|
+
input?: string;
|
|
226
|
+
};
|
|
207
227
|
/**
|
|
208
228
|
* Called when a route is rendered
|
|
209
229
|
* @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
|
|
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
|
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
toolRenderers?: Record<string, ToolCallRenderer>;
|
|
207
|
+
/**
|
|
208
|
+
* Optional class names merged onto specific UI elements.
|
|
209
|
+
* Use these for targeted styling without replacing whole components.
|
|
210
|
+
*/
|
|
211
|
+
classNames?: {
|
|
212
|
+
/** Root of `ChatLayout` (full-page or widget mode) */
|
|
213
|
+
container?: string;
|
|
214
|
+
/** Conversation sidebar root */
|
|
215
|
+
sidebar?: string;
|
|
216
|
+
/** Wrapper around the list of messages */
|
|
217
|
+
messageList?: string;
|
|
218
|
+
/** Each message row (user and assistant) */
|
|
219
|
+
message?: string;
|
|
220
|
+
/** The bubble of a user message */
|
|
221
|
+
userMessage?: string;
|
|
222
|
+
/** The bubble of an assistant message */
|
|
223
|
+
assistantMessage?: string;
|
|
224
|
+
/** The composer form at the bottom of the chat */
|
|
225
|
+
input?: string;
|
|
226
|
+
};
|
|
207
227
|
/**
|
|
208
228
|
* Called when a route is rendered
|
|
209
229
|
* @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
|
|
@@ -204,6 +204,26 @@ interface AiChatPluginOverrides {
|
|
|
204
204
|
* ```
|
|
205
205
|
*/
|
|
206
206
|
toolRenderers?: Record<string, ToolCallRenderer>;
|
|
207
|
+
/**
|
|
208
|
+
* Optional class names merged onto specific UI elements.
|
|
209
|
+
* Use these for targeted styling without replacing whole components.
|
|
210
|
+
*/
|
|
211
|
+
classNames?: {
|
|
212
|
+
/** Root of `ChatLayout` (full-page or widget mode) */
|
|
213
|
+
container?: string;
|
|
214
|
+
/** Conversation sidebar root */
|
|
215
|
+
sidebar?: string;
|
|
216
|
+
/** Wrapper around the list of messages */
|
|
217
|
+
messageList?: string;
|
|
218
|
+
/** Each message row (user and assistant) */
|
|
219
|
+
message?: string;
|
|
220
|
+
/** The bubble of a user message */
|
|
221
|
+
userMessage?: string;
|
|
222
|
+
/** The bubble of an assistant message */
|
|
223
|
+
assistantMessage?: string;
|
|
224
|
+
/** The composer form at the bottom of the chat */
|
|
225
|
+
input?: string;
|
|
226
|
+
};
|
|
207
227
|
/**
|
|
208
228
|
* Called when a route is rendered
|
|
209
229
|
* @param routeName - Name of the route (e.g., 'chat', 'chatConversation')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btst/stack",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "A composable, plugin-based library for building full-stack applications.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -1067,7 +1067,7 @@
|
|
|
1067
1067
|
}
|
|
1068
1068
|
},
|
|
1069
1069
|
"dependencies": {
|
|
1070
|
-
"@btst/db": "
|
|
1070
|
+
"@btst/db": "3.0.0",
|
|
1071
1071
|
"@milkdown/crepe": "^7.17.1",
|
|
1072
1072
|
"@milkdown/kit": "^7.17.1",
|
|
1073
1073
|
"remend": "^1.0.1",
|
|
@@ -1088,7 +1088,7 @@
|
|
|
1088
1088
|
"@tanstack/react-router": ">=1.0.0",
|
|
1089
1089
|
"@vercel/blob": ">=0.14.0",
|
|
1090
1090
|
"ai": ">=5.0.0",
|
|
1091
|
-
"better-call": "1.
|
|
1091
|
+
"better-call": "1.4.0",
|
|
1092
1092
|
"class-variance-authority": ">=0.7.0",
|
|
1093
1093
|
"clsx": ">=2.1.0",
|
|
1094
1094
|
"cmdk": ">=1.1.0",
|
|
@@ -1097,8 +1097,8 @@
|
|
|
1097
1097
|
"katex": ">=0.16.0",
|
|
1098
1098
|
"lucide-react": ">=0.469.0",
|
|
1099
1099
|
"next": ">=15.0.0",
|
|
1100
|
-
"react": "
|
|
1101
|
-
"react-dom": "
|
|
1100
|
+
"react": ">=19.2.6",
|
|
1101
|
+
"react-dom": ">=19.2.6",
|
|
1102
1102
|
"react-error-boundary": ">=4.0.0",
|
|
1103
1103
|
"react-hook-form": ">=7.55.0",
|
|
1104
1104
|
"react-markdown": ">=9.1.0",
|
|
@@ -1110,7 +1110,7 @@
|
|
|
1110
1110
|
"remark-math": ">=6.0.0",
|
|
1111
1111
|
"sonner": ">=2.0.0",
|
|
1112
1112
|
"tailwind-merge": ">=2.6.0",
|
|
1113
|
-
"tailwindcss": ">=3.
|
|
1113
|
+
"tailwindcss": ">=4.3.2",
|
|
1114
1114
|
"zod": ">=4.2.0"
|
|
1115
1115
|
},
|
|
1116
1116
|
"peerDependenciesMeta": {
|
|
@@ -1137,7 +1137,10 @@
|
|
|
1137
1137
|
"@ai-sdk/react": "^2.0.94",
|
|
1138
1138
|
"@aws-sdk/client-s3": "^3.1011.0",
|
|
1139
1139
|
"@aws-sdk/s3-request-presigner": "^3.1011.0",
|
|
1140
|
-
"@
|
|
1140
|
+
"@better-auth/core": "1.7.6",
|
|
1141
|
+
"@better-auth/utils": "0.4.2",
|
|
1142
|
+
"@better-fetch/fetch": "1.3.2",
|
|
1143
|
+
"@btst/adapter-memory": "3.0.0",
|
|
1141
1144
|
"@btst/yar": "1.3.2",
|
|
1142
1145
|
"@tanstack/react-router": "1.168.10",
|
|
1143
1146
|
"@types/react": "^19.0.0",
|
|
@@ -1146,6 +1149,7 @@
|
|
|
1146
1149
|
"@vercel/blob": "^0.27.3",
|
|
1147
1150
|
"@workspace/ui": "workspace:*",
|
|
1148
1151
|
"ai": "^5.0.94",
|
|
1152
|
+
"better-auth": "1.7.6",
|
|
1149
1153
|
"better-call": "catalog:",
|
|
1150
1154
|
"jsdom": "^28.1.0",
|
|
1151
1155
|
"knip": "^5.61.2",
|
|
@@ -11,7 +11,7 @@ import { z } from "zod";
|
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
|
|
13
13
|
describe("published dependency alignment", () => {
|
|
14
|
-
it("uses the Better DB release that shares the Better Auth 1.6
|
|
14
|
+
it("uses the Better DB release that shares the Better Auth 1.7.6 cohort", async () => {
|
|
15
15
|
const manifest = JSON.parse(
|
|
16
16
|
await readFile(resolve("package.json"), "utf8"),
|
|
17
17
|
) as {
|
|
@@ -20,11 +20,11 @@ describe("published dependency alignment", () => {
|
|
|
20
20
|
peerDependencies?: Record<string, string>;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
-
expect(manifest.dependencies?.["@btst/db"]).toBe("
|
|
24
|
-
expect(manifest.devDependencies?.["@btst/adapter-memory"]).toBe("
|
|
23
|
+
expect(manifest.dependencies?.["@btst/db"]).toBe("3.0.0");
|
|
24
|
+
expect(manifest.devDependencies?.["@btst/adapter-memory"]).toBe("3.0.0");
|
|
25
25
|
expect(manifest.devDependencies?.["@btst/yar"]).toBe("1.3.2");
|
|
26
26
|
expect(manifest.peerDependencies?.["@btst/yar"]).toBe(">=1.3.2");
|
|
27
|
-
expect(manifest.peerDependencies?.["better-call"]).toBe("1.
|
|
27
|
+
expect(manifest.peerDependencies?.["better-call"]).toBe("1.4.0");
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
|
+
import { act } from "react";
|
|
4
|
+
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
// Core primitives MUST be imported from the package entry (not relative src
|
|
7
|
+
// paths) so they share module identity — and React context — with the plugin
|
|
8
|
+
// components, which resolve `@btst/stack/*` via package self-reference.
|
|
9
|
+
import { StackProvider } from "@btst/stack/context";
|
|
10
|
+
import type { UIMessage } from "ai";
|
|
11
|
+
import { ChatInput } from "../client/components/chat-input";
|
|
12
|
+
import { ChatLayout } from "../client/components/chat-layout";
|
|
13
|
+
import { ChatMessage } from "../client/components/chat-message";
|
|
14
|
+
import { aiChatClientPlugin } from "../client/plugin";
|
|
15
|
+
import type { SerializedConversation } from "../types";
|
|
16
|
+
import { createTestClientStack } from "../../../__tests__/client-stack-test-utils";
|
|
17
|
+
|
|
18
|
+
(globalThis as any).IS_REACT_ACT_ENVIRONMENT = true;
|
|
19
|
+
(globalThis as any).ResizeObserver ??= class {
|
|
20
|
+
observe() {}
|
|
21
|
+
unobserve() {}
|
|
22
|
+
disconnect() {}
|
|
23
|
+
};
|
|
24
|
+
Element.prototype.scrollIntoView ??= () => {};
|
|
25
|
+
|
|
26
|
+
const mocks = vi.hoisted(() => ({
|
|
27
|
+
useChat: vi.fn(),
|
|
28
|
+
useConversation: vi.fn(),
|
|
29
|
+
useConversations: vi.fn(),
|
|
30
|
+
useRenameConversationForm: vi.fn(),
|
|
31
|
+
useDeleteConversation: vi.fn(),
|
|
32
|
+
useAiChatIdentityPartition: vi.fn(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
vi.mock("@ai-sdk/react", () => ({ useChat: mocks.useChat }));
|
|
36
|
+
vi.mock("../client/hooks/chat-hooks", () => ({
|
|
37
|
+
useAiChatIdentityPartition: mocks.useAiChatIdentityPartition,
|
|
38
|
+
useConversation: mocks.useConversation,
|
|
39
|
+
useConversations: mocks.useConversations,
|
|
40
|
+
useRenameConversationForm: mocks.useRenameConversationForm,
|
|
41
|
+
useDeleteConversation: mocks.useDeleteConversation,
|
|
42
|
+
}));
|
|
43
|
+
vi.mock("../client/context/page-ai-context", () => ({
|
|
44
|
+
usePageAIContext: () => undefined,
|
|
45
|
+
}));
|
|
46
|
+
// Markdown rendering is unrelated to class wiring and drags in highlight/katex.
|
|
47
|
+
vi.mock("@workspace/ui/components/markdown-content", () => ({
|
|
48
|
+
MarkdownContent: ({ content }: { content: string }) => <div>{content}</div>,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
const CLASS_NAMES = {
|
|
52
|
+
container: "bg-red-100",
|
|
53
|
+
sidebar: "bg-blue-100",
|
|
54
|
+
messageList: "bg-yellow-50",
|
|
55
|
+
message: "text-purple-700",
|
|
56
|
+
userMessage: "border-4 border-orange-500",
|
|
57
|
+
assistantMessage: "border-4 border-green-500",
|
|
58
|
+
input: "bg-pink-100",
|
|
59
|
+
} as const;
|
|
60
|
+
|
|
61
|
+
const conversation: SerializedConversation = {
|
|
62
|
+
id: "conv-1",
|
|
63
|
+
userId: "owner-1",
|
|
64
|
+
title: "First conversation",
|
|
65
|
+
createdAt: new Date("2024-01-01").toISOString(),
|
|
66
|
+
updatedAt: new Date("2024-01-02").toISOString(),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const userMessage: UIMessage = {
|
|
70
|
+
id: "user-1",
|
|
71
|
+
role: "user",
|
|
72
|
+
parts: [{ type: "text", text: "Hi there" }],
|
|
73
|
+
};
|
|
74
|
+
const assistantMessage: UIMessage = {
|
|
75
|
+
id: "assistant-1",
|
|
76
|
+
role: "assistant",
|
|
77
|
+
parts: [{ type: "text", text: "Hello back" }],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
let container: HTMLDivElement;
|
|
81
|
+
let root: Root;
|
|
82
|
+
let queryClient: QueryClient;
|
|
83
|
+
|
|
84
|
+
beforeEach(() => {
|
|
85
|
+
container = document.createElement("div");
|
|
86
|
+
document.body.appendChild(container);
|
|
87
|
+
root = createRoot(container);
|
|
88
|
+
queryClient = new QueryClient({
|
|
89
|
+
defaultOptions: { queries: { retry: false } },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
mocks.useAiChatIdentityPartition.mockReturnValue("anonymous");
|
|
93
|
+
mocks.useChat.mockReturnValue({
|
|
94
|
+
messages: [userMessage, assistantMessage],
|
|
95
|
+
sendMessage: vi.fn(),
|
|
96
|
+
status: "ready",
|
|
97
|
+
error: null,
|
|
98
|
+
setMessages: vi.fn(),
|
|
99
|
+
regenerate: vi.fn(),
|
|
100
|
+
addToolOutput: vi.fn(),
|
|
101
|
+
stop: vi.fn(),
|
|
102
|
+
});
|
|
103
|
+
mocks.useConversation.mockReturnValue({
|
|
104
|
+
conversation: null,
|
|
105
|
+
isLoading: false,
|
|
106
|
+
error: null,
|
|
107
|
+
refetch: vi.fn(),
|
|
108
|
+
});
|
|
109
|
+
mocks.useConversations.mockReturnValue({
|
|
110
|
+
conversations: [conversation],
|
|
111
|
+
isLoading: false,
|
|
112
|
+
error: null,
|
|
113
|
+
refetch: vi.fn(),
|
|
114
|
+
});
|
|
115
|
+
mocks.useRenameConversationForm.mockReturnValue({
|
|
116
|
+
submit: vi.fn(),
|
|
117
|
+
isSubmitting: false,
|
|
118
|
+
error: null,
|
|
119
|
+
fieldErrors: {},
|
|
120
|
+
clearErrors: vi.fn(),
|
|
121
|
+
});
|
|
122
|
+
mocks.useDeleteConversation.mockReturnValue({
|
|
123
|
+
mutateAsync: vi.fn(),
|
|
124
|
+
isPending: false,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
afterEach(async () => {
|
|
129
|
+
await act(async () => {
|
|
130
|
+
root.unmount();
|
|
131
|
+
});
|
|
132
|
+
container.remove();
|
|
133
|
+
vi.clearAllMocks();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
async function render(ui: React.ReactElement) {
|
|
137
|
+
await act(async () => {
|
|
138
|
+
root.render(
|
|
139
|
+
<QueryClientProvider client={queryClient}>
|
|
140
|
+
<StackProvider
|
|
141
|
+
stack={createTestClientStack(
|
|
142
|
+
{ aiChat: aiChatClientPlugin() },
|
|
143
|
+
queryClient,
|
|
144
|
+
)}
|
|
145
|
+
overrides={{ aiChat: { classNames: CLASS_NAMES } }}
|
|
146
|
+
>
|
|
147
|
+
{ui}
|
|
148
|
+
</StackProvider>
|
|
149
|
+
</QueryClientProvider>,
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function query(selector: string): HTMLElement {
|
|
155
|
+
const el = container.querySelector<HTMLElement>(selector);
|
|
156
|
+
expect(el, `expected to find ${selector}`).toBeTruthy();
|
|
157
|
+
return el!;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
describe("ai-chat classNames overrides (issue #36)", () => {
|
|
161
|
+
it("applies message, userMessage and assistantMessage to a user message", async () => {
|
|
162
|
+
await render(<ChatMessage message={userMessage} />);
|
|
163
|
+
|
|
164
|
+
const row = query('[data-testid="chat-message"][data-role="user"]');
|
|
165
|
+
expect(row.className).toContain(CLASS_NAMES.message);
|
|
166
|
+
expect(row.className).toContain("justify-end");
|
|
167
|
+
|
|
168
|
+
const bubble = query('[data-testid="chat-message-bubble"]');
|
|
169
|
+
expect(bubble.className).toContain("border-orange-500");
|
|
170
|
+
expect(bubble.className).not.toContain("border-green-500");
|
|
171
|
+
// The role-specific base styling is preserved.
|
|
172
|
+
expect(bubble.className).toContain("bg-primary");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("applies assistantMessage (not userMessage) to an assistant message", async () => {
|
|
176
|
+
await render(<ChatMessage message={assistantMessage} />);
|
|
177
|
+
|
|
178
|
+
const row = query('[data-testid="chat-message"][data-role="assistant"]');
|
|
179
|
+
expect(row.className).toContain(CLASS_NAMES.message);
|
|
180
|
+
expect(row.className).toContain("justify-start");
|
|
181
|
+
|
|
182
|
+
const bubble = query('[data-testid="chat-message-bubble"]');
|
|
183
|
+
expect(bubble.className).toContain("border-green-500");
|
|
184
|
+
expect(bubble.className).not.toContain("border-orange-500");
|
|
185
|
+
expect(bubble.className).toContain("bg-muted");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it("applies classNames.input to the composer form", async () => {
|
|
189
|
+
await render(
|
|
190
|
+
<ChatInput
|
|
191
|
+
handleInputChange={() => {}}
|
|
192
|
+
handleSubmit={() => {}}
|
|
193
|
+
isLoading={false}
|
|
194
|
+
/>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
const form = query('[data-testid="chat-input-form"]');
|
|
198
|
+
expect(form.tagName).toBe("FORM");
|
|
199
|
+
expect(form.className).toContain(CLASS_NAMES.input);
|
|
200
|
+
expect(form.className).toContain("space-y-2");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("applies container, sidebar and messageList in the full layout", async () => {
|
|
204
|
+
await render(<ChatLayout conversationId={conversation.id} />);
|
|
205
|
+
|
|
206
|
+
const layout = query('[data-testid="chat-layout"]');
|
|
207
|
+
expect(layout.className).toContain(CLASS_NAMES.container);
|
|
208
|
+
expect(layout.className).toContain("overflow-hidden");
|
|
209
|
+
|
|
210
|
+
const sidebar = query('[data-testid="chat-sidebar"]');
|
|
211
|
+
expect(sidebar.className).toContain(CLASS_NAMES.sidebar);
|
|
212
|
+
expect(sidebar.className).toContain("border-r");
|
|
213
|
+
|
|
214
|
+
const list = query('[data-testid="chat-message-list"]');
|
|
215
|
+
expect(list.className).toContain(CLASS_NAMES.messageList);
|
|
216
|
+
expect(list.className).toContain("flex-col");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("applies classNames.container to the widget root", async () => {
|
|
220
|
+
await render(<ChatLayout layout="widget" />);
|
|
221
|
+
|
|
222
|
+
const trigger = query('[data-testid="widget-trigger"]');
|
|
223
|
+
const widgetRoot = trigger.parentElement!;
|
|
224
|
+
expect(widgetRoot.className).toContain(CLASS_NAMES.container);
|
|
225
|
+
expect(widgetRoot.className).toContain("items-end");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("leaves base classes untouched when no classNames override is supplied", async () => {
|
|
229
|
+
await act(async () => {
|
|
230
|
+
root.render(
|
|
231
|
+
<QueryClientProvider client={queryClient}>
|
|
232
|
+
<StackProvider
|
|
233
|
+
stack={createTestClientStack(
|
|
234
|
+
{ aiChat: aiChatClientPlugin() },
|
|
235
|
+
queryClient,
|
|
236
|
+
)}
|
|
237
|
+
>
|
|
238
|
+
<ChatMessage message={userMessage} />
|
|
239
|
+
</StackProvider>
|
|
240
|
+
</QueryClientProvider>,
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const bubble = query('[data-testid="chat-message-bubble"]');
|
|
245
|
+
expect(bubble.className).toContain("bg-primary");
|
|
246
|
+
expect(bubble.className).not.toContain("border-orange-500");
|
|
247
|
+
});
|
|
248
|
+
});
|