@carlonicora/nextjs-jsonapi 1.108.0 → 1.110.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.
Files changed (144) hide show
  1. package/dist/{AssistantInterface-BYgI5z1-.d.mts → AssistantInterface-B1c8FhGA.d.mts} +2 -0
  2. package/dist/{AssistantInterface-DfDcz0gJ.d.ts → AssistantInterface-BBUHxOCd.d.ts} +2 -0
  3. package/dist/{AssistantMessageInterface-BpEhx2pC.d.ts → AssistantMessageInterface-BNzf1etU.d.ts} +16 -1
  4. package/dist/{AssistantMessageInterface-DJ3Me16Y.d.mts → AssistantMessageInterface-DrgHDjZ5.d.mts} +16 -1
  5. package/dist/{AuthComponent-B6DIk8Vf.d.ts → AuthComponent-Cd7lcYif.d.ts} +1 -1
  6. package/dist/{AuthComponent-BKI0ZbtD.d.mts → AuthComponent-DdxCFgUZ.d.mts} +1 -1
  7. package/dist/{BlockNoteEditor-2AXSTGGG.js → BlockNoteEditor-7OKW6HHN.js} +19 -19
  8. package/dist/{BlockNoteEditor-2AXSTGGG.js.map → BlockNoteEditor-7OKW6HHN.js.map} +1 -1
  9. package/dist/{BlockNoteEditor-XVIBGXHF.mjs → BlockNoteEditor-K7IIWM3S.mjs} +4 -4
  10. package/dist/BlockNoteViewer-OPSCTHKJ.mjs +31 -0
  11. package/dist/BlockNoteViewer-OPSCTHKJ.mjs.map +1 -0
  12. package/dist/BlockNoteViewer-UW5VZ5UF.js +31 -0
  13. package/dist/BlockNoteViewer-UW5VZ5UF.js.map +1 -0
  14. package/dist/{auth.interface-BBUgMZzs.d.ts → auth.interface-8b601idJ.d.ts} +1 -1
  15. package/dist/{auth.interface-XYEREOD6.d.mts → auth.interface-CXBF8Mhi.d.mts} +1 -1
  16. package/dist/billing/index.d.mts +41 -41
  17. package/dist/billing/index.d.ts +41 -41
  18. package/dist/billing/index.js +346 -346
  19. package/dist/billing/index.mjs +3 -3
  20. package/dist/{chunk-VLDLERJN.js → chunk-6XWW67F2.js} +29 -9
  21. package/dist/chunk-6XWW67F2.js.map +1 -0
  22. package/dist/{chunk-HC3JFN3C.js → chunk-AKXUNE3A.js} +1004 -862
  23. package/dist/chunk-AKXUNE3A.js.map +1 -0
  24. package/dist/{chunk-CFECWLHH.mjs → chunk-CWVYU4YZ.mjs} +23 -3
  25. package/dist/chunk-CWVYU4YZ.mjs.map +1 -0
  26. package/dist/{chunk-56XBGQGU.mjs → chunk-TXVVZLUQ.mjs} +1422 -1280
  27. package/dist/chunk-TXVVZLUQ.mjs.map +1 -0
  28. package/dist/{chunk-RXXZGPC3.js → chunk-U2HR57MY.js} +265 -113
  29. package/dist/chunk-U2HR57MY.js.map +1 -0
  30. package/dist/chunk-WPAQRB5Q.mjs +7 -0
  31. package/dist/chunk-WPAQRB5Q.mjs.map +1 -0
  32. package/dist/chunk-YBZVWLPH.js +7 -0
  33. package/dist/chunk-YBZVWLPH.js.map +1 -0
  34. package/dist/{chunk-N3NVIPSU.mjs → chunk-ZYPNVEHB.mjs} +235 -83
  35. package/dist/{chunk-N3NVIPSU.mjs.map → chunk-ZYPNVEHB.mjs.map} +1 -1
  36. package/dist/client/index.d.mts +9 -18
  37. package/dist/client/index.d.ts +9 -18
  38. package/dist/client/index.js +4 -4
  39. package/dist/client/index.mjs +3 -3
  40. package/dist/components/index.d.mts +421 -417
  41. package/dist/components/index.d.ts +421 -417
  42. package/dist/components/index.js +6 -4
  43. package/dist/components/index.js.map +1 -1
  44. package/dist/components/index.mjs +5 -3
  45. package/dist/{config-CLQynoaa.d.ts → config-BrkU_Ynz.d.ts} +4 -1
  46. package/dist/{config-k61pe_o2.d.mts → config-snd2ToEB.d.mts} +4 -1
  47. package/dist/contexts/index.d.mts +31 -20
  48. package/dist/contexts/index.d.ts +31 -20
  49. package/dist/contexts/index.js +4 -4
  50. package/dist/contexts/index.mjs +3 -3
  51. package/dist/core/index.d.mts +75 -11
  52. package/dist/core/index.d.ts +75 -11
  53. package/dist/core/index.js +2 -2
  54. package/dist/core/index.mjs +1 -1
  55. package/dist/features/help/index.css +29 -0
  56. package/dist/features/help/index.css.map +1 -0
  57. package/dist/features/help/index.d.mts +74 -0
  58. package/dist/features/help/index.d.ts +74 -0
  59. package/dist/features/help/index.js +522 -0
  60. package/dist/features/help/index.js.map +1 -0
  61. package/dist/features/help/index.mjs +522 -0
  62. package/dist/features/help/index.mjs.map +1 -0
  63. package/dist/features/help/server.d.mts +33 -0
  64. package/dist/features/help/server.d.ts +33 -0
  65. package/dist/features/help/server.js +8 -0
  66. package/dist/features/help/server.js.map +1 -0
  67. package/dist/features/help/server.mjs +8 -0
  68. package/dist/features/help/server.mjs.map +1 -0
  69. package/dist/help-content-config.interface-C5ESIod9.d.mts +13 -0
  70. package/dist/help-content-config.interface-C5ESIod9.d.ts +13 -0
  71. package/dist/index.d.mts +10 -8
  72. package/dist/index.d.ts +10 -8
  73. package/dist/index.js +3 -3
  74. package/dist/index.mjs +2 -2
  75. package/dist/{notification.interface-aLEJbA_g.d.ts → notification.interface-C1T1C2ee.d.ts} +1 -100
  76. package/dist/{notification.interface-DLZGtV7Z.d.mts → notification.interface-DIxR23eS.d.mts} +1 -100
  77. package/dist/{s3.service-CVgLWaDc.d.mts → s3.service-0BTClOYO.d.mts} +2 -2
  78. package/dist/{s3.service-SLlX0Zbz.d.ts → s3.service-CT27Fm1s.d.ts} +2 -2
  79. package/dist/server/index.d.mts +4 -3
  80. package/dist/server/index.d.ts +4 -3
  81. package/dist/server/index.js +3 -3
  82. package/dist/server/index.mjs +1 -1
  83. package/dist/testing/index.d.mts +1 -2
  84. package/dist/testing/index.d.ts +1 -2
  85. package/dist/types-CQSjy7et.d.mts +101 -0
  86. package/dist/types-DHOxe8rc.d.ts +101 -0
  87. package/dist/usePageUrlGenerator-tjq2mlDV.d.ts +14 -0
  88. package/dist/usePageUrlGenerator-uOnyJ6j2.d.mts +14 -0
  89. package/dist/{useSocket-BkxHHujj.d.mts → useSocket-B1fMIr17.d.mts} +1 -1
  90. package/dist/{useSocket-CMDjWFYm.d.ts → useSocket-BdJTBXKv.d.ts} +1 -1
  91. package/package.json +15 -1
  92. package/src/client/config.ts +9 -1
  93. package/src/components/index.ts +1 -0
  94. package/src/components/viewers/BlockNoteViewer.tsx +27 -0
  95. package/src/components/viewers/BlockNoteViewerContainer.tsx +10 -0
  96. package/src/core/registry/helpStore.ts +28 -0
  97. package/src/features/assistant/contexts/AssistantContext.tsx +35 -19
  98. package/src/features/assistant/contexts/__tests__/AssistantContext.spec.tsx +66 -6
  99. package/src/features/assistant/data/Assistant.ts +2 -0
  100. package/src/features/assistant/data/AssistantInterface.ts +2 -0
  101. package/src/features/assistant/data/AssistantService.ts +18 -8
  102. package/src/features/assistant-message/components/parts/MessageSourcesContainer.tsx +4 -0
  103. package/src/features/assistant-message/components/parts/MessageSourcesPanel.tsx +6 -4
  104. package/src/features/assistant-message/components/parts/tabs/ContentsTab.tsx +5 -1
  105. package/src/features/assistant-message/components/parts/tabs/ReferencesTab.tsx +3 -1
  106. package/src/features/assistant-message/data/AssistantMessage.ts +27 -1
  107. package/src/features/assistant-message/data/AssistantMessageInterface.ts +1 -0
  108. package/src/features/assistant-message/data/__tests__/AssistantMessage.spec.ts +7 -3
  109. package/src/features/help/components/HelpArticleBody.tsx +62 -0
  110. package/src/features/help/components/HelpAskAi.tsx +36 -0
  111. package/src/features/help/components/HelpAssistantSheet.tsx +53 -0
  112. package/src/features/help/components/HelpHeader.tsx +40 -0
  113. package/src/features/help/components/HelpHint.tsx +86 -0
  114. package/src/features/help/components/HelpSearchResultRow.tsx +61 -0
  115. package/src/features/help/components/HelpSideNav.tsx +119 -0
  116. package/src/features/help/components/HelpTOC.tsx +49 -0
  117. package/src/features/help/components/__tests__/HelpAskAi.spec.tsx +68 -0
  118. package/src/features/help/components/__tests__/HelpAssistantSheet.spec.tsx +36 -0
  119. package/src/features/help/components/__tests__/HelpHint.spec.tsx +50 -0
  120. package/src/features/help/components/__tests__/HelpSearchResultRow.spec.tsx +58 -0
  121. package/src/features/help/components/__tests__/HelpSideNav.spec.tsx +50 -0
  122. package/src/features/help/contexts/HelpContext.spec.tsx +26 -0
  123. package/src/features/help/contexts/HelpContext.tsx +24 -0
  124. package/src/features/help/hooks/useHelp.ts +1 -0
  125. package/src/features/help/hooks/useHelpFilter.ts +27 -0
  126. package/src/features/help/i18n-keys.ts +34 -0
  127. package/src/features/help/index.ts +24 -0
  128. package/src/features/help/interfaces/help-content-config.interface.ts +12 -0
  129. package/src/features/help/server-entry.ts +11 -0
  130. package/src/features/help/types/help-article.types.ts +35 -0
  131. package/src/features/help/utils/articleUrl.ts +13 -0
  132. package/src/features/how-to/HowToModule.ts +14 -1
  133. package/src/features/how-to/components/forms/HowToEditor.tsx +108 -4
  134. package/src/features/how-to/data/HowTo.spec.ts +43 -0
  135. package/src/features/how-to/data/HowTo.ts +64 -3
  136. package/src/features/how-to/data/HowToInterface.ts +14 -0
  137. package/src/features/how-to/data/HowToService.ts +61 -0
  138. package/src/index.ts +4 -0
  139. package/dist/chunk-56XBGQGU.mjs.map +0 -1
  140. package/dist/chunk-CFECWLHH.mjs.map +0 -1
  141. package/dist/chunk-HC3JFN3C.js.map +0 -1
  142. package/dist/chunk-RXXZGPC3.js.map +0 -1
  143. package/dist/chunk-VLDLERJN.js.map +0 -1
  144. /package/dist/{BlockNoteEditor-XVIBGXHF.mjs.map → BlockNoteEditor-K7IIWM3S.mjs.map} +0 -0
