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.
- package/package.json +4 -1
- package/src/client/assets/css/components/lists.less +3 -3
- package/src/client/assets/css/components/table.less +1 -0
- package/src/client/components/Form.ts +1 -1
- package/src/client/components/Select/ChoiceElement.tsx +20 -27
- package/src/client/components/Select/index.tsx +24 -42
- package/src/client/components/Table/index.tsx +24 -79
- package/src/client/components/inputv3/Rte/Editor.tsx +16 -8
- package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +6 -1
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +23 -23
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +26 -24
- package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +10 -38
- package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +349 -356
- package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +80 -84
- package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +308 -347
- package/src/client/components/inputv3/Rte/style.less +0 -2
- package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +1 -1
- package/src/client/components/inputv3/base.less +0 -6
- package/src/client/components/inputv3/base.tsx +2 -2
- package/src/client/components/inputv3/file/index.tsx +50 -48
- package/src/client/components/inputv3/index.tsx +0 -0
- package/src/client/services/router/request/api.ts +29 -13
- package/src/common/router/request/api.ts +6 -4
- package/src/common/validation/schema.ts +8 -2
- package/src/common/validation/validator.ts +3 -3
- package/src/server/services/disks/driver.ts +2 -0
- package/src/server/services/disks/drivers/s3/index.ts +30 -1
- package/src/server/services/router/request/index.ts +1 -1
- package/src/server/utils/rte.ts +217 -59
- package/src/server/utils/slug.ts +67 -0
- package/src/server/services/schema/rte.ts +0 -110
|
@@ -69,7 +69,7 @@ export function useInput<TValue>(
|
|
|
69
69
|
if (state.changed === false)
|
|
70
70
|
return;
|
|
71
71
|
|
|
72
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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:
|
|
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
|
-
<
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
225
|
+
const params = new URLSearchParams( data as unknown as TPostDataWithoutFile ).toString();
|
|
226
|
+
url = `${url}?${params}`;
|
|
211
227
|
|
|
212
|
-
} else if (options
|
|
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?:
|
|
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?:
|
|
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 =
|
|
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]:
|
|
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:
|
|
22
|
-
args:
|
|
23
|
-
returnType:
|
|
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
|
}
|