@carlonicora/nextjs-jsonapi 1.67.0 → 1.69.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/{AuthComponent-NwQ_ZXsv.d.mts → AuthComponent-DXe3kPzb.d.mts} +1 -1
- package/dist/{AuthComponent-DL1D3y7f.d.ts → AuthComponent-Di8DsZ2I.d.ts} +1 -1
- package/dist/{BlockNoteEditor-QHWPE3BJ.js → BlockNoteEditor-CBUEPFA7.js} +14 -14
- package/dist/{BlockNoteEditor-QHWPE3BJ.js.map → BlockNoteEditor-CBUEPFA7.js.map} +1 -1
- package/dist/{BlockNoteEditor-TIX3GDVZ.mjs → BlockNoteEditor-ON5Q6QWO.mjs} +4 -4
- package/dist/HowToInterface-DtVWAE1s.d.mts +17 -0
- package/dist/HowToInterface-NaqSG9sE.d.ts +17 -0
- package/dist/{auth.interface-BX_1qZZJ.d.ts → auth.interface-BTco8PWs.d.ts} +1 -1
- package/dist/{auth.interface-yeLelxdI.d.mts → auth.interface-C4uJzBec.d.mts} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-CJY63D6U.js → chunk-56VU7A4I.js} +180 -18
- package/dist/chunk-56VU7A4I.js.map +1 -0
- package/dist/{chunk-3BWYWS3A.js → chunk-5X4MS55M.js} +1655 -695
- package/dist/chunk-5X4MS55M.js.map +1 -0
- package/dist/{chunk-KFIQTY4O.js → chunk-6ROMPIIP.js} +11 -11
- package/dist/{chunk-KFIQTY4O.js.map → chunk-6ROMPIIP.js.map} +1 -1
- package/dist/{chunk-RIG2BEXJ.mjs → chunk-GZNHBAZF.mjs} +163 -1
- package/dist/chunk-GZNHBAZF.mjs.map +1 -0
- package/dist/{chunk-WWP32QYC.mjs → chunk-NPF7P7MU.mjs} +2562 -1602
- package/dist/chunk-NPF7P7MU.mjs.map +1 -0
- package/dist/{chunk-ZYAAJMZZ.mjs → chunk-WJYWWOTG.mjs} +2 -2
- package/dist/client/index.d.mts +5 -6
- package/dist/client/index.d.ts +5 -6
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.d.mts +76 -8
- package/dist/components/index.d.ts +76 -8
- package/dist/components/index.js +26 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +25 -3
- package/dist/{config-D-mqttuF.d.mts → config-Bmr_0qTn.d.mts} +1 -1
- package/dist/{config-CyCAWW-d.d.ts → config-n0lfSf27.d.ts} +1 -1
- package/dist/contexts/index.d.mts +16 -4
- package/dist/contexts/index.d.ts +16 -4
- package/dist/contexts/index.js +8 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +7 -3
- package/dist/core/index.d.mts +61 -11
- package/dist/core/index.d.ts +61 -11
- package/dist/core/index.js +10 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +9 -1
- package/dist/index.d.mts +9 -10
- package/dist/index.d.ts +9 -10
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/{notification.interface-ItBxq2au.d.ts → notification.interface-DYDZENx2.d.ts} +18 -1
- package/dist/{notification.interface-C6UcmJqu.d.mts → notification.interface-DrHu_1MM.d.mts} +18 -1
- package/dist/{s3.service-Cg5TmbU_.d.ts → s3.service-DK2KKXbR.d.ts} +6 -3
- package/dist/{s3.service-DLf_a0xS.d.mts → s3.service-TsN2unZr.d.mts} +6 -3
- package/dist/server/index.d.mts +3 -4
- package/dist/server/index.d.ts +3 -4
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/{useRbacState-CUj0hp8t.d.ts → useRbacState-BYaSdA78.d.ts} +1 -1
- package/dist/{useRbacState-Btk1gkQg.d.mts → useRbacState-CQEJ_ysV.d.mts} +1 -1
- package/dist/{useSocket-BSUN9s3p.d.ts → useSocket-Cjt_qvkI.d.ts} +1 -1
- package/dist/{useSocket-DKI92Fbg.d.mts → useSocket-VAGetcT3.d.mts} +1 -1
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/contexts/index.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/registry/ModuleRegistry.ts +19 -0
- package/src/features/how-to/HowToModule.ts +18 -0
- package/src/features/how-to/components/containers/HowToCommand.tsx +230 -0
- package/src/features/how-to/components/containers/HowToCommandViewer.tsx +76 -0
- package/src/features/how-to/components/containers/HowToContainer.tsx +27 -0
- package/src/features/how-to/components/containers/HowToListContainer.tsx +17 -0
- package/src/features/how-to/components/details/HowToContent.tsx +16 -0
- package/src/features/how-to/components/details/HowToDetails.tsx +52 -0
- package/src/features/how-to/components/forms/HowToDeleter.tsx +31 -0
- package/src/features/how-to/components/forms/HowToEditor.tsx +270 -0
- package/src/features/how-to/components/forms/HowToMultiSelector.tsx +152 -0
- package/src/features/how-to/components/forms/HowToSelector.tsx +164 -0
- package/src/features/how-to/components/index.ts +11 -0
- package/src/features/how-to/components/lists/HowToList.tsx +39 -0
- package/src/features/how-to/contexts/HowToContext.tsx +101 -0
- package/src/features/how-to/data/HowTo.ts +69 -0
- package/src/features/how-to/data/HowToFields.ts +10 -0
- package/src/features/how-to/data/HowToInterface.ts +11 -0
- package/src/features/how-to/data/HowToService.ts +61 -0
- package/src/features/how-to/data/index.ts +4 -0
- package/src/features/how-to/hooks/useHowToTableStructure.tsx +86 -0
- package/src/features/how-to/index.ts +2 -0
- package/src/features/how-to/utils/blocknote.ts +108 -0
- package/src/features/how-to/utils/index.ts +1 -0
- package/src/features/user/components/details/UserContent.tsx +1 -1
- package/src/features/user/data/user.service.ts +9 -0
- package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.mts +0 -6
- package/dist/breadcrumb.item.data.interface-CgB4_1EE.d.ts +0 -6
- package/dist/chunk-3BWYWS3A.js.map +0 -1
- package/dist/chunk-CJY63D6U.js.map +0 -1
- package/dist/chunk-RIG2BEXJ.mjs.map +0 -1
- package/dist/chunk-WWP32QYC.mjs.map +0 -1
- package/dist/content.interface-8T5-G84c.d.mts +0 -21
- package/dist/content.interface-D-xdYxjt.d.ts +0 -21
- /package/dist/{BlockNoteEditor-TIX3GDVZ.mjs.map → BlockNoteEditor-ON5Q6QWO.mjs.map} +0 -0
- /package/dist/{chunk-ZYAAJMZZ.mjs.map → chunk-WJYWWOTG.mjs.map} +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { JsonApiHydratedDataInterface, Modules, rehydrate } from "../../../core";
|
|
4
|
+
import { BreadcrumbItemData } from "../../../interfaces";
|
|
5
|
+
import { usePageUrlGenerator } from "../../../hooks";
|
|
6
|
+
import { SharedProvider } from "../../../contexts/SharedContext";
|
|
7
|
+
import { HowToInterface } from "../data/HowToInterface";
|
|
8
|
+
import { HowToService } from "../data/HowToService";
|
|
9
|
+
import HowToDeleter from "../components/forms/HowToDeleter";
|
|
10
|
+
import HowToEditor from "../components/forms/HowToEditor";
|
|
11
|
+
import { useTranslations } from "next-intl";
|
|
12
|
+
import { createContext, ReactNode, useContext, useState } from "react";
|
|
13
|
+
|
|
14
|
+
interface HowToContextType {
|
|
15
|
+
howTo: HowToInterface | undefined;
|
|
16
|
+
setHowTo: (value: HowToInterface | undefined) => void;
|
|
17
|
+
reloadHowTo: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const HowToContext = createContext<HowToContextType | undefined>(undefined);
|
|
21
|
+
|
|
22
|
+
type HowToProviderProps = {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
dehydratedHowTo?: JsonApiHydratedDataInterface;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const HowToProvider = ({ children, dehydratedHowTo }: HowToProviderProps) => {
|
|
28
|
+
const generateUrl = usePageUrlGenerator();
|
|
29
|
+
const t = useTranslations();
|
|
30
|
+
|
|
31
|
+
const [howTo, setHowTo] = useState<HowToInterface | undefined>(
|
|
32
|
+
dehydratedHowTo ? rehydrate<HowToInterface>(Modules.HowTo, dehydratedHowTo) : undefined,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const reloadHowTo = async () => {
|
|
36
|
+
if (!howTo) return;
|
|
37
|
+
|
|
38
|
+
const freshHowTo = await HowToService.findOne({ id: howTo.id });
|
|
39
|
+
setHowTo(freshHowTo);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const breadcrumb = () => {
|
|
43
|
+
const response: BreadcrumbItemData[] = [];
|
|
44
|
+
|
|
45
|
+
response.push({
|
|
46
|
+
name: t(`entities.howtos`, { count: 2 }),
|
|
47
|
+
href: generateUrl({ page: Modules.HowTo }),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (howTo)
|
|
51
|
+
response.push({
|
|
52
|
+
name: howTo.name,
|
|
53
|
+
href: generateUrl({ page: Modules.HowTo, id: howTo.id }),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
return response;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const title = () => {
|
|
60
|
+
const response: any = {
|
|
61
|
+
type: t(`entities.howtos`, { count: howTo ? 1 : 2 }),
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const functions: ReactNode[] = [];
|
|
65
|
+
|
|
66
|
+
if (howTo) {
|
|
67
|
+
response.element = howTo.name;
|
|
68
|
+
|
|
69
|
+
functions.push(<HowToDeleter key={`HowToDeleter`} howTo={howTo} />);
|
|
70
|
+
functions.push(<HowToEditor key={`HowToEditor`} howTo={howTo} propagateChanges={setHowTo} />);
|
|
71
|
+
} else {
|
|
72
|
+
functions.push(<HowToEditor key={`HowToEditor`} />);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (functions.length > 0) response.functions = functions;
|
|
76
|
+
|
|
77
|
+
return response;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<SharedProvider value={{ breadcrumbs: breadcrumb(), title: title() }}>
|
|
82
|
+
<HowToContext.Provider
|
|
83
|
+
value={{
|
|
84
|
+
howTo: howTo,
|
|
85
|
+
setHowTo: setHowTo,
|
|
86
|
+
reloadHowTo: reloadHowTo,
|
|
87
|
+
}}
|
|
88
|
+
>
|
|
89
|
+
{children}
|
|
90
|
+
</HowToContext.Provider>
|
|
91
|
+
</SharedProvider>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const useHowToContext = (): HowToContextType => {
|
|
96
|
+
const context = useContext(HowToContext);
|
|
97
|
+
if (context === undefined) {
|
|
98
|
+
throw new Error("useHowToContext must be used within a HowToProvider");
|
|
99
|
+
}
|
|
100
|
+
return context;
|
|
101
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { JsonApiHydratedDataInterface, Modules } from "../../../core";
|
|
2
|
+
import { Content } from "../../content/data/content";
|
|
3
|
+
import { HowToInput, HowToInterface } from "./HowToInterface";
|
|
4
|
+
|
|
5
|
+
export class HowTo extends Content implements HowToInterface {
|
|
6
|
+
private _description?: any;
|
|
7
|
+
private _pages?: string;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse pages from backend JSON string (handles legacy single string + JSON array)
|
|
11
|
+
*/
|
|
12
|
+
static parsePagesFromString(pagesStr?: string): string[] {
|
|
13
|
+
if (!pagesStr) return [];
|
|
14
|
+
try {
|
|
15
|
+
const parsed = JSON.parse(pagesStr);
|
|
16
|
+
return Array.isArray(parsed) ? parsed : [pagesStr];
|
|
17
|
+
} catch {
|
|
18
|
+
// Legacy: treat as single page if not valid JSON
|
|
19
|
+
return pagesStr ? [pagesStr] : [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Serialize pages array to JSON string for backend
|
|
25
|
+
*/
|
|
26
|
+
static serializePagesToString(pages: string[]): string | undefined {
|
|
27
|
+
const filtered = pages.filter((p) => p.trim());
|
|
28
|
+
return filtered.length > 0 ? JSON.stringify(filtered) : undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
get description(): any {
|
|
32
|
+
return this._description;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get pages(): string | undefined {
|
|
36
|
+
return this._pages;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
rehydrate(data: JsonApiHydratedDataInterface): this {
|
|
40
|
+
super.rehydrate(data);
|
|
41
|
+
|
|
42
|
+
this._description = data.jsonApi.attributes.description
|
|
43
|
+
? JSON.parse(data.jsonApi.attributes.description)
|
|
44
|
+
: undefined;
|
|
45
|
+
this._pages = data.jsonApi.attributes.pages;
|
|
46
|
+
|
|
47
|
+
return this;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
createJsonApi(data: HowToInput) {
|
|
51
|
+
const response: any = {
|
|
52
|
+
data: {
|
|
53
|
+
type: Modules.HowTo.name,
|
|
54
|
+
id: data.id,
|
|
55
|
+
attributes: {},
|
|
56
|
+
meta: {},
|
|
57
|
+
relationships: {},
|
|
58
|
+
},
|
|
59
|
+
included: [],
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
super.addContentInput(response, data);
|
|
63
|
+
|
|
64
|
+
if (data.description !== undefined) response.data.attributes.description = JSON.stringify(data.description);
|
|
65
|
+
if (data.pages !== undefined) response.data.attributes.pages = data.pages;
|
|
66
|
+
|
|
67
|
+
return response;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ContentInput, ContentInterface } from "../../content/data/content.interface";
|
|
2
|
+
|
|
3
|
+
export type HowToInput = ContentInput & {
|
|
4
|
+
description?: any;
|
|
5
|
+
pages?: string | undefined | null;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export interface HowToInterface extends ContentInterface {
|
|
9
|
+
get description(): any;
|
|
10
|
+
get pages(): string | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef, PreviousRef } from "../../../core";
|
|
2
|
+
import { HowToInput, HowToInterface } from "./HowToInterface";
|
|
3
|
+
|
|
4
|
+
export class HowToService extends AbstractService {
|
|
5
|
+
static async findOne(params: { id: string }): Promise<HowToInterface> {
|
|
6
|
+
return this.callApi<HowToInterface>({
|
|
7
|
+
type: Modules.HowTo,
|
|
8
|
+
method: HttpMethod.GET,
|
|
9
|
+
endpoint: new EndpointCreator({ endpoint: Modules.HowTo, id: params.id }).generate(),
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static async findMany(
|
|
14
|
+
params: {
|
|
15
|
+
search?: string;
|
|
16
|
+
fetchAll?: boolean;
|
|
17
|
+
next?: NextRef;
|
|
18
|
+
prev?: PreviousRef;
|
|
19
|
+
} = {},
|
|
20
|
+
): Promise<HowToInterface[]> {
|
|
21
|
+
const endpoint = new EndpointCreator({ endpoint: Modules.HowTo });
|
|
22
|
+
|
|
23
|
+
if (params.fetchAll) endpoint.addAdditionalParam("fetchAll", "true");
|
|
24
|
+
if (params.search) endpoint.addAdditionalParam("search", params.search);
|
|
25
|
+
if (Modules.HowTo.inclusions?.lists?.fields) endpoint.limitToFields(Modules.HowTo.inclusions.lists.fields);
|
|
26
|
+
if (Modules.HowTo.inclusions?.lists?.types) endpoint.limitToType(Modules.HowTo.inclusions.lists.types);
|
|
27
|
+
|
|
28
|
+
return this.callApi({
|
|
29
|
+
type: Modules.HowTo,
|
|
30
|
+
method: HttpMethod.GET,
|
|
31
|
+
endpoint: endpoint.generate(),
|
|
32
|
+
next: params.next,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static async create(params: HowToInput): Promise<HowToInterface> {
|
|
37
|
+
return this.callApi({
|
|
38
|
+
type: Modules.HowTo,
|
|
39
|
+
method: HttpMethod.POST,
|
|
40
|
+
endpoint: new EndpointCreator({ endpoint: Modules.HowTo }).generate(),
|
|
41
|
+
input: params,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async update(params: HowToInput): Promise<HowToInterface> {
|
|
46
|
+
return this.callApi({
|
|
47
|
+
type: Modules.HowTo,
|
|
48
|
+
method: HttpMethod.PUT,
|
|
49
|
+
endpoint: new EndpointCreator({ endpoint: Modules.HowTo, id: params.id }).generate(),
|
|
50
|
+
input: params,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static async delete(params: { howToId: string }): Promise<void> {
|
|
55
|
+
await this.callApi({
|
|
56
|
+
type: Modules.HowTo,
|
|
57
|
+
method: HttpMethod.DELETE,
|
|
58
|
+
endpoint: new EndpointCreator({ endpoint: Modules.HowTo, id: params.howToId }).generate(),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { ColumnDef } from "@tanstack/react-table";
|
|
4
|
+
import { useTranslations } from "next-intl";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
|
|
7
|
+
import { cellDate, cellId } from "../../../components";
|
|
8
|
+
import { Modules } from "../../../core";
|
|
9
|
+
import { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from "../../../hooks";
|
|
10
|
+
import { Link } from "../../../shadcnui";
|
|
11
|
+
import { HowToFields } from "../data/HowToFields";
|
|
12
|
+
import { HowToInterface } from "../data/HowToInterface";
|
|
13
|
+
|
|
14
|
+
export const useHowToTableStructure: UseTableStructureHook<HowToInterface, HowToFields> = (params) => {
|
|
15
|
+
const t = useTranslations();
|
|
16
|
+
const generateUrl = usePageUrlGenerator();
|
|
17
|
+
|
|
18
|
+
const tableData = useMemo(() => {
|
|
19
|
+
return params.data.map((howTo: HowToInterface) => {
|
|
20
|
+
const entry: TableContent<HowToInterface> = {
|
|
21
|
+
jsonApiData: howTo,
|
|
22
|
+
};
|
|
23
|
+
entry[HowToFields.howToId] = howTo.id;
|
|
24
|
+
params.fields.forEach((field) => {
|
|
25
|
+
entry[field] = howTo[field as keyof HowToInterface];
|
|
26
|
+
});
|
|
27
|
+
return entry;
|
|
28
|
+
});
|
|
29
|
+
}, [params.data, params.fields]);
|
|
30
|
+
|
|
31
|
+
const fieldColumnMap: Partial<Record<HowToFields, () => any>> = {
|
|
32
|
+
[HowToFields.howToId]: () =>
|
|
33
|
+
cellId({
|
|
34
|
+
name: "howToId",
|
|
35
|
+
checkedIds: params.checkedIds,
|
|
36
|
+
toggleId: params.toggleId,
|
|
37
|
+
}),
|
|
38
|
+
[HowToFields.name]: () => ({
|
|
39
|
+
id: "name",
|
|
40
|
+
accessorKey: "name",
|
|
41
|
+
header: t(`howto.fields.name.label`),
|
|
42
|
+
cell: ({ row }: { row: TableContent<HowToInterface> }) => {
|
|
43
|
+
const howTo: HowToInterface = row.original.jsonApiData;
|
|
44
|
+
return <Link href={generateUrl({ page: Modules.HowTo, id: howTo.id })}>{howTo.name}</Link>;
|
|
45
|
+
},
|
|
46
|
+
enableSorting: false,
|
|
47
|
+
enableHiding: false,
|
|
48
|
+
}),
|
|
49
|
+
[HowToFields.description]: () => ({
|
|
50
|
+
id: "description",
|
|
51
|
+
accessorKey: "description",
|
|
52
|
+
header: t(`howto.fields.description.label`),
|
|
53
|
+
cell: ({ row }: { row: TableContent<HowToInterface> }) => <>{row.getValue("description")}</>,
|
|
54
|
+
enableSorting: false,
|
|
55
|
+
enableHiding: false,
|
|
56
|
+
}),
|
|
57
|
+
[HowToFields.pages]: () => ({
|
|
58
|
+
id: "pages",
|
|
59
|
+
accessorKey: "pages",
|
|
60
|
+
header: t(`howto.fields.pages.label`),
|
|
61
|
+
cell: ({ row }: { row: TableContent<HowToInterface> }) => <>{row.getValue("pages")}</>,
|
|
62
|
+
enableSorting: false,
|
|
63
|
+
enableHiding: false,
|
|
64
|
+
}),
|
|
65
|
+
[HowToFields.createdAt]: () =>
|
|
66
|
+
cellDate({
|
|
67
|
+
name: "createdAt",
|
|
68
|
+
title: t(`common.date.create`),
|
|
69
|
+
}),
|
|
70
|
+
[HowToFields.updatedAt]: () =>
|
|
71
|
+
cellDate({
|
|
72
|
+
name: "updatedAt",
|
|
73
|
+
title: t(`common.date.update`),
|
|
74
|
+
}),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const columns = useMemo(() => {
|
|
78
|
+
return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
|
|
79
|
+
TableContent<HowToInterface>
|
|
80
|
+
>[];
|
|
81
|
+
}, [params.fields, fieldColumnMap, t, generateUrl]);
|
|
82
|
+
|
|
83
|
+
return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
registerTableGenerator("howtos", useHowToTableStructure);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export type TocHeading = {
|
|
2
|
+
id: string;
|
|
3
|
+
text: string;
|
|
4
|
+
level: number;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Extracts all text content from a BlockNote block recursively
|
|
9
|
+
*/
|
|
10
|
+
function extractTextFromContent(content: any): string {
|
|
11
|
+
if (!content) return "";
|
|
12
|
+
|
|
13
|
+
if (typeof content === "string") return content;
|
|
14
|
+
|
|
15
|
+
if (Array.isArray(content)) {
|
|
16
|
+
return content.map(extractTextFromContent).join("");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (content.text) return content.text;
|
|
20
|
+
|
|
21
|
+
if (content.content) {
|
|
22
|
+
return extractTextFromContent(content.content);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extracts all text from BlockNote content for word counting
|
|
30
|
+
*/
|
|
31
|
+
export function extractAllText(blocks: any[] | undefined): string {
|
|
32
|
+
if (!blocks || !Array.isArray(blocks)) return "";
|
|
33
|
+
|
|
34
|
+
return blocks
|
|
35
|
+
.map((block) => {
|
|
36
|
+
let text = extractTextFromContent(block.content);
|
|
37
|
+
|
|
38
|
+
// Recursively get text from children blocks
|
|
39
|
+
if (block.children && Array.isArray(block.children)) {
|
|
40
|
+
text += " " + extractAllText(block.children);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return text;
|
|
44
|
+
})
|
|
45
|
+
.join(" ")
|
|
46
|
+
.trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Calculates estimated reading time based on word count
|
|
51
|
+
* Assumes average reading speed of 200 words per minute
|
|
52
|
+
*/
|
|
53
|
+
export function calculateReadingTime(blocks: any[] | undefined): number {
|
|
54
|
+
const text = extractAllText(blocks);
|
|
55
|
+
const wordCount = text.split(/\s+/).filter((word: string) => word.length > 0).length;
|
|
56
|
+
const minutes = Math.ceil(wordCount / 200);
|
|
57
|
+
return Math.max(1, minutes); // Minimum 1 minute
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generates a URL-friendly slug from text
|
|
62
|
+
*/
|
|
63
|
+
function generateSlug(text: string, index: number): string {
|
|
64
|
+
const baseSlug = text
|
|
65
|
+
.toLowerCase()
|
|
66
|
+
.replace(/[^a-z0-9\s-]/g, "")
|
|
67
|
+
.replace(/\s+/g, "-")
|
|
68
|
+
.replace(/-+/g, "-")
|
|
69
|
+
.trim();
|
|
70
|
+
|
|
71
|
+
return baseSlug || `heading-${index}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extracts headings from BlockNote content for table of contents
|
|
76
|
+
*/
|
|
77
|
+
export function extractHeadings(blocks: any[] | undefined): TocHeading[] {
|
|
78
|
+
if (!blocks || !Array.isArray(blocks)) return [];
|
|
79
|
+
|
|
80
|
+
const headings: TocHeading[] = [];
|
|
81
|
+
let headingIndex = 0;
|
|
82
|
+
|
|
83
|
+
function processBlocks(blockArray: any[]) {
|
|
84
|
+
for (const block of blockArray) {
|
|
85
|
+
if (block.type === "heading") {
|
|
86
|
+
const level = block.props?.level || 1;
|
|
87
|
+
const text = extractTextFromContent(block.content);
|
|
88
|
+
|
|
89
|
+
if (text.trim()) {
|
|
90
|
+
headings.push({
|
|
91
|
+
id: generateSlug(text, headingIndex),
|
|
92
|
+
text: text.trim(),
|
|
93
|
+
level,
|
|
94
|
+
});
|
|
95
|
+
headingIndex++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Process children blocks recursively
|
|
100
|
+
if (block.children && Array.isArray(block.children)) {
|
|
101
|
+
processBlocks(block.children);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
processBlocks(blocks);
|
|
107
|
+
return headings;
|
|
108
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./blocknote";
|
|
@@ -35,7 +35,7 @@ export function UserContent({ user }: UserContentProps) {
|
|
|
35
35
|
fallback={getInitials(user.name)}
|
|
36
36
|
alt={user.name}
|
|
37
37
|
patchImage={async (imageKey) => {
|
|
38
|
-
const updated = await UserService.
|
|
38
|
+
const updated = await UserService.patchAvatar({ id: user.id, avatar: imageKey });
|
|
39
39
|
setUser(updated);
|
|
40
40
|
}}
|
|
41
41
|
className="h-24 w-24"
|
|
@@ -234,6 +234,15 @@ export class UserService extends AbstractService {
|
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
static async patchAvatar(params: { id: string; avatar: string }): Promise<UserInterface> {
|
|
238
|
+
return this.callApi({
|
|
239
|
+
type: Modules.User,
|
|
240
|
+
method: HttpMethod.PATCH,
|
|
241
|
+
endpoint: new EndpointCreator({ endpoint: Modules.User, id: params.id, childEndpoint: "avatar" }).generate(),
|
|
242
|
+
input: params,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
static async patchRate(params: UserInput): Promise<UserInterface> {
|
|
238
247
|
return this.callApi({
|
|
239
248
|
type: Modules.User,
|