5htp-core 0.4.9-1 → 0.4.9-5

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 (31) hide show
  1. package/package.json +4 -1
  2. package/src/client/assets/css/components/lists.less +3 -3
  3. package/src/client/assets/css/components/table.less +1 -0
  4. package/src/client/components/Form.ts +1 -1
  5. package/src/client/components/Select/ChoiceElement.tsx +20 -27
  6. package/src/client/components/Select/index.tsx +24 -42
  7. package/src/client/components/Table/index.tsx +24 -79
  8. package/src/client/components/inputv3/Rte/Editor.tsx +16 -8
  9. package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +6 -1
  10. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +23 -23
  11. package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +26 -24
  12. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +10 -38
  13. package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +349 -356
  14. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +80 -84
  15. package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +308 -347
  16. package/src/client/components/inputv3/Rte/style.less +0 -2
  17. package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +1 -1
  18. package/src/client/components/inputv3/base.less +0 -6
  19. package/src/client/components/inputv3/base.tsx +2 -2
  20. package/src/client/components/inputv3/file/index.tsx +50 -48
  21. package/src/client/components/inputv3/index.tsx +0 -0
  22. package/src/client/services/router/request/api.ts +29 -13
  23. package/src/common/router/request/api.ts +6 -4
  24. package/src/common/validation/schema.ts +8 -2
  25. package/src/common/validation/validator.ts +3 -3
  26. package/src/server/services/disks/driver.ts +2 -0
  27. package/src/server/services/disks/drivers/s3/index.ts +30 -1
  28. package/src/server/services/router/request/index.ts +1 -1
  29. package/src/server/utils/rte.ts +217 -59
  30. package/src/server/utils/slug.ts +67 -0
  31. package/src/server/services/schema/rte.ts +0 -110
