5htp-core 0.2.6 → 0.2.7-2

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 (34) hide show
  1. package/package.json +5 -4
  2. package/src/client/app/index.ts +2 -2
  3. package/src/client/assets/css/text/icons.less +8 -2
  4. package/src/client/assets/css/utils/layouts.less +3 -0
  5. package/src/client/components/Form.ts +2 -2
  6. package/src/client/components/index.ts +2 -0
  7. package/src/client/components/input/Checkbox/index.less +0 -0
  8. package/src/client/components/input/Checkbox/index.tsx +58 -50
  9. package/src/client/components/input/Checkbox/old.tsx +74 -0
  10. package/src/client/components/inputv3/base.tsx +13 -1
  11. package/src/client/components/inputv3/date/index.tsx +49 -0
  12. package/src/client/components/inputv3/date/react-calendar.less +143 -0
  13. package/src/client/components/inputv3/date/react-daterange-picker.less +112 -0
  14. package/src/client/pages/useHeader.tsx +3 -2
  15. package/src/client/services/router/components/router.tsx +3 -2
  16. package/src/client/services/router/request/api.ts +0 -5
  17. package/src/client/services/router/request/index.ts +10 -0
  18. package/src/client/services/router/request/multipart.ts +120 -9
  19. package/src/server/app/config.ts +2 -0
  20. package/src/server/app/index.ts +4 -2
  21. package/src/server/services/console/index.ts +4 -4
  22. package/src/server/services/fetch/index.ts +1 -1
  23. package/src/server/services/router/http/index.ts +7 -3
  24. package/src/server/services/router/index.ts +10 -2
  25. package/src/server/services/router/response/index.ts +3 -1
  26. package/src/server/services/router/response/page/document.tsx +7 -19
  27. package/src/server/services/router/response/page/index.tsx +17 -1
  28. package/src/server/services/router/service.ts +1 -1
  29. package/src/types/global/modules.d.ts +1 -1
  30. package/src/client/components/input/Date/index.less +0 -167
  31. package/src/client/components/input/Date/index.tsx +0 -90
  32. package/src/client/services/metrics/index.ts +0 -37
  33. package/src/server/services/metrics/detect.ts +0 -109
  34. package/src/server/services/metrics/index.ts +0 -272
