@carlonicora/nextjs-jsonapi 1.109.0 → 1.111.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/dist/{AssistantMessageInterface-Cs1yb-gF.d.ts → AssistantMessageInterface-BNzf1etU.d.ts} +14 -1
- package/dist/{AssistantMessageInterface-DQ3mH5L8.d.mts → AssistantMessageInterface-DrgHDjZ5.d.mts} +14 -1
- package/dist/{BlockNoteEditor-3XYBZLWO.js → BlockNoteEditor-7OKW6HHN.js} +19 -20
- package/dist/BlockNoteEditor-7OKW6HHN.js.map +1 -0
- package/dist/{BlockNoteEditor-EBFZG7AL.mjs → BlockNoteEditor-K7IIWM3S.mjs} +4 -5
- package/dist/{BlockNoteEditor-EBFZG7AL.mjs.map → BlockNoteEditor-K7IIWM3S.mjs.map} +1 -1
- package/dist/BlockNoteViewer-OPSCTHKJ.mjs +31 -0
- package/dist/BlockNoteViewer-OPSCTHKJ.mjs.map +1 -0
- package/dist/BlockNoteViewer-UW5VZ5UF.js +31 -0
- package/dist/BlockNoteViewer-UW5VZ5UF.js.map +1 -0
- package/dist/billing/index.d.mts +41 -41
- package/dist/billing/index.d.ts +41 -41
- package/dist/billing/index.js +346 -347
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +3 -4
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-7E3O52U5.js → chunk-6XWW67F2.js} +25 -12
- package/dist/chunk-6XWW67F2.js.map +1 -0
- package/dist/{chunk-UHO3KUUH.js → chunk-AKXUNE3A.js} +977 -850
- package/dist/chunk-AKXUNE3A.js.map +1 -0
- package/dist/{chunk-UOYIWJEJ.mjs → chunk-CWVYU4YZ.mjs} +18 -5
- package/dist/{chunk-UOYIWJEJ.mjs.map → chunk-CWVYU4YZ.mjs.map} +1 -1
- package/dist/{chunk-CRTVAQEK.mjs → chunk-TXVVZLUQ.mjs} +1388 -1261
- package/dist/chunk-TXVVZLUQ.mjs.map +1 -0
- package/dist/{chunk-CFI4WZ5R.js → chunk-U2HR57MY.js} +111 -5
- package/dist/chunk-U2HR57MY.js.map +1 -0
- package/dist/chunk-WPAQRB5Q.mjs +7 -0
- package/dist/chunk-WPAQRB5Q.mjs.map +1 -0
- package/dist/chunk-YBZVWLPH.js +7 -0
- package/dist/chunk-YBZVWLPH.js.map +1 -0
- package/dist/{chunk-MSNNAHDB.mjs → chunk-ZYPNVEHB.mjs} +112 -6
- package/dist/{chunk-MSNNAHDB.mjs.map → chunk-ZYPNVEHB.mjs.map} +1 -1
- package/dist/client/index.d.mts +3 -4
- package/dist/client/index.d.ts +3 -4
- package/dist/client/index.js +4 -5
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -4
- package/dist/components/index.d.mts +418 -415
- package/dist/components/index.d.ts +418 -415
- package/dist/components/index.js +6 -5
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +5 -4
- package/dist/{config-CN23v3eJ.d.ts → config-BrkU_Ynz.d.ts} +2 -2
- package/dist/{config-gh88Qn4h.d.mts → config-snd2ToEB.d.mts} +2 -2
- package/dist/contexts/index.d.mts +15 -15
- package/dist/contexts/index.d.ts +15 -15
- package/dist/contexts/index.js +4 -5
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +3 -4
- package/dist/core/index.d.mts +35 -4
- package/dist/core/index.d.ts +35 -4
- package/dist/core/index.js +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/features/help/index.d.mts +25 -66
- package/dist/features/help/index.d.ts +25 -66
- package/dist/features/help/index.js +114 -124
- package/dist/features/help/index.js.map +1 -1
- package/dist/features/help/index.mjs +95 -105
- package/dist/features/help/index.mjs.map +1 -1
- package/dist/features/help/server.d.mts +27 -65
- package/dist/features/help/server.d.ts +27 -65
- package/dist/features/help/server.js +4 -119
- package/dist/features/help/server.js.map +1 -1
- package/dist/features/help/server.mjs +4 -119
- package/dist/features/help/server.mjs.map +1 -1
- package/dist/help-content-config.interface-C5ESIod9.d.mts +13 -0
- package/dist/help-content-config.interface-C5ESIod9.d.ts +13 -0
- package/dist/index.d.mts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -3
- package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.js +54 -0
- package/dist/scripts/generate-web-module/__tests__/container.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.js +19 -0
- package/dist/scripts/generate-web-module/__tests__/data-service.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.js +35 -0
- package/dist/scripts/generate-web-module/__tests__/editor.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts +4 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.js +45 -0
- package/dist/scripts/generate-web-module/__tests__/fixtures.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js +74 -0
- package/dist/scripts/generate-web-module/__tests__/model-interface.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js +21 -0
- package/dist/scripts/generate-web-module/__tests__/plumbing.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js +25 -0
- package/dist/scripts/generate-web-module/__tests__/selector.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts +2 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js +55 -0
- package/dist/scripts/generate-web-module/__tests__/table-module-list.spec.js.map +1 -0
- package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/generator.js +14 -11
- package/dist/scripts/generate-web-module/generator.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts +5 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/container.template.js +43 -63
- package/dist/scripts/generate-web-module/templates/components/container.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts +7 -4
- package/dist/scripts/generate-web-module/templates/components/content.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/content.template.js +103 -14
- package/dist/scripts/generate-web-module/templates/components/content.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.d.ts +1 -1
- package/dist/scripts/generate-web-module/templates/components/editor.template.js +139 -150
- package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/index.d.ts +0 -1
- package/dist/scripts/generate-web-module/templates/components/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/index.js +1 -3
- package/dist/scripts/generate-web-module/templates/components/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/list-container.template.js +2 -2
- package/dist/scripts/generate-web-module/templates/components/list.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/list.template.js +6 -1
- package/dist/scripts/generate-web-module/templates/components/list.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/components/selector.template.js +48 -52
- package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/interface.template.js +38 -11
- package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/model.template.js +83 -26
- package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js +21 -1
- package/dist/scripts/generate-web-module/templates/data/service.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts +1 -1
- package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/index.js +1 -2
- package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/templates/module.template.js +11 -3
- package/dist/scripts/generate-web-module/templates/module.template.js.map +1 -1
- package/dist/scripts/generate-web-module/templates/table-hook.template.js +31 -1
- package/dist/scripts/generate-web-module/templates/table-hook.template.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts +0 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.js +0 -1
- package/dist/scripts/generate-web-module/transformers/import-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts +5 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.d.ts.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.js +21 -0
- package/dist/scripts/generate-web-module/transformers/relationship-key.js.map +1 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js +2 -0
- package/dist/scripts/generate-web-module/transformers/relationship-resolver.js.map +1 -1
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts +21 -0
- package/dist/scripts/generate-web-module/types/json-schema.interface.d.ts.map +1 -1
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +17 -0
- package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/testing/index.d.mts +1 -2
- package/dist/testing/index.d.ts +1 -2
- package/package.json +1 -6
- package/scripts/generate-web-module/__tests__/container.spec.ts +58 -0
- package/scripts/generate-web-module/__tests__/data-service.spec.ts +18 -0
- package/scripts/generate-web-module/__tests__/editor.spec.ts +36 -0
- package/scripts/generate-web-module/__tests__/fixtures.ts +43 -0
- package/scripts/generate-web-module/__tests__/model-interface.spec.ts +77 -0
- package/scripts/generate-web-module/__tests__/plumbing.spec.ts +21 -0
- package/scripts/generate-web-module/__tests__/selector.spec.ts +24 -0
- package/scripts/generate-web-module/__tests__/table-module-list.spec.ts +59 -0
- package/scripts/generate-web-module/generator.ts +14 -13
- package/scripts/generate-web-module/templates/components/container.template.ts +51 -66
- package/scripts/generate-web-module/templates/components/content.template.ts +107 -15
- package/scripts/generate-web-module/templates/components/editor.template.ts +140 -152
- package/scripts/generate-web-module/templates/components/index.ts +0 -1
- package/scripts/generate-web-module/templates/components/list-container.template.ts +2 -2
- package/scripts/generate-web-module/templates/components/list.template.ts +6 -1
- package/scripts/generate-web-module/templates/components/selector.template.ts +48 -52
- package/scripts/generate-web-module/templates/data/interface.template.ts +44 -12
- package/scripts/generate-web-module/templates/data/model.template.ts +88 -28
- package/scripts/generate-web-module/templates/data/service.template.ts +23 -1
- package/scripts/generate-web-module/templates/index.ts +0 -1
- package/scripts/generate-web-module/templates/module.template.ts +13 -3
- package/scripts/generate-web-module/templates/table-hook.template.ts +31 -2
- package/scripts/generate-web-module/transformers/import-resolver.ts +0 -2
- package/scripts/generate-web-module/transformers/relationship-key.ts +18 -0
- package/scripts/generate-web-module/transformers/relationship-resolver.ts +2 -0
- package/scripts/generate-web-module/types/json-schema.interface.ts +9 -0
- package/scripts/generate-web-module/types/template-data.interface.ts +7 -0
- package/src/client/config.ts +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/viewers/BlockNoteViewer.tsx +27 -0
- package/src/components/viewers/BlockNoteViewerContainer.tsx +10 -0
- package/src/core/registry/helpStore.ts +2 -19
- package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +4 -0
- package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -5
- package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +3 -2
- package/src/features/help/components/HelpArticleBody.tsx +27 -19
- package/src/features/help/components/HelpHint.tsx +20 -11
- package/src/features/help/components/HelpSearchResultRow.tsx +16 -6
- package/src/features/help/components/HelpSideNav.tsx +54 -19
- package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +1 -1
- package/src/features/help/components/__tests__/HelpHint.spec.tsx +17 -17
- package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +18 -19
- package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +20 -22
- package/src/features/help/contexts/HelpContext.spec.tsx +1 -3
- package/src/features/help/index.ts +3 -6
- package/src/features/help/interfaces/help-content-config.interface.ts +0 -5
- package/src/features/help/server-entry.ts +6 -15
- package/src/features/help/types/help-article.types.ts +2 -4
- package/src/features/how-to/HowToModule.ts +14 -1
- package/src/features/how-to/components/forms/HowToEditor.tsx +108 -4
- package/src/features/how-to/data/HowTo.spec.ts +43 -0
- package/src/features/how-to/data/HowTo.ts +47 -4
- package/src/features/how-to/data/HowToInterface.ts +14 -1
- package/src/features/how-to/data/HowToService.ts +61 -0
- package/dist/BlockNoteEditor-3XYBZLWO.js.map +0 -1
- package/dist/chunk-3J7RQBF3.js +0 -123
- package/dist/chunk-3J7RQBF3.js.map +0 -1
- package/dist/chunk-7E3O52U5.js.map +0 -1
- package/dist/chunk-CFI4WZ5R.js.map +0 -1
- package/dist/chunk-CQID6RCF.mjs +0 -38
- package/dist/chunk-CQID6RCF.mjs.map +0 -1
- package/dist/chunk-CRTVAQEK.mjs.map +0 -1
- package/dist/chunk-MZTKPPET.mjs +0 -123
- package/dist/chunk-MZTKPPET.mjs.map +0 -1
- package/dist/chunk-UHO3KUUH.js.map +0 -1
- package/dist/chunk-YQQHAFBS.js +0 -38
- package/dist/chunk-YQQHAFBS.js.map +0 -1
- package/dist/features/help/server/createHelpAssetRouteHandler.d.mts +0 -11
- package/dist/features/help/server/createHelpAssetRouteHandler.d.ts +0 -11
- package/dist/features/help/server/createHelpAssetRouteHandler.js +0 -43
- package/dist/features/help/server/createHelpAssetRouteHandler.js.map +0 -1
- package/dist/features/help/server/createHelpAssetRouteHandler.mjs +0 -43
- package/dist/features/help/server/createHelpAssetRouteHandler.mjs.map +0 -1
- package/dist/help-content-config.interface-B9L02u9i.d.mts +0 -50
- package/dist/help-content-config.interface-B9L02u9i.d.ts +0 -50
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts +0 -14
- package/dist/scripts/generate-web-module/templates/components/details.template.d.ts.map +0 -1
- package/dist/scripts/generate-web-module/templates/components/details.template.js +0 -112
- package/dist/scripts/generate-web-module/templates/components/details.template.js.map +0 -1
- package/scripts/generate-web-module/templates/components/details.template.ts +0 -116
- package/src/features/help/components/mdx/Callout.tsx +0 -21
- package/src/features/help/components/mdx/EntityRef.tsx +0 -18
- package/src/features/help/components/mdx/KeyBinding.tsx +0 -6
- package/src/features/help/components/mdx/Related.tsx +0 -33
- package/src/features/help/components/mdx/Screenshot.tsx +0 -9
- package/src/features/help/components/mdx/Steps.tsx +0 -21
- package/src/features/help/components/mdx/Video.tsx +0 -8
- package/src/features/help/components/mdx/mdx-server-components.ts +0 -23
- package/src/features/help/components/mdx/mdxComponents.ts +0 -9
- package/src/features/help/hooks/useHelpArticle.ts +0 -7
- package/src/features/help/hooks/useHelpManifest.ts +0 -5
- package/src/features/help/server/__tests__/createHelpAssetRouteHandler.spec.ts +0 -43
- package/src/features/help/server/createHelpAssetRouteHandler.ts +0 -35
- package/src/features/help/server/generateHelpArticleMetadata.ts +0 -18
- package/src/features/help/server/generateHelpArticleStaticParams.ts +0 -7
- package/src/features/help/server/generateHelpModeStaticParams.ts +0 -5
- package/src/features/help/server/getHelpContent.ts +0 -17
- package/src/features/help/server/index.ts +0 -8
- package/src/features/help/server/serializeHelpArticle.tsx +0 -46
- package/src/features/help/utils/__tests__/helpNavigation.spec.ts +0 -70
- package/src/features/help/utils/helpNavigation.ts +0 -29
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
import { configureJsonApi } from "../../../../client/config";
|
|
4
4
|
import { HelpProvider } from "../../contexts/HelpContext";
|
|
5
5
|
import { HelpHint } from "../HelpHint";
|
|
6
|
+
import { HowToService } from "../../../how-to/data/HowToService";
|
|
7
|
+
|
|
8
|
+
vi.mock("../../../how-to/data/HowToService", () => ({
|
|
9
|
+
HowToService: { findPublished: vi.fn() },
|
|
10
|
+
}));
|
|
6
11
|
|
|
7
12
|
const article = {
|
|
8
13
|
id: "1",
|
|
9
14
|
slug: "x",
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
howToType: "how-to",
|
|
16
|
+
name: "X",
|
|
12
17
|
summary: "S",
|
|
13
|
-
order: 1,
|
|
14
|
-
tags: [],
|
|
15
18
|
contextualKeys: ["npc.editor"],
|
|
16
|
-
aiIndexed: true,
|
|
17
19
|
draft: false,
|
|
18
|
-
|
|
19
|
-
path: "how-to/x.mdx",
|
|
20
|
-
headings: [],
|
|
21
|
-
relatedSlugs: [],
|
|
22
|
-
lastUpdated: "2026-01-01T00:00:00Z",
|
|
23
|
-
};
|
|
20
|
+
} as any;
|
|
24
21
|
|
|
25
22
|
beforeEach(() => {
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
vi.mocked(HowToService.findPublished).mockResolvedValue([article]);
|
|
26
25
|
configureJsonApi({
|
|
27
26
|
apiUrl: "http://localhost",
|
|
28
|
-
helpContent: {
|
|
27
|
+
helpContent: { brand: { label: "narr8" } },
|
|
29
28
|
});
|
|
30
29
|
});
|
|
31
30
|
|
|
32
31
|
describe("HelpHint", () => {
|
|
33
|
-
it("renders null when no article matches the contextKey", () => {
|
|
32
|
+
it("renders null when no published article matches the contextKey", async () => {
|
|
34
33
|
const { container } = render(
|
|
35
34
|
<HelpProvider>
|
|
36
35
|
<HelpHint contextKey="nope.absent" />
|
|
37
36
|
</HelpProvider>,
|
|
38
37
|
);
|
|
38
|
+
await waitFor(() => expect(HowToService.findPublished).toHaveBeenCalled());
|
|
39
39
|
expect(container.querySelector("button")).toBeNull();
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
it("renders the trigger when at least one article matches", () => {
|
|
42
|
+
it("renders the trigger when at least one published article matches", async () => {
|
|
43
43
|
render(
|
|
44
44
|
<HelpProvider>
|
|
45
45
|
<HelpHint contextKey="npc.editor" />
|
|
46
46
|
</HelpProvider>,
|
|
47
47
|
);
|
|
48
|
-
expect(screen.
|
|
48
|
+
expect(await screen.findByLabelText(/help on this|hint/i)).toBeTruthy();
|
|
49
49
|
});
|
|
50
50
|
});
|
|
@@ -1,37 +1,34 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
2
|
-
import { render, screen } from "@testing-library/react";
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import { render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
import { Command, CommandList } from "../../../../shadcnui";
|
|
4
4
|
import { configureJsonApi } from "../../../../client/config";
|
|
5
5
|
import { HelpProvider } from "../../contexts/HelpContext";
|
|
6
6
|
import { HelpSearchResultRow } from "../HelpSearchResultRow";
|
|
7
|
+
import { HowToService } from "../../../how-to/data/HowToService";
|
|
8
|
+
|
|
9
|
+
vi.mock("../../../how-to/data/HowToService", () => ({
|
|
10
|
+
HowToService: { findOne: vi.fn() },
|
|
11
|
+
}));
|
|
7
12
|
|
|
8
13
|
const article = {
|
|
9
14
|
id: "abc",
|
|
10
15
|
slug: "x",
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
howToType: "how-to",
|
|
17
|
+
name: "X title",
|
|
13
18
|
summary: "S",
|
|
14
|
-
|
|
15
|
-
tags: [],
|
|
16
|
-
contextualKeys: [],
|
|
17
|
-
aiIndexed: true,
|
|
18
|
-
draft: false,
|
|
19
|
-
contentHash: "h",
|
|
20
|
-
path: "how-to/x.mdx",
|
|
21
|
-
headings: [],
|
|
22
|
-
relatedSlugs: [],
|
|
23
|
-
lastUpdated: "2026-01-01T00:00:00Z",
|
|
24
|
-
};
|
|
19
|
+
} as any;
|
|
25
20
|
|
|
26
21
|
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
27
23
|
configureJsonApi({
|
|
28
24
|
apiUrl: "http://localhost",
|
|
29
|
-
helpContent: {
|
|
25
|
+
helpContent: { brand: { label: "narr8" } },
|
|
30
26
|
});
|
|
31
27
|
});
|
|
32
28
|
|
|
33
29
|
describe("HelpSearchResultRow", () => {
|
|
34
|
-
it("renders the article title when
|
|
30
|
+
it("renders the article title when the service returns the how-to", async () => {
|
|
31
|
+
vi.mocked(HowToService.findOne).mockResolvedValue(article);
|
|
35
32
|
render(
|
|
36
33
|
<HelpProvider>
|
|
37
34
|
<Command>
|
|
@@ -41,10 +38,11 @@ describe("HelpSearchResultRow", () => {
|
|
|
41
38
|
</Command>
|
|
42
39
|
</HelpProvider>,
|
|
43
40
|
);
|
|
44
|
-
expect(screen.
|
|
41
|
+
expect(await screen.findByText("X title")).toBeTruthy();
|
|
45
42
|
});
|
|
46
43
|
|
|
47
|
-
it("renders disabled removed-state row when
|
|
44
|
+
it("renders disabled removed-state row when the service has no how-to", async () => {
|
|
45
|
+
vi.mocked(HowToService.findOne).mockRejectedValue(new Error("not found"));
|
|
48
46
|
render(
|
|
49
47
|
<HelpProvider>
|
|
50
48
|
<Command>
|
|
@@ -54,6 +52,7 @@ describe("HelpSearchResultRow", () => {
|
|
|
54
52
|
</Command>
|
|
55
53
|
</HelpProvider>,
|
|
56
54
|
);
|
|
55
|
+
await waitFor(() => expect(HowToService.findOne).toHaveBeenCalledWith({ id: "ghost" }));
|
|
57
56
|
expect(screen.queryByText("X title")).toBeNull();
|
|
58
57
|
});
|
|
59
58
|
});
|
|
@@ -1,50 +1,48 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
2
2
|
import { render, screen, fireEvent } from "@testing-library/react";
|
|
3
3
|
import { configureJsonApi } from "../../../../client/config";
|
|
4
4
|
import { HelpProvider } from "../../contexts/HelpContext";
|
|
5
5
|
import { HelpSideNav } from "../HelpSideNav";
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
7
|
+
// HelpSideNav now sources its list from the public API via
|
|
8
|
+
// HowToService.findPublished() (Plan 3b) instead of the help manifest, so the
|
|
9
|
+
// test mocks the service rather than seeding the manifest.
|
|
10
|
+
vi.mock("../../../how-to/data/HowToService", () => ({
|
|
11
|
+
HowToService: { findPublished: vi.fn() },
|
|
12
|
+
}));
|
|
13
|
+
import { HowToService } from "../../../how-to/data/HowToService";
|
|
14
|
+
|
|
15
|
+
const published = [
|
|
16
|
+
{ id: "a", name: "Alpha", summary: "s", tags: [], howToType: "how-to", slug: "a", order: 1, draft: false },
|
|
17
|
+
{ id: "b", name: "Beta", summary: "s", tags: [], howToType: "how-to", slug: "b", order: 2, draft: false },
|
|
18
|
+
];
|
|
17
19
|
|
|
18
20
|
beforeEach(() => {
|
|
19
21
|
configureJsonApi({
|
|
20
22
|
apiUrl: "http://localhost",
|
|
21
|
-
helpContent: {
|
|
22
|
-
manifest: [
|
|
23
|
-
{ ...base, id: "a", slug: "a", mode: "how-to", title: "Alpha", summary: "s", order: 1, path: "how-to/a.mdx" },
|
|
24
|
-
{ ...base, id: "b", slug: "b", mode: "how-to", title: "Beta", summary: "s", order: 2, path: "how-to/b.mdx" },
|
|
25
|
-
],
|
|
26
|
-
namespaceUuid: "00000000-0000-5000-8000-000000000000",
|
|
27
|
-
},
|
|
23
|
+
helpContent: { brand: { label: "narr8" } },
|
|
28
24
|
});
|
|
25
|
+
(HowToService.findPublished as ReturnType<typeof vi.fn>).mockResolvedValue(published as any);
|
|
29
26
|
});
|
|
30
27
|
|
|
31
28
|
describe("HelpSideNav", () => {
|
|
32
|
-
it("lists every non-draft article", () => {
|
|
29
|
+
it("lists every non-draft article", async () => {
|
|
33
30
|
render(
|
|
34
31
|
<HelpProvider>
|
|
35
32
|
<HelpSideNav />
|
|
36
33
|
</HelpProvider>,
|
|
37
34
|
);
|
|
38
|
-
expect(screen.
|
|
39
|
-
expect(screen.
|
|
35
|
+
expect(await screen.findByText("Alpha")).toBeTruthy();
|
|
36
|
+
expect(await screen.findByText("Beta")).toBeTruthy();
|
|
40
37
|
});
|
|
41
38
|
|
|
42
|
-
it("filters by query", () => {
|
|
39
|
+
it("filters by query", async () => {
|
|
43
40
|
render(
|
|
44
41
|
<HelpProvider>
|
|
45
42
|
<HelpSideNav />
|
|
46
43
|
</HelpProvider>,
|
|
47
44
|
);
|
|
45
|
+
await screen.findByText("Alpha");
|
|
48
46
|
fireEvent.change(screen.getByPlaceholderText(/filter/i), { target: { value: "Bet" } });
|
|
49
47
|
expect(screen.queryByText("Alpha")).toBeNull();
|
|
50
48
|
expect(screen.getByText("Beta")).toBeTruthy();
|
|
@@ -6,8 +6,6 @@ import { HelpProvider, useHelp } from "./HelpContext";
|
|
|
6
6
|
|
|
7
7
|
describe("HelpProvider / useHelp", () => {
|
|
8
8
|
const cfg = {
|
|
9
|
-
manifest: [],
|
|
10
|
-
namespaceUuid: "00000000-0000-5000-8000-000000000000",
|
|
11
9
|
brand: { logo: "/logo.png", label: "Test", appHref: "/" },
|
|
12
10
|
};
|
|
13
11
|
|
|
@@ -18,8 +16,8 @@ describe("HelpProvider / useHelp", () => {
|
|
|
18
16
|
it("exposes the configured helpContent via useHelp()", () => {
|
|
19
17
|
const wrapper = ({ children }: { children: ReactNode }) => <HelpProvider>{children}</HelpProvider>;
|
|
20
18
|
const { result } = renderHook(() => useHelp(), { wrapper });
|
|
21
|
-
expect(result.current.manifest).toBe(cfg.manifest);
|
|
22
19
|
expect(result.current.brand?.label).toBe("Test");
|
|
20
|
+
expect(result.current.brand?.appHref).toBe("/");
|
|
23
21
|
});
|
|
24
22
|
|
|
25
23
|
it("throws when useHelp() is called outside HelpProvider", () => {
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
// Consumed via `@carlonicora/nextjs-jsonapi/help`. Tsup adds a top-level
|
|
3
3
|
// "use client" directive to the bundled output via clientEntries.
|
|
4
4
|
//
|
|
5
|
-
// Pair this with `@carlonicora/nextjs-jsonapi/help/server`
|
|
6
|
-
//
|
|
5
|
+
// Pair this with `@carlonicora/nextjs-jsonapi/help/server` for the server-safe
|
|
6
|
+
// HELP_MODES constant + article types (imported by server-component pages).
|
|
7
7
|
// Consumers compose page shells themselves: import the client primitives below
|
|
8
8
|
// (HelpProvider, HelpHeader, HelpSideNav, HelpArticleBody, HelpTOC, etc.) and
|
|
9
|
-
//
|
|
9
|
+
// assemble pages in app routes.
|
|
10
10
|
|
|
11
11
|
export { HelpProvider, useHelp } from "./contexts/HelpContext";
|
|
12
12
|
export { HelpHeader } from "./components/HelpHeader";
|
|
@@ -16,9 +16,6 @@ export { HelpTOC } from "./components/HelpTOC";
|
|
|
16
16
|
export { HelpHint } from "./components/HelpHint";
|
|
17
17
|
export { HelpAskAi } from "./components/HelpAskAi";
|
|
18
18
|
export { HelpSearchResultRow } from "./components/HelpSearchResultRow";
|
|
19
|
-
export { MDX_COMPONENTS } from "./components/mdx/mdxComponents";
|
|
20
|
-
export { useHelpManifest } from "./hooks/useHelpManifest";
|
|
21
|
-
export { useHelpArticle } from "./hooks/useHelpArticle";
|
|
22
19
|
export { useHelpFilter } from "./hooks/useHelpFilter";
|
|
23
20
|
export { articleUrl, modeUrl } from "./utils/articleUrl";
|
|
24
21
|
export { HELP_I18N_KEYS } from "./i18n-keys";
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import type { HelpArticle, HelpRedirect } from "../types/help-article.types";
|
|
2
|
-
|
|
3
1
|
export interface HelpBrandConfig {
|
|
4
2
|
/** URL of the brand logo image (served by the consuming app). */
|
|
5
3
|
logo?: string;
|
|
@@ -10,8 +8,5 @@ export interface HelpBrandConfig {
|
|
|
10
8
|
}
|
|
11
9
|
|
|
12
10
|
export interface HelpContentConfig {
|
|
13
|
-
manifest: readonly HelpArticle[];
|
|
14
|
-
namespaceUuid: string;
|
|
15
|
-
redirects?: readonly HelpRedirect[];
|
|
16
11
|
brand?: HelpBrandConfig;
|
|
17
12
|
}
|
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
// Server-
|
|
1
|
+
// Server-safe public barrel for the help feature.
|
|
2
2
|
// Consumed via `@carlonicora/nextjs-jsonapi/help/server`. Must NOT receive a
|
|
3
|
-
// "use client" directive.
|
|
3
|
+
// "use client" directive — server components (e.g. the /help index + mode pages)
|
|
4
|
+
// import HELP_MODES/HelpMode from here without pulling the client bundle.
|
|
4
5
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// without leaking client-only modules (react-dom/client, hook usage at module
|
|
9
|
-
// init) into server scopes. Consumers therefore compose the page shells in
|
|
10
|
-
// their own route files, importing client primitives from
|
|
11
|
-
// `@carlonicora/nextjs-jsonapi/help` and server utilities from here.
|
|
6
|
+
// Now that help articles are served from the API (HowTo) rather than the MDX
|
|
7
|
+
// manifest, this barrel exposes only the shared mode constant + article types.
|
|
8
|
+
// Client primitives live in `@carlonicora/nextjs-jsonapi/help`.
|
|
12
9
|
|
|
13
|
-
export { getHelpContent } from "./server/getHelpContent";
|
|
14
|
-
export { renderHelpArticle, serializeHelpArticle } from "./server/serializeHelpArticle";
|
|
15
|
-
export { generateHelpArticleStaticParams, generateHelpArticleMetadata, generateHelpModeStaticParams } from "./server";
|
|
16
|
-
export { findHelpArticle, prevNextWithinMode, buildHelpNav } from "./utils/helpNavigation";
|
|
17
|
-
export { setHelpReader } from "../../core/registry/helpStore";
|
|
18
|
-
export type { HelpContentConfig, HelpBrandConfig } from "./interfaces/help-content-config.interface";
|
|
19
10
|
export { HELP_MODES } from "./types/help-article.types";
|
|
20
11
|
export type { HelpArticle, HelpHeading, HelpMode, HelpRedirect } from "./types/help-article.types";
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* decoupled from `@carlonicora/nestjs-neo4jsonapi`.
|
|
4
|
-
* `packages/nestjs-neo4jsonapi/src/foundations/help-content-sync/interfaces/help-article.interface.ts`.
|
|
2
|
+
* Help-article types for the frontend help feature. Self-contained — the
|
|
3
|
+
* published frontend library stays decoupled from `@carlonicora/nestjs-neo4jsonapi`.
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
6
|
export const HELP_MODES = ["tutorial", "how-to", "reference", "explanation"] as const;
|
|
@@ -24,7 +23,6 @@ export interface HelpArticle {
|
|
|
24
23
|
contextualKeys: readonly string[];
|
|
25
24
|
aiIndexed: boolean;
|
|
26
25
|
draft: boolean;
|
|
27
|
-
contentHash: string;
|
|
28
26
|
path: string;
|
|
29
27
|
headings: readonly HelpHeading[];
|
|
30
28
|
relatedSlugs: readonly string[];
|
|
@@ -12,7 +12,20 @@ export const HowToModule = (factory: ModuleFactory) =>
|
|
|
12
12
|
icon: LifeBuoyIcon,
|
|
13
13
|
inclusions: {
|
|
14
14
|
lists: {
|
|
15
|
-
fields: [
|
|
15
|
+
fields: [
|
|
16
|
+
createJsonApiInclusion("howtos", [
|
|
17
|
+
"name",
|
|
18
|
+
"description",
|
|
19
|
+
"pages",
|
|
20
|
+
"howToType",
|
|
21
|
+
"slug",
|
|
22
|
+
"order",
|
|
23
|
+
"summary",
|
|
24
|
+
"tags",
|
|
25
|
+
"contextualKeys",
|
|
26
|
+
"draft",
|
|
27
|
+
]),
|
|
28
|
+
],
|
|
16
29
|
},
|
|
17
30
|
},
|
|
18
31
|
});
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
4
4
|
import { PlusIcon, SearchIcon, XIcon } from "lucide-react";
|
|
5
5
|
import { useTranslations } from "next-intl";
|
|
6
|
-
import { ReactNode, useCallback, useMemo, useState } from "react";
|
|
6
|
+
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { useForm } from "react-hook-form";
|
|
8
8
|
import { v4 } from "uuid";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
|
-
import { EditorSheet, FormInput } from "../../../../components";
|
|
11
|
+
import { EditorSheet, FormInput, FormSelect, FormTextarea, FormCheckbox } from "../../../../components";
|
|
12
12
|
import { BlockNoteEditorContainer } from "../../../../components";
|
|
13
13
|
import { ModuleRegistry, Modules } from "../../../../core";
|
|
14
14
|
import { useI18nRouter } from "../../../../hooks";
|
|
@@ -27,6 +27,9 @@ import {
|
|
|
27
27
|
import { HowTo } from "../../data/HowTo";
|
|
28
28
|
import { HowToInput, HowToInterface } from "../../data/HowToInterface";
|
|
29
29
|
import { HowToService } from "../../data/HowToService";
|
|
30
|
+
import HowToMultiSelector from "./HowToMultiSelector";
|
|
31
|
+
|
|
32
|
+
const HOW_TO_TYPES = ["tutorial", "how-to", "reference", "explanation"] as const;
|
|
30
33
|
|
|
31
34
|
function PageSelector({
|
|
32
35
|
value,
|
|
@@ -140,6 +143,14 @@ function HowToEditorInternal({
|
|
|
140
143
|
name: z.string().min(1, { message: t(`howto.fields.name.error`) }),
|
|
141
144
|
description: z.any(),
|
|
142
145
|
pages: z.array(z.string()),
|
|
146
|
+
howToType: z.enum(HOW_TO_TYPES),
|
|
147
|
+
slug: z.string().min(1, { message: t(`howto.fields.slug.error`) }),
|
|
148
|
+
order: z.number().int().min(0),
|
|
149
|
+
summary: z.string().optional(),
|
|
150
|
+
tags: z.string().optional(),
|
|
151
|
+
contextualKeys: z.string().optional(),
|
|
152
|
+
draft: z.boolean().optional(),
|
|
153
|
+
relatedArticles: z.array(z.object({ id: z.string().uuid(), name: z.string() })).optional(),
|
|
143
154
|
}),
|
|
144
155
|
[t],
|
|
145
156
|
);
|
|
@@ -150,6 +161,14 @@ function HowToEditorInternal({
|
|
|
150
161
|
name: howTo?.name || "",
|
|
151
162
|
description: howTo?.description || [],
|
|
152
163
|
pages: HowTo.parsePagesFromString(howTo?.pages),
|
|
164
|
+
howToType: (howTo?.howToType as (typeof HOW_TO_TYPES)[number]) ?? "how-to",
|
|
165
|
+
slug: howTo?.slug ?? "",
|
|
166
|
+
order: howTo?.order ?? 0,
|
|
167
|
+
summary: howTo?.summary ?? "",
|
|
168
|
+
tags: (howTo?.tags ?? []).join(", "),
|
|
169
|
+
contextualKeys: (howTo?.contextualKeys ?? []).join(", "),
|
|
170
|
+
draft: howTo?.draft ?? false,
|
|
171
|
+
relatedArticles: [] as { id: string; name: string }[],
|
|
153
172
|
}),
|
|
154
173
|
[howTo],
|
|
155
174
|
);
|
|
@@ -159,6 +178,26 @@ function HowToEditorInternal({
|
|
|
159
178
|
defaultValues: getDefaultValues(),
|
|
160
179
|
});
|
|
161
180
|
|
|
181
|
+
const initialRelatedIds = useRef<string[]>([]);
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (!howTo?.howToType || !howTo?.slug) return;
|
|
184
|
+
let active = true;
|
|
185
|
+
HowToService.findRelated({ howToType: howTo.howToType, slug: howTo.slug })
|
|
186
|
+
.then((list) => {
|
|
187
|
+
if (!active) return;
|
|
188
|
+
initialRelatedIds.current = list.map((r) => r.id);
|
|
189
|
+
form.setValue(
|
|
190
|
+
"relatedArticles",
|
|
191
|
+
list.map((r) => ({ id: r.id, name: r.name })),
|
|
192
|
+
{ shouldDirty: false },
|
|
193
|
+
);
|
|
194
|
+
})
|
|
195
|
+
.catch(() => {});
|
|
196
|
+
return () => {
|
|
197
|
+
active = false;
|
|
198
|
+
};
|
|
199
|
+
}, [howTo, form]);
|
|
200
|
+
|
|
162
201
|
const handleDescriptionChange = useCallback(
|
|
163
202
|
(content: any) => {
|
|
164
203
|
form.setValue("description", content, { shouldDirty: true });
|
|
@@ -191,17 +230,40 @@ function HowToEditorInternal({
|
|
|
191
230
|
propagateChanges={propagateChanges}
|
|
192
231
|
size="lg"
|
|
193
232
|
onSubmit={async (values) => {
|
|
233
|
+
const toList = (s?: string) =>
|
|
234
|
+
(s ?? "")
|
|
235
|
+
.split(",")
|
|
236
|
+
.map((x) => x.trim())
|
|
237
|
+
.filter(Boolean);
|
|
238
|
+
|
|
194
239
|
const payload: HowToInput = {
|
|
195
240
|
id: values.id,
|
|
196
241
|
name: values.name,
|
|
197
242
|
authorId: "",
|
|
198
243
|
description: values.description,
|
|
199
244
|
pages: HowTo.serializePagesToString(values.pages),
|
|
245
|
+
howToType: values.howToType,
|
|
246
|
+
slug: values.slug,
|
|
247
|
+
order: values.order,
|
|
248
|
+
summary: values.summary,
|
|
249
|
+
tags: toList(values.tags),
|
|
250
|
+
contextualKeys: toList(values.contextualKeys),
|
|
251
|
+
draft: values.draft ?? false,
|
|
200
252
|
};
|
|
201
253
|
|
|
202
|
-
const
|
|
254
|
+
const saved = howTo ? await HowToService.update(payload) : await HowToService.create(payload);
|
|
203
255
|
|
|
204
|
-
|
|
256
|
+
const desired = new Set((values.relatedArticles ?? []).map((r) => r.id));
|
|
257
|
+
const current = new Set(initialRelatedIds.current);
|
|
258
|
+
for (const relatedId of desired) {
|
|
259
|
+
if (!current.has(relatedId)) await HowToService.addRelated({ howToId: values.id, relatedId });
|
|
260
|
+
}
|
|
261
|
+
for (const relatedId of current) {
|
|
262
|
+
if (!desired.has(relatedId)) await HowToService.removeRelated({ howToId: values.id, relatedId });
|
|
263
|
+
}
|
|
264
|
+
initialRelatedIds.current = Array.from(desired);
|
|
265
|
+
|
|
266
|
+
return saved;
|
|
205
267
|
}}
|
|
206
268
|
onReset={() => {
|
|
207
269
|
return getDefaultValues();
|
|
@@ -222,6 +284,28 @@ function HowToEditorInternal({
|
|
|
222
284
|
placeholder={t(`howto.fields.name.placeholder`)}
|
|
223
285
|
isRequired
|
|
224
286
|
/>
|
|
287
|
+
<FormSelect
|
|
288
|
+
form={form}
|
|
289
|
+
id="howToType"
|
|
290
|
+
name={t(`howto.fields.howToType.label`)}
|
|
291
|
+
placeholder={t(`howto.fields.howToType.placeholder`)}
|
|
292
|
+
values={HOW_TO_TYPES.map((m) => ({ id: m, text: t(`howto.howToType.${m}` as any) }))}
|
|
293
|
+
isRequired
|
|
294
|
+
/>
|
|
295
|
+
<FormInput
|
|
296
|
+
form={form}
|
|
297
|
+
id="slug"
|
|
298
|
+
name={t(`howto.fields.slug.label`)}
|
|
299
|
+
placeholder={t(`howto.fields.slug.placeholder`)}
|
|
300
|
+
isRequired
|
|
301
|
+
/>
|
|
302
|
+
<FormInput form={form} id="order" name={t(`howto.fields.order.label`)} type="number" />
|
|
303
|
+
<FormTextarea
|
|
304
|
+
form={form}
|
|
305
|
+
id="summary"
|
|
306
|
+
name={t(`howto.fields.summary.label`)}
|
|
307
|
+
placeholder={t(`howto.fields.summary.placeholder`)}
|
|
308
|
+
/>
|
|
225
309
|
<div className="space-y-2">
|
|
226
310
|
<Label>{t(`howto.fields.description.label`)}</Label>
|
|
227
311
|
<div className="max-h-80 overflow-y-auto rounded-md border">
|
|
@@ -260,6 +344,26 @@ function HowToEditorInternal({
|
|
|
260
344
|
</Button>
|
|
261
345
|
</div>
|
|
262
346
|
</div>
|
|
347
|
+
<FormInput
|
|
348
|
+
form={form}
|
|
349
|
+
id="tags"
|
|
350
|
+
name={t(`howto.fields.tags.label`)}
|
|
351
|
+
placeholder={t(`howto.fields.tags.placeholder`)}
|
|
352
|
+
/>
|
|
353
|
+
<FormInput
|
|
354
|
+
form={form}
|
|
355
|
+
id="contextualKeys"
|
|
356
|
+
name={t(`howto.fields.contextualKeys.label`)}
|
|
357
|
+
placeholder={t(`howto.fields.contextualKeys.placeholder`)}
|
|
358
|
+
/>
|
|
359
|
+
<HowToMultiSelector
|
|
360
|
+
id="relatedArticles"
|
|
361
|
+
form={form}
|
|
362
|
+
currentHowTo={howTo}
|
|
363
|
+
label={t(`howto.fields.relatedArticles.label`)}
|
|
364
|
+
placeholder={t(`howto.fields.relatedArticles.placeholder`)}
|
|
365
|
+
/>
|
|
366
|
+
<FormCheckbox form={form} id="draft" name={t(`howto.fields.draft.label`)} />
|
|
263
367
|
</div>
|
|
264
368
|
</EditorSheet>
|
|
265
369
|
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { beforeAll, describe, it, expect } from "vitest";
|
|
2
|
+
import { ApiRequestDataTypeInterface } from "../../../core/interfaces/ApiRequestDataTypeInterface";
|
|
3
|
+
import { ModuleRegistry } from "../../../core/registry/ModuleRegistry";
|
|
4
|
+
import { HowTo } from "./HowTo";
|
|
5
|
+
|
|
6
|
+
// createJsonApi reads Modules.HowTo.name (resolved lazily via ModuleRegistry),
|
|
7
|
+
// so the module must be registered before the model serialises — mirrors the
|
|
8
|
+
// AssistantMessage.spec setup convention in this package.
|
|
9
|
+
const howToModule: ApiRequestDataTypeInterface = {
|
|
10
|
+
name: "howtos",
|
|
11
|
+
model: HowTo,
|
|
12
|
+
} as any;
|
|
13
|
+
|
|
14
|
+
beforeAll(() => {
|
|
15
|
+
ModuleRegistry.register("HowTo", howToModule);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe("HowTo.createJsonApi", () => {
|
|
19
|
+
it("serialises the new attributes", () => {
|
|
20
|
+
const res = new HowTo().createJsonApi({
|
|
21
|
+
id: "11111111-1111-4111-8111-111111111111",
|
|
22
|
+
name: "Add an NPC",
|
|
23
|
+
authorId: "",
|
|
24
|
+
description: [{ type: "paragraph", content: [] }],
|
|
25
|
+
howToType: "how-to",
|
|
26
|
+
slug: "add-an-npc",
|
|
27
|
+
order: 30,
|
|
28
|
+
summary: "Create a Non-Player Character.",
|
|
29
|
+
tags: ["world"],
|
|
30
|
+
contextualKeys: ["npc.editor"],
|
|
31
|
+
draft: false,
|
|
32
|
+
});
|
|
33
|
+
const a = res.data.attributes;
|
|
34
|
+
expect(a.howToType).toBe("how-to");
|
|
35
|
+
expect(a.slug).toBe("add-an-npc");
|
|
36
|
+
expect(a.order).toBe(30);
|
|
37
|
+
expect(a.summary).toBe("Create a Non-Player Character.");
|
|
38
|
+
expect(a.tags).toEqual(["world"]);
|
|
39
|
+
expect(a.contextualKeys).toEqual(["npc.editor"]);
|
|
40
|
+
expect(a.draft).toBe(false);
|
|
41
|
+
expect(typeof a.description).toBe("string"); // existing: BlockNote JSON stringified
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -5,7 +5,13 @@ import { HowToInput, HowToInterface } from "./HowToInterface";
|
|
|
5
5
|
export class HowTo extends Content implements HowToInterface {
|
|
6
6
|
private _description?: any;
|
|
7
7
|
private _pages?: string;
|
|
8
|
-
private
|
|
8
|
+
private _howToType?: string;
|
|
9
|
+
private _slug?: string;
|
|
10
|
+
private _order?: number;
|
|
11
|
+
private _summary?: string;
|
|
12
|
+
private _tags?: string[];
|
|
13
|
+
private _contextualKeys?: string[];
|
|
14
|
+
private _draft?: boolean;
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
17
|
* Parse pages from backend JSON string (handles legacy single string + JSON array)
|
|
@@ -37,8 +43,32 @@ export class HowTo extends Content implements HowToInterface {
|
|
|
37
43
|
return this._pages;
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
get
|
|
41
|
-
return this.
|
|
46
|
+
get howToType(): string | undefined {
|
|
47
|
+
return this._howToType;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get slug(): string | undefined {
|
|
51
|
+
return this._slug;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get order(): number | undefined {
|
|
55
|
+
return this._order;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get summary(): string | undefined {
|
|
59
|
+
return this._summary;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get tags(): string[] {
|
|
63
|
+
return this._tags ?? [];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get contextualKeys(): string[] {
|
|
67
|
+
return this._contextualKeys ?? [];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get draft(): boolean {
|
|
71
|
+
return this._draft ?? false;
|
|
42
72
|
}
|
|
43
73
|
|
|
44
74
|
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
@@ -60,7 +90,13 @@ export class HowTo extends Content implements HowToInterface {
|
|
|
60
90
|
this._description = rawDescription;
|
|
61
91
|
}
|
|
62
92
|
this._pages = data.jsonApi.attributes.pages;
|
|
63
|
-
this.
|
|
93
|
+
this._howToType = data.jsonApi.attributes.howToType;
|
|
94
|
+
this._slug = data.jsonApi.attributes.slug;
|
|
95
|
+
this._order = data.jsonApi.attributes.order;
|
|
96
|
+
this._summary = data.jsonApi.attributes.summary;
|
|
97
|
+
this._tags = data.jsonApi.attributes.tags ?? [];
|
|
98
|
+
this._contextualKeys = data.jsonApi.attributes.contextualKeys ?? [];
|
|
99
|
+
this._draft = data.jsonApi.attributes.draft ?? false;
|
|
64
100
|
|
|
65
101
|
return this;
|
|
66
102
|
}
|
|
@@ -81,6 +117,13 @@ export class HowTo extends Content implements HowToInterface {
|
|
|
81
117
|
|
|
82
118
|
if (data.description !== undefined) response.data.attributes.description = JSON.stringify(data.description);
|
|
83
119
|
if (data.pages !== undefined) response.data.attributes.pages = data.pages;
|
|
120
|
+
if (data.howToType !== undefined) response.data.attributes.howToType = data.howToType;
|
|
121
|
+
if (data.slug !== undefined) response.data.attributes.slug = data.slug;
|
|
122
|
+
if (data.order !== undefined) response.data.attributes.order = data.order;
|
|
123
|
+
if (data.summary !== undefined) response.data.attributes.summary = data.summary;
|
|
124
|
+
if (data.tags !== undefined) response.data.attributes.tags = data.tags;
|
|
125
|
+
if (data.contextualKeys !== undefined) response.data.attributes.contextualKeys = data.contextualKeys;
|
|
126
|
+
if (data.draft !== undefined) response.data.attributes.draft = data.draft;
|
|
84
127
|
|
|
85
128
|
return response;
|
|
86
129
|
}
|