@@ -84,8 +84,6 @@
84
84
  .editor-input {
85
85
  min-height: 150px;
86
86
  resize: none;
87
- font-size: 15px;
88
- caret-color: rgb(5, 5, 5);
89
87
  position: relative;
90
88
  tab-size: 1;
91
89
  outline: 0;
@@ -76,7 +76,7 @@ const theme: EditorThemeClasses = {
76
76
  listitem: 'PlaygroundEditorTheme__nestedListItem',
77
77
  },
78
78
  olDepth: [
79
- 'PlaygroundEditorTheme__ol1',
79
+ 'steps',
80
80
  'PlaygroundEditorTheme__ol2',
81
81
  'PlaygroundEditorTheme__ol3',
82
82
  'PlaygroundEditorTheme__ol4',
@@ -47,12 +47,6 @@
47
47
 
48
48
  padding: 0;
49
49
 
50
- > * {
51
- + * {
52
- border-top: solid 1px var(--cLine);;
53
- }
54
- }
55
-
56
50
  .input.text {
57
51
  border: none;
58
52
  box-shadow: none;
@@ -69,7 +69,7 @@ export function useInput<TValue>(
69
69
  if (state.changed === false)
70
70
  return;
71
71
 
72
- //console.log(`[input] Commit value:`, state.value, externalValue);
72
+ console.log(`[input] Commit value:`, state.value, externalValue);
73
73
  if (onChange !== undefined)
74
74
  onChange(state.value);
75
75
  }
@@ -78,7 +78,7 @@ export function useInput<TValue>(
78
78
  React.useEffect(() => {
79
79
 
80
80
  if (externalValue !== undefined && externalValue !== state.value) {
81
- //console.log("External value change", externalValue);
81
+ console.log("External value change", externalValue);
82
82
  setState({ value: externalValue, valueSource: 'external', changed: true })
83
83
  }
84
84
 
@@ -8,7 +8,8 @@ import { ComponentChild } from 'preact';
8
8
  // Composants généraux
9
9
  import Bouton from '@client/components/button';
10
10
 
11
- // Libs
11
+ // Core libs
12
+ import { InputWrapper } from '../base';
12
13
  import useContext from '@/client/context';
13
14
 
14
15
  // specific
@@ -59,7 +60,7 @@ const normalizeFile = (file: File) => new FileToUpload({
59
60
  export type Props = {
60
61
 
61
62
  // Input
62
- title: ComponentChild,
63
+ title: string,
63
64
  value?: string | FileToUpload, // string = already registered
64
65
 
65
66
  // Display
@@ -75,25 +76,25 @@ export type Props = {
75
76
  /*----------------------------------
76
77
  - COMPOSANT
77
78
  ----------------------------------*/
78
- export default ({
79
-
80
- // Input
81
- title,
82
- value: file,
83
- className,
84
-
85
- // Display
86
- emptyText = 'Click here to select a File',
87
- previewUrl: previewUrlInit,
88
-
89
- // Actions
90
- onChange
91
- }: Props) => {
79
+ export default (props: Props) => {
92
80
 
93
81
  /*----------------------------------
94
82
  - INITIALIZE
95
83
  ----------------------------------*/
96
84
 
85
+ let {
86
+ // Input
87
+ value: file,
88
+ className,
89
+
90
+ // Display
91
+ emptyText = 'Click here to select a File',
92
+ previewUrl: previewUrlInit,
93
+
94
+ // Actions
95
+ onChange
96
+ } = props;
97
+
97
98
  const [previewUrl, setPreviewUrl] = React.useState<string | undefined>(previewUrlInit);
98
99
 
99
100
  className = 'input upload ' + (className === undefined ? '' : ' ' + className);
@@ -125,39 +126,40 @@ export default ({
125
126
  /*----------------------------------
126
127
  - RENDER
127
128
  ----------------------------------*/
128
- return <>
129
- <label>{title}</label>
130
-
131
- <div class={className}>
132
-
133
- {file && <>
134
- <div class="preview">
135
-
136
- {previewUrl ? (
137
- <img src={previewUrl} />
138
- ) : typeof file === 'string' ? <>
139
- <strong>A file has been selected</strong>
140
- </> : file ? <>
141
- <strong>{file.name}</strong>
142
- </> : null}
129
+ return (
130
+ <InputWrapper {...props}>
131
+
132
+ <div class={className}>
133
+
134
+ {file && <>
135
+ <div class="preview">
136
+
137
+ {previewUrl ? (
138
+ <img src={previewUrl} />
139
+ ) : typeof file === 'string' ? <>
140
+ <strong>A file has been selected</strong>
141
+ </> : file ? <>
142
+ <strong>{file.name}</strong>
143
+ </> : null}
144
+ </div>
145
+
146
+ <div class="row actions sp-05">
147
+
148
+ {typeof file === 'string' && <>
149
+ <Bouton type="secondary" icon="eye" shape="pill" size="s" link={file} />
150
+ </>}
151
+
152
+ <Bouton class='bg error' icon="trash" shape="pill" size="s"
153
+ async onClick={() => onChange(undefined)} />
154
+ </div>
155
+ </>}
156
+
157
+ <div class="indication col al-center">
158
+ {emptyText}
143
159
  </div>
144
160
 
145
- <div class="row actions sp-05">
146
-
147
- {typeof file === 'string' && <>
148
- <Bouton type="secondary" icon="eye" shape="pill" size="s" link={file} />
149
- </>}
150
-
151
- <Bouton class='bg error' icon="trash" shape="pill" size="s"
152
- async onClick={() => onChange(undefined)} />
153
- </div>
154
- </>}
155
-
156
- <div class="indication col al-center">
157
- {emptyText}
161
+ <input type="file" onChange={selectFile} />
158
162
  </div>
159
-
160
- <input type="file" onChange={selectFile} />
161
- </div>
162
- </>
163
+ </InputWrapper>
164
+ )
163
165
  }
File without changes
@@ -3,19 +3,19 @@
3
3
  ----------------------------------*/
4
4
 
5
5
  // Core
6
- import type { TApiResponseData } from '@server/services/router';
6
+ import type ClientApplication from '@client/app';
7
+ import { fromJson as errorFromJson, NetworkError } from '@common/errors';
7
8
  import ApiClientService, {
8
- TPostData,
9
+ TPostData, TPostDataWithoutFile,
9
10
  TApiFetchOptions, TFetcherList, TFetcherArgs, TFetcher,
10
11
  TDataReturnedByFetchers
11
12
  } from '@common/router/request/api';
12
- import { fromJson as errorFromJson, NetworkError } from '@common/errors';
13
- import type ClientApplication from '@client/app';
14
13
 
15
- import { toMultipart } from './multipart';
16
14
 
17
15
  // Specific
18
16
  import type { default as Router, Request } from '..';
17
+ import { toMultipart } from './multipart';
18
+ import FileToUpload from '@client/components/inputv3/file/FileToUpload';
19
19
 
20
20
  /*----------------------------------
21
21
  - TYPES
@@ -187,29 +187,45 @@ export default class ApiClient implements ApiClientService {
187
187
  return { ...alreadyLoadedData, ...fetchedData }
188
188
  }
189
189
 
190
- public configure = (...[method, path, data, options]: TFetcherArgs) => {
191
- const { onProgress, captcha } = options || {};
190
+ public configure = (...[method, path, data, options = {}]: TFetcherArgs) => {
192
191
 
193
- const url = this.router.url(path, {}, false);
192
+ let url = this.router.url(path, {}, false);
194
193
 
195
194
  debug && console.log(`[api] Sending request`, method, url, data);
196
195
 
197
196
  // Create Fetch config
198
- const config = {
197
+ const config: With<RequestInit, 'headers'> = {
199
198
  method: method,
200
199
  headers: {
201
200
  'Accept': "application/json",
202
201
  }
203
202
  };
204
203
 
205
- // Format request data
204
+ // Update options depending on data
206
205
  if (data) {
206
+
207
+ // If file included in data, need to use multipart
208
+ // TODO: deep check
209
+ const hasFile = Object.values(data).some((value) => value instanceof FileToUpload);
210
+ if (hasFile) {
211
+ // GET request = Can't send files
212
+ if (method === "GET")
213
+ throw new Error("Cannot send file in GET request");
214
+ // Auto switch to multiplart
215
+ else if (options.encoding === undefined)
216
+ options.encoding = 'multipart';
217
+ else if (options.encoding !== 'multipart')
218
+ // Encoding set to JSON = Can't send files
219
+ throw new Error("Cannot send file in non-multipart request");
220
+ }
221
+
222
+ // Data encoding
207
223
  if (method === "GET") {
208
224
 
209
- const params = new URLSearchParams(data).toString();
210
- config.url = `${url}?${params}`;
225
+ const params = new URLSearchParams( data as unknown as TPostDataWithoutFile ).toString();
226
+ url = `${url}?${params}`;
211
227
 
212
- } else if (options?.encoding === 'multipart') {
228
+ } else if (options.encoding === 'multipart') {
213
229
 
214
230
  console.log("[api] Multipart request", data);
215
231
  // Browser will automatically choose the right headers
@@ -22,14 +22,14 @@ export type TFetcher<TData extends any = unknown> = {
22
22
 
23
23
  method: HttpMethod,
24
24
  path: string,
25
- data?: TPostData,
25
+ data?: TPostDataWithFile,
26
26
  options?: TApiFetchOptions
27
27
  }
28
28
 
29
29
  export type TFetcherArgs = [
30
30
  method: HttpMethod,
31
31
  path: string,
32
- data?: TPostData,
32
+ data?: TPostDataWithFile,
33
33
  options?: TApiFetchOptions
34
34
  ]
35
35
 
@@ -40,9 +40,11 @@ export type TApiFetchOptions = {
40
40
  encoding?: 'json' | 'multipart'
41
41
  }
42
42
 
43
- export type TPostData = {[key: string]: PrimitiveValue}
43
+ export type TPostData = TPostDataWithFile
44
44
 
45
- export type TPostDataWithFile = {[key: string]: PrimitiveValue | FileToUpload}
45
+ export type TPostDataWithFile = { [key: string]: PrimitiveValue | FileToUpload }
46
+
47
+ export type TPostDataWithoutFile = { [key: string]: PrimitiveValue }
46
48
 
47
49
  // https://stackoverflow.com/questions/44851268/typescript-how-to-extract-the-generic-parameter-from-a-type
48
50
  type TypeWithGeneric<T> = TFetcher<T>
@@ -14,7 +14,7 @@ import defaultValidators, { SchemaValidators, getFieldValidator } from './valida
14
14
  ----------------------------------*/
15
15
 
16
16
  export type TSchemaFields = {
17
- [fieldName: string]: TSchemaFields | Schema<{}> | TValidatorDefinition
17
+ [fieldName: string]: TSchemaFields | Schema<{}> | Validator<any> | TValidatorDefinition
18
18
  }
19
19
 
20
20
  type TSchemaOptions = {
@@ -42,9 +42,15 @@ export type TSchemaData<TSchema extends Schema<{}>> =
42
42
 
43
43
  export type TValidatedData<TFields extends TSchemaFields> = {
44
44
  // For each field, the values returned by validator.validate()
45
- [name in keyof TFields]: ReturnType<TFields[name]["validate"]>
45
+ [name in keyof TFields]: TFieldReturnType<TFields[name]>
46
46
  }
47
47
 
48
+ type TFieldReturnType<TField> = TField extends TValidatorDefinition
49
+ ? TField[2]
50
+ : TField extends Schema<infer T>
51
+ ? TValidatedData<T>
52
+ : never
53
+
48
54
  /*----------------------------------
49
55
  - CONST
50
56
  ----------------------------------*/
@@ -18,9 +18,9 @@ import type { InputBaseProps } from '@client/components/inputv3/base';
18
18
  ----------------------------------*/
19
19
 
20
20
  export type TValidatorDefinition<K extends keyof SchemaValidators = keyof SchemaValidators> = [
21
- type: K,
22
- args: Parameters<SchemaValidators[K]>,
23
- returnType: ReturnType<SchemaValidators[K]>
21
+ type: string,
22
+ args: any[],
23
+ returnType: string
24
24
  ]
25
25
 
26
26
  // TODO: remove
@@ -92,6 +92,8 @@ export default abstract class FsDriver<
92
92
 
93
93
  public abstract delete( bucketName: TBucketName, filename: string ): Promise<boolean>;
94
94
 
95
+ public abstract deleteDir( bucketName: TBucketName, dirname: string ): Promise<boolean>;
96
+
95
97
  public abstract unmount(): Promise<void>;
96
98
 
97
99
  }
@@ -244,7 +244,6 @@ export default class S3Driver<
244
244
  Bucket: bucket,
245
245
  Key: filename,
246
246
  Body: content,
247
- ACL: 'public-read'
248
247
  }, (err, data) => {
249
248
 
250
249
  if (err) return reject(err);
@@ -289,4 +288,34 @@ export default class S3Driver<
289
288
  })
290
289
  }
291
290
 
291
+ public async deleteDir( bucketName: TBucketName, directoryPath: string ) {
292
+ const bucket = this.config.buckets[bucketName];
293
+ debug && console.log(`delete ${bucket}/${directoryPath}`);
294
+ try {
295
+ // Liste des objets dans le répertoire
296
+ const listedObjects = await this.s3.listObjectsV2({
297
+ Bucket: bucket,
298
+ Prefix: directoryPath
299
+ }).promise();
300
+
301
+ if (!listedObjects.Contents?.length) return;
302
+
303
+ // Supprimer les objets
304
+ await this.s3.deleteObjects({
305
+ Bucket: bucket,
306
+ Delete: {
307
+ Objects: listedObjects.Contents.map(({ Key }) => ({ Key }))
308
+ }
309
+ }).promise();
310
+
311
+ // Récursivement, traiter d'autres pages d'objets si elles existent
312
+ //if (listedObjects.IsTruncated) await deleteDirectory();
313
+
314
+ console.log(`Le répertoire ${directoryPath} a été supprimé.`);
315
+
316
+ } catch (error) {
317
+ console.error("Erreur lors de la suppression :", error);
318
+ }
319
+ }
320
+
292
321
  }
@@ -44,7 +44,7 @@ export default class ServerRequest<
44
44
 
45
45
  // Requete
46
46
  public method: HttpMethod;
47
- public ip: string;
47
+ public ip?: string;
48
48
  public locale: string;
49
49
  public domain: string;
50
50
  public headers: HttpHeaders = {};