@@ -55,7 +55,8 @@ export default ({ service: router }: { service: Router }) => {
55
55
 
56
56
  // Page not found: Directly load with the browser
57
57
  if (newpage === undefined) {
58
- window.location.replace(request.path);
58
+ window.location.replace(request.url);
59
+ console.error("not found");
59
60
  return;
60
61
  // Unable to load (no connection, server error, ....)
61
62
  } else if (newpage === null) {
@@ -86,7 +87,7 @@ export default ({ service: router }: { service: Router }) => {
86
87
  // But when we call setLayout, the style of the previous layout are still oaded and applied
87
88
  // Find a way to unload the previous layout / page resources before to load the new one
88
89
  console.log(LogPrefix, `Changing layout. Before:`, curLayout, 'New layout:', newLayout);
89
- window.location.replace(request.path);
90
+ window.location.replace(request.url);
90
91
  return pages;
91
92
 
92
93
  context.app.setLayout(newLayout);
@@ -89,16 +89,11 @@ export default class ApiClient implements ApiClientService {
89
89
  fetcher.data = { ...(fetcher.data || {}), ...params };
90
90
 
91
91
  console.log("[api][reload]", id, fetcher.method, fetcher.path, fetcher.data);
92
- const indicator = this.toast.loading("Loading ...");
93
92
 
94
93
  this.fetchAsync(fetcher.method, fetcher.path, fetcher.data).then((data) => {
95
94
 
96
95
  this.set({ [id]: data });
97
96
 
98
- }).finally(() => {
99
-
100
- indicator.close(true);
101
-
102
97
  })
103
98
  }
104
99
  }
@@ -27,6 +27,8 @@ export default class ClientRequest<TRouter extends ClientRouter = ClientRouter>
27
27
 
28
28
  public api: ApiClient;
29
29
  public response?: ClientResponse<TRouter>;
30
+
31
+ public url: string;
30
32
  public hash?: string;
31
33
 
32
34
  public constructor(
@@ -38,8 +40,16 @@ export default class ClientRequest<TRouter extends ClientRouter = ClientRouter>
38
40
  super(location.pathname);
39
41
 
40
42
  this.host = window.location.host;
43
+ this.url = window.location.protocol + '//' + window.location.host + this.path;
41
44
  this.hash = location.hash;
42
45
 
46
+ // Extract search params
47
+ if (location.search) {
48
+ this.url += location.search;
49
+ this.data = Object.fromEntries( new URLSearchParams( location.search ));
50
+ }
51
+
52
+ // Request services
43
53
  this.api = new ApiClient(this.app, this);
44
54
  }
45
55
 
@@ -10,18 +10,129 @@ import { FileToUpload } from '@client/components/inputv3/file';
10
10
  - TYPES
11
11
  ----------------------------------*/
12
12
 
13
+ function mergeObjects(object1, object2) {
14
+ return [object1, object2].reduce(function (carry, objectToMerge) {
15
+ Object.keys(objectToMerge).forEach(function (objectKey) {
16
+ carry[objectKey] = objectToMerge[objectKey];
17
+ });
18
+ return carry;
19
+ }, {});
20
+ }
21
+
22
+ function isArray(val) {
23
+
24
+ return ({}).toString.call(val) === '[object Array]';
25
+ }
26
+
27
+ function isJsonObject(val) {
28
+
29
+ return !isArray(val) && typeof val === 'object' && !!val && !(val instanceof Blob) && !(val instanceof Date);
30
+ }
31
+
32
+ function isAppendFunctionPresent(formData) {
33
+
34
+ return typeof formData.append === 'function';
35
+ }
36
+
37
+ function isGlobalFormDataPresent() {
38
+
39
+ return typeof FormData === 'function';
40
+ }
41
+
42
+ function getDefaultFormData() {
43
+
44
+ if (isGlobalFormDataPresent()) {
45
+ return new FormData();
46
+ }
47
+ }
48
+
49
+ function convertRecursively(jsonObject, options, formData, parentKey) {
50
+
51
+ var index = 0;
52
+
53
+ for (var key in jsonObject) {
54
+
55
+ if (jsonObject.hasOwnProperty(key)) {
56
+
57
+ var propName = parentKey || key;
58
+ var value = options.mapping(jsonObject[key]);
59
+
60
+ if (parentKey && isJsonObject(jsonObject)) {
61
+ propName = parentKey + '[' + key + ']';
62
+ }
63
+
64
+ if (parentKey && isArray(jsonObject)) {
65
+
66
+ if (isArray(value) || options.showLeafArrayIndexes ) {
67
+ propName = parentKey + '[' + index + ']';
68
+ } else {
69
+ propName = parentKey + '[]';
70
+ }
71
+ }
72
+
73
+ // Exract the file object from value
74
+ if (typeof value === 'object' && value instanceof FileToUpload)
75
+ value = value.data;
76
+
77
+ if (isArray(value) || isJsonObject(value)) {
78
+
79
+ convertRecursively(value, options, formData, propName);
80
+
81
+ } else if (value instanceof FileList) {
82
+
83
+ for (var j = 0; j < value.length; j++) {
84
+ formData.append(propName + '[' + j + ']', value.item(j));
85
+ }
86
+ } else if (value instanceof Blob) {
87
+
88
+ formData.append(propName, value, value.name);
89
+
90
+ } else if (value instanceof Date) {
91
+
92
+ formData.append(propName, value.toISOString());
93
+
94
+ } else if (((value === null && options.includeNullValues) || value !== null) && value !== undefined) {
95
+
96
+ formData.append(propName, value);
97
+ }
98
+ }
99
+ index++;
100
+ }
101
+ return formData;
102
+ }
103
+
13
104
  /*----------------------------------
14
105
  - UTILS
15
106
  ----------------------------------*/
16
- export const toMultipart = (postData: TPostData) => {
17
-
18
- const formData = new FormData();
19
- for (const key in postData) {
20
- let data = postData[key];
21
- if (typeof data === 'object' && (data instanceof FileToUpload))
22
- data = data.data;
23
- formData.append(key, data);
107
+ /* Based on https://github.com/hyperatom/json-form-data
108
+ Changes:
109
+ - Add support for FileToUpload
110
+ */
111
+ export const toMultipart = (jsonObject: TPostData, options) => {
112
+
113
+ if (options && options.initialFormData) {
114
+
115
+ if (!isAppendFunctionPresent(options.initialFormData)) {
116
+ throw 'initialFormData must have an append function.';
117
+ }
118
+ } else if (!isGlobalFormDataPresent()) {
119
+
120
+ throw 'This environment does not have global form data. options.initialFormData must be specified.';
24
121
  }
25
122
 
26
- return formData;
123
+ var defaultOptions = {
124
+ initialFormData: getDefaultFormData(),
125
+ showLeafArrayIndexes: true,
126
+ includeNullValues: false,
127
+ mapping: function(value) {
128
+ if (typeof value === 'boolean') {
129
+ return +value ? '1': '0';
130
+ }
131
+ return value;
132
+ }
133
+ };
134
+
135
+ var mergedOptions = mergeObjects(defaultOptions, options || {});
136
+
137
+ return convertRecursively(jsonObject, mergedOptions, mergedOptions.initialFormData);
27
138
  }
@@ -51,6 +51,8 @@ type AppIdentityConfig = {
51
51
 
52
52
  web: {
53
53
  title: string,
54
+ titleSuffix: string,
55
+ fullTitle: string,
54
56
  description: string,
55
57
  version: string
56
58
  }
@@ -183,7 +183,7 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
183
183
  service.status = 'starting';
184
184
 
185
185
  if (service.register)
186
- service.register();
186
+ await service.register();
187
187
 
188
188
  // Register commands
189
189
  if (service.commands)
@@ -193,9 +193,11 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
193
193
  if (service.start) {
194
194
  service.started = service.start();
195
195
  await service.started.catch(e => {
196
- console.error("Catched error while starting service " + serviceClassName + '. Exiting process if mode production.');
196
+ console.error("Catched error while starting service " + serviceClassName + '. Exiting process if mode production.', e);
197
197
  if (this.env.profile === 'prod')
198
198
  process.exit();
199
+ else
200
+ throw e;
199
201
  })
200
202
  }
201
203
 
@@ -190,10 +190,10 @@ export default class Console extends Service<Config, Hooks, Application> {
190
190
  }
191
191
 
192
192
  private clean() {
193
- this.config.debug && console.log(LogPrefix, `Clean logs buffer. Current size:`, this.logs.length, '/', this.config.bufferLimit);
193
+ /*this.config.debug && console.log(LogPrefix, `Clean logs buffer. Current size:`, this.logs.length, '/', this.config.bufferLimit);
194
194
  const bufferOverflow = this.logs.length - this.config.bufferLimit;
195
195
  if (bufferOverflow > 0)
196
- this.logs = this.logs.slice(bufferOverflow);
196
+ this.logs = this.logs.slice(bufferOverflow);*/
197
197
  }
198
198
 
199
199
  /*----------------------------------
@@ -230,7 +230,7 @@ export default class Console extends Service<Config, Hooks, Application> {
230
230
  // On envoi l'email avant l'insertion dans bla bdd
231
231
  // Car cette denrière a plus de chances de provoquer une erreur
232
232
  const logsHtml = this.printHtml(
233
- this.logs.filter(e => e.channelId === channelId),
233
+ this.logs/*.filter(e => e.channelId === channelId)*/.slice(-100),
234
234
  true
235
235
  );
236
236
 
@@ -372,7 +372,7 @@ export default class Console extends Service<Config, Hooks, Application> {
372
372
  }
373
373
  }
374
374
 
375
- return this.printHtml( entries.reverse() );
375
+ return this.printHtml( entries );
376
376
  }
377
377
 
378
378
  public printHtml(logs: TLog[], full: boolean = false): string {
@@ -93,7 +93,7 @@ export default class FetchService extends Service<Config, Hooks, Application> {
93
93
 
94
94
  // Convert to webp and finalize
95
95
  const processedBuffer = await processing.webp({ quality }).toBuffer().catch(e => {
96
- console.error(LogPrefix, `Error while processing image at ${imageBuffer}:`, e);
96
+ console.error(LogPrefix, `Error while processing image at ${imageFileUrl}:`, e);
97
97
  return null;
98
98
  })
99
99
 
@@ -43,6 +43,12 @@ export type Config = {
43
43
  upload: {
44
44
  maxSize: string // Expression package bytes
45
45
  },
46
+ csp: {
47
+ default?: string[],
48
+ styles?: string[],
49
+ images?: string[],
50
+ scripts: string[],
51
+ }
46
52
  }
47
53
 
48
54
  export type Hooks = {
@@ -193,9 +199,7 @@ export default class HttpServer extends Service<Config, Hooks, Application> {
193
199
  routes.use( csp.expressCspHeader({
194
200
  directives: {
195
201
  'script-src': [csp.INLINE, csp.SELF,
196
- // Whitelist external js scripts
197
- "https://www.googletagmanager.com/gtag/js",
198
- "https://cdn.jsdelivr.net"
202
+ ...this.config.csp.scripts
199
203
  ]
200
204
  }
201
205
  }));
@@ -282,7 +282,7 @@ export default class ServerRouter<
282
282
  //await TrackingService.LoadCache();
283
283
 
284
284
  // Generate typescript typings
285
- if (this.app.env.profile = 'dev')
285
+ if (this.app.env.profile === 'dev')
286
286
  this.genTypings();
287
287
 
288
288
  // Ordonne par ordre de priorité
@@ -468,7 +468,12 @@ declare type Routes = {
468
468
 
469
469
  public async resolve(request: ServerRequest<this>): Promise<ServerResponse<this>> {
470
470
 
471
- console.info(request.ip, request.method, request.domain, request.path);
471
+ console.info(LogPrefix, request.ip, request.method, request.domain, request.path);
472
+
473
+ if (this.status === 'starting') {
474
+ console.log(LogPrefix, `Waiting for servert to be resdy before resolving request`);
475
+ await this.started;
476
+ }
472
477
 
473
478
  const response = new ServerResponse<this>(request);
474
479
 
@@ -545,6 +550,9 @@ declare type Routes = {
545
550
  // Rapport / debug
546
551
  if (code === 500) {
547
552
 
553
+ // Print the error here so the stacktrace appears in the bug report logs
554
+ console.log(LogPrefix, "Error catched from the router:", e);
555
+
548
556
  // Report error
549
557
  await this.app.runHook('error', e, request);
550
558
 
@@ -133,7 +133,9 @@ export default class ServerResponse<
133
133
  for (const serviceName in this.router.services) {
134
134
 
135
135
  const routerService = this.router.services[serviceName];
136
- contextServices[ serviceName ] = routerService.requestService( this.request );
136
+ const requestService = routerService.requestService( this.request );
137
+ if (requestService !== null)
138
+ contextServices[ serviceName ] = requestService;
137
139
 
138
140
  }
139
141
 
@@ -19,10 +19,10 @@ import type Page from '.';
19
19
  /*----------------------------------
20
20
  - SERVICE
21
21
  ----------------------------------*/
22
- export default class DocumentRenderer {
22
+ export default class DocumentRenderer<TRouter extends Router> {
23
23
 
24
24
  public constructor(
25
- public router: Router,
25
+ public router: TRouter,
26
26
  public app = router.app
27
27
  ) {
28
28
 
@@ -55,7 +55,7 @@ export default class DocumentRenderer {
55
55
  );
56
56
  }
57
57
 
58
- public async page( html: string, page: Page, response: ServerResponse<Router> ) {
58
+ public async page( html: string, page: Page, response: ServerResponse<TRouter> ) {
59
59
 
60
60
  const fullUrl = this.router.http.publicUrl + response.request.path;
61
61
 
@@ -133,7 +133,7 @@ export default class DocumentRenderer {
133
133
  </>
134
134
  }
135
135
 
136
- private styles( page ) {
136
+ private styles( page: Page ) {
137
137
  return <>
138
138
  <link rel="stylesheet" type="text/css" href="/public/icons.css" />
139
139
  <link rel="preload" href="/public/client.css" as="style" />
@@ -151,7 +151,7 @@ export default class DocumentRenderer {
151
151
  </>
152
152
  }
153
153
 
154
- private async scripts( response, page ) {
154
+ private async scripts( response: ServerResponse<TRouter>, page: Page ) {
155
155
 
156
156
  const context = safeStringify( response.forSsr(page) );
157
157
 
@@ -174,21 +174,9 @@ export default class DocumentRenderer {
174
174
  <link rel="preload" href={script.url} as="script" />
175
175
  <script type="text/javascript" src={script.url} {...script.attrs || {}} />
176
176
  </> : <>
177
- <script type="text/javascript" {...script.attrs || {}} id={script.id} dangerouslySetInnerHTML={{ __html: script.inline }} />
177
+ <script type="text/javascript" {...script.attrs || {}} id={script.id}
178
+ dangerouslySetInnerHTML={{ __html: script.inline }} />
178
179
  </>)}
179
-
180
- {/* Initialize GTM & GA for pagechange events */}
181
- {/* TODO: append via the metrics module */}
182
- {/*<script async src={"https://www.googletagmanager.com/gtag/js?id=" + this.app.config.tracking.ga.pub}></script>
183
- <script dangerouslySetInnerHTML={{ __html: `
184
- window.dataLayer = window.dataLayer || [];
185
- function gtag(){dataLayer.push(arguments);}
186
- gtag('js', new Date());
187
-
188
- gtag('config', '${this.app.config.tracking.ga.pub}', {
189
- send_page_view: false
190
- });
191
- `}} />*/}
192
180
  </>
193
181
  }
194
182
  }
@@ -22,6 +22,11 @@ const chunks = require('./chunk-manifest.json');
22
22
  - TYPES
23
23
  ----------------------------------*/
24
24
 
25
+ const seoLimits = {
26
+ title: 70,
27
+ description: 255
28
+ }
29
+
25
30
  /*----------------------------------
26
31
  - FONCTION
27
32
  ----------------------------------*/
@@ -35,7 +40,8 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
35
40
  public layout?: Layout,
36
41
 
37
42
  public route = context.route,
38
- public router = context.request.router
43
+ public app = context.app,
44
+ public router = context.request.router,
39
45
 
40
46
  ) {
41
47
 
@@ -45,6 +51,16 @@ export default class Page<TRouter extends Router = Router> extends PageResponse<
45
51
 
46
52
  public render(): Promise<string> {
47
53
 
54
+ // Complete SEO metadatas
55
+ const titleSuffix = ' | ' + this.app.identity.web.titleSuffix
56
+ if (this.title === undefined)
57
+ this.title = this.app.identity.web.fullTitle;
58
+ else if (this.title.length < seoLimits.title - titleSuffix.length)
59
+ this.title += titleSuffix;
60
+
61
+ if (this.description === undefined)
62
+ this.description = this.app.identity.web.description;
63
+
48
64
  // We render page & document separatly,
49
65
  // because document needs to access to runtime assigned values
50
66
  // Ex: runtime added scripts, title, metas, ....
@@ -43,6 +43,6 @@ export default abstract class RouterService<TRouter extends Router = Router> {
43
43
 
44
44
  public abstract register(): Promise<void>;
45
45
 
46
- public abstract requestService( request: ServerRequest<TRouter> ): RequestService;
46
+ public abstract requestService( request: ServerRequest<TRouter> ): RequestService | null;
47
47
 
48
48
  }
@@ -36,7 +36,7 @@ declare module "*.svg" {
36
36
  export = value;
37
37
  }
38
38
 
39
- declare module "*.png" {
39
+ declare module "*.(png|webp)" {
40
40
  const value: string;
41
41
  export = value;
42
42
  }
@@ -1,167 +0,0 @@
1
- .champ.periode {
2
-
3
- display: flex;
4
-
5
- > .input-date {
6
-
7
- display: flex;
8
- justify-content: center;
9
- align-items: center;
10
-
11
- > input[type=number] {
12
- -moz-appearance: textfield;
13
- padding: 0;
14
- text-align: center;
15
-
16
- width: 30px;
17
- &.annee { width: 45px; }
18
-
19
- &::-webkit-outer-spin-button,
20
- &::-webkit-inner-spin-button {
21
- -webkit-appearance: none;
22
- margin: 0;
23
- }
24
- }
25
-
26
- }
27
- }
28
-
29
- // https://github.com/wojtekmaj/react-calendar/blob/master/src/Calendar.less
30
- .react-calendar {
31
-
32
- &--doubleView {
33
- width: 700px;
34
-
35
- .react-calendar__viewContainer {
36
- display: flex;
37
- margin: -.5em;
38
-
39
- > * {
40
- width: 50%;
41
- margin: .5em;
42
- }
43
- }
44
- }
45
-
46
- &, & *, & *:before, & *:after {
47
- -moz-box-sizing: border-box;
48
- -webkit-box-sizing: border-box;
49
- box-sizing: border-box;
50
- }
51
-
52
- button {
53
- margin: 0;
54
- border: 0;
55
- outline: none;
56
- border-radius: @radiusBase;
57
-
58
- &:enabled {
59
- &:hover {
60
- cursor: pointer;
61
- }
62
- }
63
- }
64
-
65
- &__navigation {
66
- height: 40px;
67
-
68
- button {
69
- min-width: 44px;
70
- background: none;
71
-
72
- &:enabled {
73
- &:hover, &:focus {
74
- background-color: var(--cBgControl)
75
- }
76
- }
77
-
78
- &[disabled] {
79
- color: var(--cTxtDiscret)
80
- }
81
- }
82
- }
83
-
84
- &__month-view {
85
- &__weekdays {
86
- text-align: center;
87
- text-transform: uppercase;
88
- font-weight: bold;
89
- font-size: .75em;
90
-
91
- &__weekday {
92
- padding: .5em;
93
- }
94
- }
95
-
96
- &__weekNumbers {
97
- font-weight: bold;
98
-
99
- .react-calendar__tile {
100
- display: flex;
101
- align-items: center;
102
- justify-content: center;
103
- font-size: .75em;
104
- padding: calc(.75em / .75) calc(.5em / .75);
105
- }
106
- }
107
-
108
- &__days {
109
- &__day {
110
- &--weekend {
111
- //color: rgb(209, 0, 0);
112
- }
113
-
114
- &--neighboringMonth {
115
- color: var(--cTxtDesc)
116
- }
117
- }
118
- }
119
- }
120
-
121
- &__year-view,
122
- &__decade-view,
123
- &__century-view {
124
- .react-calendar__tile {
125
- padding: 2em .5em;
126
- }
127
- }
128
-
129
- &__tile {
130
- max-width: 100%;
131
- text-align: center;
132
- padding: .75em .5em;
133
- background: none;
134
-
135
- &:disabled {
136
- color: var(--cTxtDiscret)
137
- }
138
-
139
- &:enabled {
140
- &:hover, &:focus {
141
- background-color: var(--cBgControl)
142
- }
143
- }
144
-
145
- &--now {
146
- font-weight: 700;
147
- }
148
-
149
- &--hasActive {
150
- background: var(--cPrincipale);
151
- color: #fff;
152
- }
153
-
154
- &--active {
155
- background: var(--cPrincipale);
156
- color: #fff;
157
- }
158
- }
159
-
160
- &--selectRange {
161
- .react-calendar__tile {
162
- &--hover {
163
- background-color: var(--cBgControl)
164
- }
165
- }
166
- }
167
- }