proteum 1.0.0-1
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/.dockerignore +10 -0
- package/Rte.zip +0 -0
- package/cli/app/config.ts +54 -0
- package/cli/app/index.ts +195 -0
- package/cli/bin.js +11 -0
- package/cli/commands/build.ts +34 -0
- package/cli/commands/deploy/app.ts +29 -0
- package/cli/commands/deploy/web.ts +60 -0
- package/cli/commands/dev.ts +109 -0
- package/cli/commands/init.ts +85 -0
- package/cli/compiler/client/identite.ts +72 -0
- package/cli/compiler/client/index.ts +334 -0
- package/cli/compiler/common/babel/index.ts +170 -0
- package/cli/compiler/common/babel/plugins/index.ts +0 -0
- package/cli/compiler/common/babel/plugins/services.ts +579 -0
- package/cli/compiler/common/babel/routes/imports.ts +127 -0
- package/cli/compiler/common/babel/routes/routes.ts +1130 -0
- package/cli/compiler/common/files/autres.ts +39 -0
- package/cli/compiler/common/files/images.ts +35 -0
- package/cli/compiler/common/files/style.ts +78 -0
- package/cli/compiler/common/index.ts +154 -0
- package/cli/compiler/index.ts +532 -0
- package/cli/compiler/server/index.ts +211 -0
- package/cli/index.ts +189 -0
- package/cli/paths.ts +165 -0
- package/cli/print.ts +12 -0
- package/cli/tsconfig.json +38 -0
- package/cli/utils/index.ts +22 -0
- package/cli/utils/keyboard.ts +78 -0
- package/client/app/component.tsx +54 -0
- package/client/app/index.ts +142 -0
- package/client/app/service.ts +34 -0
- package/client/app.tsconfig.json +28 -0
- package/client/components/Button.tsx +298 -0
- package/client/components/Dialog/Manager.tsx +309 -0
- package/client/components/Dialog/card.tsx +208 -0
- package/client/components/Dialog/index.less +151 -0
- package/client/components/Dialog/status.less +176 -0
- package/client/components/Dialog/status.tsx +48 -0
- package/client/components/index.ts +2 -0
- package/client/components/types.d.ts +3 -0
- package/client/data/input.ts +32 -0
- package/client/global.d.ts +5 -0
- package/client/hooks.ts +22 -0
- package/client/index.ts +6 -0
- package/client/pages/_layout/index.less +6 -0
- package/client/pages/_layout/index.tsx +43 -0
- package/client/pages/bug.tsx.old +60 -0
- package/client/pages/useHeader.tsx +50 -0
- package/client/services/captcha/index.ts +67 -0
- package/client/services/router/components/Link.tsx +46 -0
- package/client/services/router/components/Page.tsx +55 -0
- package/client/services/router/components/router.tsx +218 -0
- package/client/services/router/index.tsx +521 -0
- package/client/services/router/request/api.ts +267 -0
- package/client/services/router/request/history.ts +5 -0
- package/client/services/router/request/index.ts +53 -0
- package/client/services/router/request/multipart.ts +147 -0
- package/client/services/router/response/index.tsx +128 -0
- package/client/services/router/response/page.ts +86 -0
- package/client/services/socket/index.ts +147 -0
- package/client/utils/dom.ts +77 -0
- package/common/app/index.ts +9 -0
- package/common/data/chaines/index.ts +54 -0
- package/common/data/dates.ts +179 -0
- package/common/data/markdown.ts +73 -0
- package/common/data/rte/nodes.ts +83 -0
- package/common/data/stats.ts +90 -0
- package/common/errors/index.tsx +326 -0
- package/common/router/index.ts +213 -0
- package/common/router/layouts.ts +93 -0
- package/common/router/register.ts +55 -0
- package/common/router/request/api.ts +77 -0
- package/common/router/request/index.ts +35 -0
- package/common/router/response/index.ts +45 -0
- package/common/router/response/page.ts +128 -0
- package/common/utils/rte.ts +183 -0
- package/common/utils.ts +7 -0
- package/doc/TODO.md +71 -0
- package/doc/front/router.md +27 -0
- package/doc/workspace/workspace.png +0 -0
- package/doc/workspace/workspace2.png +0 -0
- package/doc/workspace/workspace_26.01.22.png +0 -0
- package/package.json +171 -0
- package/server/app/commands.ts +141 -0
- package/server/app/container/config.ts +203 -0
- package/server/app/container/console/index.ts +550 -0
- package/server/app/container/index.ts +137 -0
- package/server/app/index.ts +273 -0
- package/server/app/service/container.ts +88 -0
- package/server/app/service/index.ts +235 -0
- package/server/app.tsconfig.json +28 -0
- package/server/context.ts +4 -0
- package/server/index.ts +4 -0
- package/server/services/auth/index.ts +250 -0
- package/server/services/auth/old.ts +277 -0
- package/server/services/auth/router/index.ts +95 -0
- package/server/services/auth/router/request.ts +54 -0
- package/server/services/auth/router/service.json +6 -0
- package/server/services/auth/service.json +6 -0
- package/server/services/cache/commands.ts +41 -0
- package/server/services/cache/index.ts +297 -0
- package/server/services/cache/service.json +6 -0
- package/server/services/cron/CronTask.ts +86 -0
- package/server/services/cron/index.ts +112 -0
- package/server/services/cron/service.json +6 -0
- package/server/services/disks/driver.ts +103 -0
- package/server/services/disks/drivers/local/index.ts +188 -0
- package/server/services/disks/drivers/local/service.json +6 -0
- package/server/services/disks/drivers/s3/index.ts +301 -0
- package/server/services/disks/drivers/s3/service.json +6 -0
- package/server/services/disks/index.ts +90 -0
- package/server/services/disks/service.json +6 -0
- package/server/services/email/index.ts +188 -0
- package/server/services/email/utils.ts +53 -0
- package/server/services/fetch/index.ts +201 -0
- package/server/services/fetch/service.json +7 -0
- package/server/services/models.7z +0 -0
- package/server/services/prisma/Facet.ts +142 -0
- package/server/services/prisma/index.ts +201 -0
- package/server/services/prisma/service.json +6 -0
- package/server/services/router/http/index.ts +217 -0
- package/server/services/router/http/multipart.ts +102 -0
- package/server/services/router/http/session.ts.old +40 -0
- package/server/services/router/index.ts +801 -0
- package/server/services/router/request/api.ts +87 -0
- package/server/services/router/request/index.ts +184 -0
- package/server/services/router/request/service.ts +21 -0
- package/server/services/router/request/validation/zod.ts +180 -0
- package/server/services/router/response/index.ts +338 -0
- package/server/services/router/response/mask/Filter.ts +323 -0
- package/server/services/router/response/mask/index.ts +60 -0
- package/server/services/router/response/mask/selecteurs.ts +92 -0
- package/server/services/router/response/page/document.tsx +160 -0
- package/server/services/router/response/page/index.tsx +196 -0
- package/server/services/router/service.json +6 -0
- package/server/services/router/service.ts +36 -0
- package/server/services/schema/index.ts +44 -0
- package/server/services/schema/request.ts +49 -0
- package/server/services/schema/router/index.ts +28 -0
- package/server/services/schema/router/service.json +6 -0
- package/server/services/schema/service.json +6 -0
- package/server/services/security/encrypt/aes/index.ts +85 -0
- package/server/services/security/encrypt/aes/service.json +6 -0
- package/server/services/socket/index.ts +162 -0
- package/server/services/socket/scope.ts +226 -0
- package/server/services/socket/service.json +6 -0
- package/server/services_old/SocketClient.ts +92 -0
- package/server/services_old/Token.old.ts +97 -0
- package/server/utils/slug.ts +79 -0
- package/tsconfig.common.json +45 -0
- package/tsconfig.json +3 -0
- package/types/aliases.d.ts +54 -0
- package/types/global/modules.d.ts +49 -0
- package/types/global/utils.d.ts +103 -0
- package/types/icons.d.ts +1 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import type { ComponentChild } from 'preact';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import type { ClientContext } from '@/client/context';
|
|
10
|
+
import type { TRouteOptions } from '.';
|
|
11
|
+
import type { TDataProvider } from './response/page';
|
|
12
|
+
|
|
13
|
+
// App
|
|
14
|
+
import internalLayout from '@client/pages/_layout';
|
|
15
|
+
|
|
16
|
+
import * as layouts from '@/client/pages/**/_layout/index.tsx';
|
|
17
|
+
|
|
18
|
+
/*----------------------------------
|
|
19
|
+
- CONST
|
|
20
|
+
----------------------------------*/
|
|
21
|
+
|
|
22
|
+
export const layoutsList = layouts as ImportedLayouts;
|
|
23
|
+
|
|
24
|
+
/*----------------------------------
|
|
25
|
+
- TYPES
|
|
26
|
+
----------------------------------*/
|
|
27
|
+
type LayoutComponent = (attributes: { context: ClientContext }) => ComponentChild;
|
|
28
|
+
|
|
29
|
+
export type Layout = {
|
|
30
|
+
path: string,
|
|
31
|
+
Component: LayoutComponent,
|
|
32
|
+
data?: TDataProvider
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ImportedLayouts = {
|
|
36
|
+
[chunkId: string]: Layout["Component"]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*----------------------------------
|
|
40
|
+
- UTILS
|
|
41
|
+
----------------------------------*/
|
|
42
|
+
// TODO: getLayot only on server side, and pass the layout chunk id
|
|
43
|
+
export const getLayout = (routePath: string, routeOptions?: TRouteOptions): Layout | undefined => {
|
|
44
|
+
|
|
45
|
+
if (routeOptions === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
// W don't want a layout on this page
|
|
48
|
+
if (routeOptions.layout === false)
|
|
49
|
+
return undefined;
|
|
50
|
+
|
|
51
|
+
// options.id has been injected via the babel plugon
|
|
52
|
+
const chunkId = routeOptions["id"];
|
|
53
|
+
if (chunkId === undefined) {
|
|
54
|
+
console.error("Route informations where ID cas not injected:", routeOptions);
|
|
55
|
+
throw new Error(`ID has not injected for the following page route: ${routePath}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Layout via name
|
|
59
|
+
if (routeOptions.layout !== undefined) {
|
|
60
|
+
|
|
61
|
+
const { default: LayoutComponent, data } = layouts[routeOptions.layout];
|
|
62
|
+
if (LayoutComponent === undefined)
|
|
63
|
+
throw new Error(`No layout found with ID: ${routeOptions.layout}. registered layouts: ${Object.keys(layouts)}`);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
path: routeOptions.layout,
|
|
67
|
+
Component: layouts[routeOptions.layout].default,
|
|
68
|
+
data
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Automatic layout via the nearest _layout folder
|
|
73
|
+
for (const layoutPath in layouts)
|
|
74
|
+
if (
|
|
75
|
+
// The layout is nammed '' when it's at the root (@/client/pages/_layout)
|
|
76
|
+
layoutPath === '' // Root layout
|
|
77
|
+
// Exact match
|
|
78
|
+
|| chunkId === layoutPath
|
|
79
|
+
// Parent
|
|
80
|
+
|| chunkId.startsWith( layoutPath + '_' )
|
|
81
|
+
)
|
|
82
|
+
return {
|
|
83
|
+
path: layoutPath,
|
|
84
|
+
Component: layouts[layoutPath].default,
|
|
85
|
+
data: layouts[layoutPath].data,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// Internal layout
|
|
89
|
+
return {
|
|
90
|
+
path: '/',
|
|
91
|
+
Component: internalLayout
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import { pathToRegexp, Key } from 'path-to-regexp';
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import { getLayout } from './layouts';
|
|
10
|
+
|
|
11
|
+
// types
|
|
12
|
+
import type { TRouteOptions } from '.';
|
|
13
|
+
import type { TFrontRenderer } from './response/page';
|
|
14
|
+
import type { TRegisterPageArgs } from '@client/services/router';
|
|
15
|
+
|
|
16
|
+
/*----------------------------------
|
|
17
|
+
- UTILS
|
|
18
|
+
----------------------------------*/
|
|
19
|
+
|
|
20
|
+
export const getRegisterPageArgs = (...args: TRegisterPageArgs) => {
|
|
21
|
+
|
|
22
|
+
let path: string;
|
|
23
|
+
let options: Partial<TRouteOptions> = {};
|
|
24
|
+
let renderer: TFrontRenderer;
|
|
25
|
+
|
|
26
|
+
if (args.length === 2)
|
|
27
|
+
([path, renderer] = args)
|
|
28
|
+
else
|
|
29
|
+
([path, options, renderer] = args)
|
|
30
|
+
|
|
31
|
+
// Automatic layout form the nearest _layout folder
|
|
32
|
+
const layout = getLayout(path, options);
|
|
33
|
+
|
|
34
|
+
return { path, options, renderer, layout }
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const buildRegex = ( path: string ) => {
|
|
39
|
+
|
|
40
|
+
// pathToRegexp ne supporte plus les wildcards depuis 4.0
|
|
41
|
+
if (path.endsWith('*'))
|
|
42
|
+
path = path.substring(0, path.length - 1) + '(.*)';
|
|
43
|
+
|
|
44
|
+
// path => regex
|
|
45
|
+
const keys: Key[] = []
|
|
46
|
+
const regex = pathToRegexp(path, keys, {
|
|
47
|
+
sensitive: true
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
keys: keys.map(k => k.name),
|
|
52
|
+
regex
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
import type { HttpMethod } from '@server/services/router';
|
|
6
|
+
|
|
7
|
+
/*----------------------------------
|
|
8
|
+
- TYPES
|
|
9
|
+
----------------------------------*/
|
|
10
|
+
|
|
11
|
+
// The fetcher can be undefined if we put a condition on it
|
|
12
|
+
// By example if we want to fetch an api endpoint only if the url contains a certain url parameter
|
|
13
|
+
export type TFetcherList = { [id: string]: /*TFetcher | */Promise<any> | undefined }
|
|
14
|
+
|
|
15
|
+
export type TFetcher<TData extends any = unknown> = {
|
|
16
|
+
|
|
17
|
+
// For async calls: api.post(...).then((data) => ...)
|
|
18
|
+
then: (callback: (data: TData) => void) => Promise<TData>,
|
|
19
|
+
catch: (callback: (data: any) => false | void) => Promise<TData>,
|
|
20
|
+
finally: (callback: () => void) => Promise<TData>,
|
|
21
|
+
run: () => Promise<TData>,
|
|
22
|
+
|
|
23
|
+
method: HttpMethod,
|
|
24
|
+
path: string,
|
|
25
|
+
data?: TPostDataWithFile,
|
|
26
|
+
options?: TApiFetchOptions
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TFetcherArgs = [
|
|
30
|
+
method: HttpMethod,
|
|
31
|
+
path: string,
|
|
32
|
+
data?: TPostDataWithFile,
|
|
33
|
+
options?: TApiFetchOptions
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
export type TApiFetchOptions = {
|
|
37
|
+
captcha?: string, // Action id (required by recaptcha)
|
|
38
|
+
onProgress?: (percent: number) => void,
|
|
39
|
+
// Default: json
|
|
40
|
+
encoding?: 'json' | 'multipart'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type TPostData = TPostDataWithFile
|
|
44
|
+
|
|
45
|
+
export type TPostDataWithFile = { [key: string]: PrimitiveValue }
|
|
46
|
+
|
|
47
|
+
export type TPostDataWithoutFile = { [key: string]: PrimitiveValue }
|
|
48
|
+
|
|
49
|
+
// https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
|
|
50
|
+
type TypeWithGeneric<T> = TFetcher<T>
|
|
51
|
+
type extractGeneric<Type> = Type extends TypeWithGeneric<infer X> ? X : never
|
|
52
|
+
|
|
53
|
+
export type TDataReturnedByFetchers<TProvidedData extends TFetcherList = {}> = {
|
|
54
|
+
[Property in keyof TProvidedData]: ThenArg< TProvidedData[Property] >
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/*----------------------------------
|
|
58
|
+
- CLASS
|
|
59
|
+
----------------------------------*/
|
|
60
|
+
export default abstract class ApiClient {
|
|
61
|
+
|
|
62
|
+
/*----------------------------------
|
|
63
|
+
- TOP LEVEL
|
|
64
|
+
----------------------------------*/
|
|
65
|
+
|
|
66
|
+
public abstract set( newData: TObjetDonnees );
|
|
67
|
+
|
|
68
|
+
public abstract reload( ids?: string | string[], params?: TObjetDonnees );
|
|
69
|
+
|
|
70
|
+
/*----------------------------------
|
|
71
|
+
- LOW LEVEL
|
|
72
|
+
----------------------------------*/
|
|
73
|
+
|
|
74
|
+
public abstract createFetcher<TData extends unknown = unknown>(...args: TFetcherArgs): TFetcher<TData>;
|
|
75
|
+
|
|
76
|
+
public abstract fetchSync(fetchers: TFetcherList, alreadyLoadedData: {}): Promise<TObjetDonnees>;
|
|
77
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Core
|
|
6
|
+
import Response from '../response';
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { TBasicUser } from '@server/services/auth';
|
|
10
|
+
|
|
11
|
+
/*----------------------------------
|
|
12
|
+
- TYPES
|
|
13
|
+
----------------------------------*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/*----------------------------------
|
|
17
|
+
- CONTEXT
|
|
18
|
+
----------------------------------*/
|
|
19
|
+
export default abstract class BaseRequest {
|
|
20
|
+
|
|
21
|
+
// Permet d'accèder à l'instance complète via spread
|
|
22
|
+
public request: this = this;
|
|
23
|
+
public url!: string;
|
|
24
|
+
public host!: string;
|
|
25
|
+
|
|
26
|
+
public data: TObjetDonnees = {};
|
|
27
|
+
public abstract response?: Response;
|
|
28
|
+
public user: TBasicUser | null = null;
|
|
29
|
+
|
|
30
|
+
public constructor(
|
|
31
|
+
public path: string,
|
|
32
|
+
) {
|
|
33
|
+
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import { FunctionalComponent } from "preact";
|
|
7
|
+
|
|
8
|
+
// Core
|
|
9
|
+
import { TAnyRoute } from "..";
|
|
10
|
+
import type ClientRequest from '@client/services/router/request';
|
|
11
|
+
import Page from '@client/services/router/response/page'
|
|
12
|
+
|
|
13
|
+
/*----------------------------------
|
|
14
|
+
- TYPES
|
|
15
|
+
----------------------------------*/
|
|
16
|
+
|
|
17
|
+
export type TResponseData = Page
|
|
18
|
+
|
|
19
|
+
/*----------------------------------
|
|
20
|
+
- CONTEXT
|
|
21
|
+
----------------------------------*/
|
|
22
|
+
export default abstract class BaseResponse<
|
|
23
|
+
TData extends TResponseData = Page,
|
|
24
|
+
TRequest extends ClientRequest = ClientRequest
|
|
25
|
+
> {
|
|
26
|
+
|
|
27
|
+
public data?: TData;
|
|
28
|
+
public request: TRequest;
|
|
29
|
+
public route?: TAnyRoute;
|
|
30
|
+
|
|
31
|
+
public constructor(
|
|
32
|
+
request: TRequest,
|
|
33
|
+
) {
|
|
34
|
+
// ServerResponse et ClientResponse assignent request.response
|
|
35
|
+
request.response = this;
|
|
36
|
+
this.request = request as TRequest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public setRoute(route: TAnyRoute) {
|
|
40
|
+
this.route = route;
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public abstract redirect(url: string, code: number);
|
|
45
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Npm
|
|
6
|
+
import type { VNode } from 'preact';
|
|
7
|
+
import type { Thing } from 'schema-dts';
|
|
8
|
+
|
|
9
|
+
// Core libs
|
|
10
|
+
import { ClientOrServerRouter, TClientOrServerContextForPage, TRoute, TErrorRoute } from '@common/router';
|
|
11
|
+
import { TFetcherList, TDataReturnedByFetchers } from '@common/router/request/api';
|
|
12
|
+
|
|
13
|
+
/*----------------------------------
|
|
14
|
+
- TYPES
|
|
15
|
+
----------------------------------*/
|
|
16
|
+
|
|
17
|
+
// The function that fetch data from the api before to pass them as context to the renderer
|
|
18
|
+
export type TDataProvider<TProvidedData extends TFetcherList = TFetcherList> = (
|
|
19
|
+
context: TClientOrServerContextForPage & {
|
|
20
|
+
// URL query parameters
|
|
21
|
+
// TODO: typings
|
|
22
|
+
data: {[key: string]: string | number}
|
|
23
|
+
}
|
|
24
|
+
) => TProvidedData
|
|
25
|
+
|
|
26
|
+
// The function that renders routes
|
|
27
|
+
export type TFrontRenderer<
|
|
28
|
+
TProvidedData extends TFetcherList = TFetcherList,
|
|
29
|
+
TAdditionnalData extends {} = {},
|
|
30
|
+
TRouter = ClientOrServerRouter,
|
|
31
|
+
> = (
|
|
32
|
+
context: (
|
|
33
|
+
TClientOrServerContextForPage
|
|
34
|
+
&
|
|
35
|
+
TAdditionnalData
|
|
36
|
+
&
|
|
37
|
+
{
|
|
38
|
+
context: TClientOrServerContextForPage,
|
|
39
|
+
data: {[key: string]: PrimitiveValue}
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
) => VNode<any> | null
|
|
43
|
+
|
|
44
|
+
// Script or CSS resource
|
|
45
|
+
export type TPageResource = {
|
|
46
|
+
id: string,
|
|
47
|
+
attrs?: TObjetDonnees
|
|
48
|
+
} & ({
|
|
49
|
+
inline: string
|
|
50
|
+
} | {
|
|
51
|
+
url: string,
|
|
52
|
+
preload?: boolean
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
type TMetasDict = {
|
|
56
|
+
[key: string]: string | Date | undefined | null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type TMetasList = ({ $: string } & TMetasDict)[]
|
|
60
|
+
|
|
61
|
+
const debug = false;
|
|
62
|
+
|
|
63
|
+
/*----------------------------------
|
|
64
|
+
- CLASS
|
|
65
|
+
----------------------------------*/
|
|
66
|
+
export default abstract class PageResponse<TRouter extends ClientOrServerRouter = ClientOrServerRouter> {
|
|
67
|
+
|
|
68
|
+
// Metadata
|
|
69
|
+
public chunkId: string;
|
|
70
|
+
public title?: string;
|
|
71
|
+
public description?: string;
|
|
72
|
+
public bodyClass: Set<string> = new Set<string>();
|
|
73
|
+
public bodyId?: string;
|
|
74
|
+
public url: string;
|
|
75
|
+
|
|
76
|
+
// Resources
|
|
77
|
+
public head: TMetasList = [];
|
|
78
|
+
public metas: TMetasDict = {};
|
|
79
|
+
public jsonld: Thing[] = [];
|
|
80
|
+
public scripts: TPageResource[] = [];
|
|
81
|
+
public style: TPageResource[] = [];
|
|
82
|
+
|
|
83
|
+
// Data
|
|
84
|
+
public fetchers: TFetcherList = {};
|
|
85
|
+
public data: TObjetDonnees = {};
|
|
86
|
+
|
|
87
|
+
public constructor(
|
|
88
|
+
public route: TRoute | TErrorRoute,
|
|
89
|
+
public renderer: TFrontRenderer,
|
|
90
|
+
public context: TClientOrServerContextForPage
|
|
91
|
+
) {
|
|
92
|
+
|
|
93
|
+
this.chunkId = context.route.options["id"];
|
|
94
|
+
|
|
95
|
+
this.url = context.request.url;
|
|
96
|
+
|
|
97
|
+
this.fetchers = this.createFetchers(route.options.data);
|
|
98
|
+
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private createFetchers( dataProvider?: TDataProvider ) {
|
|
102
|
+
|
|
103
|
+
// Load the fetchers list to load data if needed
|
|
104
|
+
if (dataProvider)
|
|
105
|
+
return dataProvider({
|
|
106
|
+
...this.context,
|
|
107
|
+
data: this.context.request.data
|
|
108
|
+
});
|
|
109
|
+
else
|
|
110
|
+
return {}
|
|
111
|
+
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
public async fetchData() {
|
|
115
|
+
|
|
116
|
+
// Fetch layout data
|
|
117
|
+
if (this.layout?.data) {
|
|
118
|
+
const fetchers = this.createFetchers(this.layout.data);
|
|
119
|
+
this.fetchers = { ...this.fetchers, ...fetchers };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Fetch page data
|
|
123
|
+
debug && console.log(`[router][page] Fetching api data:` + Object.keys(this.fetchers));
|
|
124
|
+
this.data = await this.context.request.api.fetchSync( this.fetchers, this.data );
|
|
125
|
+
|
|
126
|
+
return this.data;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
import type Driver from '@server/services/disks/driver';
|
|
3
|
+
import { Anomaly } from '@common/errors';
|
|
4
|
+
|
|
5
|
+
export type LexicalNode = {
|
|
6
|
+
version: number,
|
|
7
|
+
type: string,
|
|
8
|
+
children?: LexicalNode[],
|
|
9
|
+
// Attachement
|
|
10
|
+
src?: string;
|
|
11
|
+
// Headhing
|
|
12
|
+
text?: string;
|
|
13
|
+
anchor?: string;
|
|
14
|
+
tag?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type LexicalState = {
|
|
18
|
+
root: LexicalNode
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type TRenderOptions = {
|
|
22
|
+
|
|
23
|
+
format?: 'html' | 'text', // Default = html
|
|
24
|
+
transform?: RteUtils["transformNode"],
|
|
25
|
+
|
|
26
|
+
render?: (
|
|
27
|
+
node: LexicalNode,
|
|
28
|
+
parent: LexicalNode | null,
|
|
29
|
+
options: TRenderOptions
|
|
30
|
+
) => Promise<LexicalNode>,
|
|
31
|
+
|
|
32
|
+
attachements?: {
|
|
33
|
+
disk: Driver,
|
|
34
|
+
directory: string,
|
|
35
|
+
prevVersion?: string | LexicalState | null,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type TSkeleton = {
|
|
40
|
+
id: string,
|
|
41
|
+
title: string,
|
|
42
|
+
level: number,
|
|
43
|
+
childrens: TSkeleton
|
|
44
|
+
}[];
|
|
45
|
+
|
|
46
|
+
export type TContentAssets = {
|
|
47
|
+
attachements: string[],
|
|
48
|
+
skeleton: TSkeleton
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default abstract class RteUtils {
|
|
52
|
+
|
|
53
|
+
public async render(
|
|
54
|
+
content: string | LexicalState,
|
|
55
|
+
options: TRenderOptions = {}
|
|
56
|
+
): Promise<TContentAssets & {
|
|
57
|
+
html: string | null,
|
|
58
|
+
json: string | LexicalState,
|
|
59
|
+
}> {
|
|
60
|
+
|
|
61
|
+
// Transform content
|
|
62
|
+
const assets: TContentAssets = {
|
|
63
|
+
attachements: [],
|
|
64
|
+
skeleton: []
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Parse content if string
|
|
68
|
+
let json = this.parseState(content);
|
|
69
|
+
if (json === false)
|
|
70
|
+
return { html: '', json: content, ...assets }
|
|
71
|
+
|
|
72
|
+
// Parse prev version if string
|
|
73
|
+
if (typeof options?.attachements?.prevVersion === 'string') {
|
|
74
|
+
try {
|
|
75
|
+
options.attachements.prevVersion = JSON.parse(options.attachements.prevVersion) as LexicalState;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Anomaly("Invalid JSON format for the given JSON RTE prev version.");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const root = await this.processContent(json.root, null, async (node, parent) => {
|
|
82
|
+
return await this.transformNode(node, parent, assets, options);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
json = { ...json, root };
|
|
86
|
+
|
|
87
|
+
// Delete unused attachements
|
|
88
|
+
const attachementOptions = options?.attachements;
|
|
89
|
+
if (attachementOptions && attachementOptions.prevVersion !== undefined) {
|
|
90
|
+
|
|
91
|
+
await this.processContent(root, null, async (node) => {
|
|
92
|
+
return await this.deleteUnusedFile(node, assets, attachementOptions);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Convert json to HTML
|
|
97
|
+
let html: string | null;
|
|
98
|
+
if (options.format === 'text')
|
|
99
|
+
html = await this.jsonToText( json.root );
|
|
100
|
+
else
|
|
101
|
+
html = await this.jsonToHtml( json, options );
|
|
102
|
+
|
|
103
|
+
return { html, json: content, ...assets };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private parseState( content: string | LexicalState ): LexicalState | false {
|
|
107
|
+
|
|
108
|
+
if (typeof content === 'string' && content.trim().startsWith('{')) {
|
|
109
|
+
try {
|
|
110
|
+
return JSON.parse(content) as LexicalState;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
throw new Anomaly("Invalid JSON format for the given JSON RTE content.");
|
|
113
|
+
}
|
|
114
|
+
} else if (content && typeof content === 'object' && content.root)
|
|
115
|
+
return content;
|
|
116
|
+
else
|
|
117
|
+
return false;
|
|
118
|
+
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected jsonToText(root: LexicalNode): string {
|
|
122
|
+
let result = '';
|
|
123
|
+
|
|
124
|
+
function traverse(node: LexicalNode) {
|
|
125
|
+
switch (node.type) {
|
|
126
|
+
case 'text':
|
|
127
|
+
// Leaf text node
|
|
128
|
+
result += node.text ?? '';
|
|
129
|
+
break;
|
|
130
|
+
case 'linebreak':
|
|
131
|
+
// Explicit line break node
|
|
132
|
+
result += '\n';
|
|
133
|
+
break;
|
|
134
|
+
default:
|
|
135
|
+
// Container or block node: dive into children if any
|
|
136
|
+
if (node.children) {
|
|
137
|
+
node.children.forEach(traverse);
|
|
138
|
+
}
|
|
139
|
+
// After finishing a block-level node, append newline
|
|
140
|
+
if (isBlockNode(node.type)) {
|
|
141
|
+
result += '\n';
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Heuristic: treat these as blocks
|
|
148
|
+
function isBlockNode(type: string): boolean {
|
|
149
|
+
return [
|
|
150
|
+
'root',
|
|
151
|
+
'paragraph',
|
|
152
|
+
'heading',
|
|
153
|
+
'listitem',
|
|
154
|
+
'unorderedlist',
|
|
155
|
+
'orderedlist',
|
|
156
|
+
'quote',
|
|
157
|
+
'codeblock',
|
|
158
|
+
'table',
|
|
159
|
+
].includes(type);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
traverse(root);
|
|
163
|
+
|
|
164
|
+
// Trim trailing whitespace/newlines
|
|
165
|
+
return result.replace(/\s+$/, '');
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
public abstract jsonToHtml( json: LexicalState, options: TRenderOptions ): Promise<string | null>;
|
|
169
|
+
|
|
170
|
+
protected abstract processContent(
|
|
171
|
+
node: LexicalNode,
|
|
172
|
+
parent: LexicalNode | null,
|
|
173
|
+
callback: (node: LexicalNode, parent: LexicalNode | null) => Promise<LexicalNode>
|
|
174
|
+
): Promise<LexicalNode>;
|
|
175
|
+
|
|
176
|
+
protected abstract transformNode( node: LexicalNode, parent: LexicalNode | null, assets: TContentAssets, options: TRenderOptions ): Promise<LexicalNode>;
|
|
177
|
+
|
|
178
|
+
protected abstract deleteUnusedFile(
|
|
179
|
+
node: LexicalNode,
|
|
180
|
+
assets: TContentAssets,
|
|
181
|
+
options: NonNullable<TRenderOptions["attachements"]>
|
|
182
|
+
): Promise<LexicalNode>;
|
|
183
|
+
}
|
package/common/utils.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper to wait x seconds with promises
|
|
3
|
+
* Usage: await seconds(10); // Wait 10 seconds
|
|
4
|
+
* @param seconds The number of seconds to wait
|
|
5
|
+
* @returns A void promise
|
|
6
|
+
*/
|
|
7
|
+
export const seconds = (seconds: number) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
|
package/doc/TODO.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
* Fix erreurs type Client / Server context
|
|
2
|
+
* Server side: ServerContext
|
|
3
|
+
* Client side: ClientContext | ServerContext
|
|
4
|
+
* PageResponse extends Response
|
|
5
|
+
* Toast service
|
|
6
|
+
* ClientApplication hooks
|
|
7
|
+
app.on('bug')
|
|
8
|
+
app.on('error')
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Dependancies injection
|
|
12
|
+
|
|
13
|
+
# Full stack Pages
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import Router from '@server/services/router';
|
|
17
|
+
import { TRouterContext as ServerServices } from '@server/services/router/response';
|
|
18
|
+
import { TRouterContext as ClientServices } from '@client/services/router/response';
|
|
19
|
+
|
|
20
|
+
abstract class Controller<
|
|
21
|
+
TRouter extends Router,
|
|
22
|
+
TData extends any = any,
|
|
23
|
+
TUserAccess extends string = string
|
|
24
|
+
> {
|
|
25
|
+
|
|
26
|
+
abstract auth: TUserAccess;
|
|
27
|
+
|
|
28
|
+
abstract get( services: ServerServices<TRouter> ): Promise<TData>;
|
|
29
|
+
|
|
30
|
+
abstract render( context: TData, services: ClientServices<TRouter> ): ComponentChild;
|
|
31
|
+
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
//? /headhunter/missions/suggested'
|
|
37
|
+
class Missions extends Controller<CrossPath["router"]> {
|
|
38
|
+
|
|
39
|
+
auth = 'USER';
|
|
40
|
+
|
|
41
|
+
async get({ headhunting, response, auth }) {
|
|
42
|
+
|
|
43
|
+
const user = await auth.check('USER');
|
|
44
|
+
|
|
45
|
+
const suggested = await headhunting.missions.Suggest( user );
|
|
46
|
+
|
|
47
|
+
return { suggested }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
render({ page, api, suggested }) {
|
|
51
|
+
return (
|
|
52
|
+
<Page title="App title here" subtitle="SEO description here">{page.loading || <>
|
|
53
|
+
|
|
54
|
+
<section class="col">
|
|
55
|
+
|
|
56
|
+
<header class="row">
|
|
57
|
+
<h2 class="col-1">Suggested Missions</h2>
|
|
58
|
+
</header>
|
|
59
|
+
|
|
60
|
+
<div class="grid xa3">
|
|
61
|
+
{suggested.map( mission => (
|
|
62
|
+
<MissionCard mission={mission} />
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
</section>
|
|
66
|
+
|
|
67
|
+
</>}</Page>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|