@@ -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 updatedHowTo = howTo ? await HowToService.update(payload) : await HowToService.create(payload);
254
+ const saved = howTo ? await HowToService.update(payload) : await HowToService.create(payload);
203
255
 
204
- return updatedHowTo;
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,6 +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 _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;
8
15
 
9
16
  /**
10
17
  * Parse pages from backend JSON string (handles legacy single string + JSON array)
@@ -36,13 +43,60 @@ export class HowTo extends Content implements HowToInterface {
36
43
  return this._pages;
37
44
  }
38
45
 
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;
72
+ }
73
+
39
74
  rehydrate(data: JsonApiHydratedDataInterface): this {
40
75
  super.rehydrate(data);
41
76
 
42
- this._description = data.jsonApi.attributes.description
43
- ? JSON.parse(data.jsonApi.attributes.description)
44
- : undefined;
77
+ const rawDescription = data.jsonApi.attributes.description;
78
+ if (rawDescription === undefined || rawDescription === null || rawDescription === "") {
79
+ this._description = undefined;
80
+ } else if (typeof rawDescription === "string") {
81
+ try {
82
+ this._description = JSON.parse(rawDescription);
83
+ } catch {
84
+ // Help-content HowTos store plain-text summaries; admin-created HowTos
85
+ // store JSON-stringified BlockNote docs. Fall back to the raw string
86
+ // rather than crashing rehydration for the plain-text case.
87
+ this._description = rawDescription;
88
+ }
89
+ } else {
90
+ this._description = rawDescription;
91
+ }
45
92
  this._pages = data.jsonApi.attributes.pages;
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;
46
100
 
47
101
  return this;
48
102
  }
@@ -63,6 +117,13 @@ export class HowTo extends Content implements HowToInterface {
63
117
 
64
118
  if (data.description !== undefined) response.data.attributes.description = JSON.stringify(data.description);
65
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;
66
127
 
67
128
  return response;
68
129
  }
@@ -3,9 +3,23 @@ import { ContentInput, ContentInterface } from "../../content/data/content.inter
3
3
  export type HowToInput = ContentInput & {
4
4
  description?: any;
5
5
  pages?: string | undefined | null;
6
+ howToType?: string;
7
+ slug?: string;
8
+ order?: number;
9
+ summary?: string;
10
+ tags?: string[];
11
+ contextualKeys?: string[];
12
+ draft?: boolean;
6
13
  };
7
14
 
8
15
  export interface HowToInterface extends ContentInterface {
9
16
  get description(): any;
10
17
  get pages(): string | undefined;
18
+ get howToType(): string | undefined;
19
+ get slug(): string | undefined;
20
+ get order(): number | undefined;
21
+ get summary(): string | undefined;
22
+ get tags(): string[];
23
+ get contextualKeys(): string[];
24
+ get draft(): boolean;
11
25
  }
@@ -58,4 +58,65 @@ export class HowToService extends AbstractService {
58
58
  endpoint: new EndpointCreator({ endpoint: Modules.HowTo, id: params.howToId }).generate(),
59
59
  });
60
60
  }
61
+
62
+ static async findPublished(params: { howToType?: string } = {}): Promise<HowToInterface[]> {
63
+ const endpoint = new EndpointCreator({ endpoint: `public/${Modules.HowTo.name}` });
64
+ if (params.howToType) endpoint.addAdditionalParam("type", params.howToType);
65
+ return this.callApi({
66
+ type: Modules.HowTo,
67
+ method: HttpMethod.GET,
68
+ endpoint: endpoint.generate(),
69
+ });
70
+ }
71
+
72
+ static async findPublishedArticle(params: { howToType: string; slug: string }): Promise<HowToInterface> {
73
+ return this.callApi<HowToInterface>({
74
+ type: Modules.HowTo,
75
+ method: HttpMethod.GET,
76
+ endpoint: new EndpointCreator({
77
+ endpoint: `public/${Modules.HowTo.name}`,
78
+ id: params.howToType,
79
+ childEndpoint: params.slug,
80
+ }).generate(),
81
+ });
82
+ }
83
+
84
+ static async findRelated(params: { howToType: string; slug: string }): Promise<HowToInterface[]> {
85
+ return this.callApi({
86
+ type: Modules.HowTo,
87
+ method: HttpMethod.GET,
88
+ endpoint: new EndpointCreator({
89
+ endpoint: `public/${Modules.HowTo.name}`,
90
+ id: params.howToType,
91
+ childEndpoint: params.slug,
92
+ childId: "related",
93
+ }).generate(),
94
+ });
95
+ }
96
+
97
+ static async addRelated(params: { howToId: string; relatedId: string }): Promise<HowToInterface> {
98
+ return this.callApi<HowToInterface>({
99
+ type: Modules.HowTo,
100
+ method: HttpMethod.POST,
101
+ endpoint: new EndpointCreator({
102
+ endpoint: Modules.HowTo,
103
+ id: params.howToId,
104
+ childEndpoint: "related",
105
+ childId: params.relatedId,
106
+ }).generate(),
107
+ });
108
+ }
109
+
110
+ static async removeRelated(params: { howToId: string; relatedId: string }): Promise<void> {
111
+ await this.callApi({
112
+ type: Modules.HowTo,
113
+ method: HttpMethod.DELETE,
114
+ endpoint: new EndpointCreator({
115
+ endpoint: Modules.HowTo,
116
+ id: params.howToId,
117
+ childEndpoint: "related",
118
+ childId: params.relatedId,
119
+ }).generate(),
120
+ });
121
+ }
61
122
  }
package/src/index.ts CHANGED
@@ -57,3 +57,7 @@ export * from "./features/rbac/rbac.module";
57
57
  // Assistant + AssistantMessage feature data
58
58
  export * from "./features/assistant/data";
59
59
  export * from "./features/assistant-message/data";
60
+
61
+ // Help feature: NOT re-exported from the main barrel. Consumers must import from
62
+ // the dedicated `@carlonicora/nextjs-jsonapi/help` subpath to keep the help-feature
63
+ // surface (client contexts + server-only utilities) out of the main client bundle.