5htp-core 0.4.8 → 0.4.9-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.
- package/package.json +5 -1
- package/src/client/assets/css/components/table.less +3 -0
- package/src/client/components/Form.ts +2 -6
- package/src/client/components/Table/index.tsx +24 -79
- package/src/client/components/button.tsx +2 -1
- package/src/client/components/containers/Popover/index.tsx +2 -2
- package/src/client/components/dropdown/index.tsx +16 -6
- package/src/client/components/input/Slider/index.tsx +0 -2
- package/src/client/components/inputv3/Rte/Editor.tsx +271 -0
- package/src/client/components/inputv3/Rte/ToolbarPlugin/BlockFormat.tsx +220 -0
- package/src/client/components/inputv3/Rte/ToolbarPlugin/ElementFormat.tsx +107 -0
- package/src/client/components/inputv3/Rte/ToolbarPlugin/index.tsx +768 -0
- package/src/client/components/inputv3/Rte/appSettings.ts +36 -0
- package/src/client/components/inputv3/Rte/context/FlashMessageContext.tsx +68 -0
- package/src/client/components/inputv3/Rte/context/SettingsContext.tsx +71 -0
- package/src/client/components/inputv3/Rte/context/SharedAutocompleteContext.tsx +71 -0
- package/src/client/components/inputv3/Rte/context/SharedHistoryContext.tsx +35 -0
- package/src/client/components/inputv3/Rte/currentEditor.ts +42 -0
- package/src/client/components/inputv3/Rte/hooks/useFlashMessage.tsx +16 -0
- package/src/client/components/inputv3/Rte/hooks/useReport.ts +67 -0
- package/src/client/components/inputv3/Rte/images/emoji/1F600.png +0 -0
- package/src/client/components/inputv3/Rte/images/emoji/1F641.png +0 -0
- package/src/client/components/inputv3/Rte/images/emoji/1F642.png +0 -0
- package/src/client/components/inputv3/Rte/images/emoji/2764.png +0 -0
- package/src/client/components/inputv3/Rte/images/emoji/LICENSE.md +5 -0
- package/src/client/components/inputv3/Rte/images/icons/draggable-block-menu.svg +1 -0
- package/src/client/components/inputv3/Rte/images/icons/prettier-error.svg +1 -0
- package/src/client/components/inputv3/Rte/images/icons/prettier.svg +1 -0
- package/src/client/components/inputv3/Rte/images/image/LICENSE.md +5 -0
- package/src/client/components/inputv3/Rte/images/image-broken.svg +4 -0
- package/src/client/components/inputv3/Rte/images/logo.svg +1 -0
- package/src/client/components/inputv3/Rte/index.tsx +63 -79
- package/src/client/components/inputv3/Rte/nodes/AutocompleteNode.tsx +119 -0
- package/src/client/components/inputv3/Rte/nodes/EmojiNode.tsx +102 -0
- package/src/client/components/inputv3/Rte/nodes/EquationComponent.tsx +141 -0
- package/src/client/components/inputv3/Rte/nodes/EquationNode.tsx +174 -0
- package/src/client/components/inputv3/Rte/nodes/FigmaNode.tsx +135 -0
- package/src/client/components/inputv3/Rte/nodes/ImageComponent.tsx +468 -0
- package/src/client/components/inputv3/Rte/nodes/ImageNode.css +43 -0
- package/src/client/components/inputv3/Rte/nodes/ImageNode.tsx +266 -0
- package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageComponent.tsx +402 -0
- package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.css +94 -0
- package/src/client/components/inputv3/Rte/nodes/InlineImageNode/InlineImageNode.tsx +294 -0
- package/src/client/components/inputv3/Rte/nodes/KeywordNode.ts +67 -0
- package/src/client/components/inputv3/Rte/nodes/LayoutContainerNode.ts +137 -0
- package/src/client/components/inputv3/Rte/nodes/LayoutItemNode.ts +71 -0
- package/src/client/components/inputv3/Rte/nodes/MentionNode.ts +130 -0
- package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.css +62 -0
- package/src/client/components/inputv3/Rte/nodes/PageBreakNode/index.tsx +170 -0
- package/src/client/components/inputv3/Rte/nodes/PlaygroundNodes.ts +76 -0
- package/src/client/components/inputv3/Rte/nodes/PollComponent.tsx +249 -0
- package/src/client/components/inputv3/Rte/nodes/PollNode.css +187 -0
- package/src/client/components/inputv3/Rte/nodes/PollNode.tsx +209 -0
- package/src/client/components/inputv3/Rte/nodes/StickyComponent.tsx +261 -0
- package/src/client/components/inputv3/Rte/nodes/StickyNode.css +37 -0
- package/src/client/components/inputv3/Rte/nodes/StickyNode.tsx +150 -0
- package/src/client/components/inputv3/Rte/nodes/TweetNode.tsx +223 -0
- package/src/client/components/inputv3/Rte/nodes/YouTubeNode.tsx +184 -0
- package/src/client/components/inputv3/Rte/plugins/ActionsPlugin/index.tsx +334 -0
- package/src/client/components/inputv3/Rte/plugins/AutoEmbedPlugin/index.tsx +352 -0
- package/src/client/components/inputv3/Rte/plugins/AutoLinkPlugin/index.tsx +32 -0
- package/src/client/components/inputv3/Rte/plugins/AutocompletePlugin/index.tsx +2529 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/CopyButton/index.tsx +70 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.css +14 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/components/PrettierButton/index.tsx +156 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.css +54 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/index.tsx +190 -0
- package/src/client/components/inputv3/Rte/plugins/CodeActionMenuPlugin/utils.ts +33 -0
- package/src/client/components/inputv3/Rte/plugins/CodeHighlightPlugin/index.ts +21 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/Collapsible.css +57 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContainerNode.ts +168 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleContentNode.ts +127 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleTitleNode.ts +152 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/CollapsibleUtils.ts +17 -0
- package/src/client/components/inputv3/Rte/plugins/CollapsiblePlugin/index.ts +284 -0
- package/src/client/components/inputv3/Rte/plugins/ComponentPickerPlugin/index.tsx +370 -0
- package/src/client/components/inputv3/Rte/plugins/ContextMenuPlugin/index.tsx +270 -0
- package/src/client/components/inputv3/Rte/plugins/DocsPlugin/index.tsx +20 -0
- package/src/client/components/inputv3/Rte/plugins/DragDropPastePlugin/index.ts +51 -0
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.css +36 -0
- package/src/client/components/inputv3/Rte/plugins/DraggableBlockPlugin/index.tsx +43 -0
- package/src/client/components/inputv3/Rte/plugins/EmojiPickerPlugin/index.tsx +198 -0
- package/src/client/components/inputv3/Rte/plugins/EmojisPlugin/index.ts +75 -0
- package/src/client/components/inputv3/Rte/plugins/EquationsPlugin/index.tsx +82 -0
- package/src/client/components/inputv3/Rte/plugins/FigmaPlugin/index.tsx +40 -0
- package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.css +41 -0
- package/src/client/components/inputv3/Rte/plugins/FloatingLinkEditorPlugin/index.tsx +393 -0
- package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.css +141 -0
- package/src/client/components/inputv3/Rte/plugins/FloatingTextFormatToolbarPlugin/index.tsx +388 -0
- package/src/client/components/inputv3/Rte/plugins/ImagesPlugin/index.tsx +350 -0
- package/src/client/components/inputv3/Rte/plugins/InlineImagePlugin/index.tsx +336 -0
- package/src/client/components/inputv3/Rte/plugins/KeywordsPlugin/index.ts +56 -0
- package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/InsertLayoutDialog.tsx +58 -0
- package/src/client/components/inputv3/Rte/plugins/LayoutPlugin/LayoutPlugin.tsx +219 -0
- package/src/client/components/inputv3/Rte/plugins/LinkPlugin/index.tsx +34 -0
- package/src/client/components/inputv3/Rte/plugins/ListMaxIndentLevelPlugin/index.ts +85 -0
- package/src/client/components/inputv3/Rte/plugins/MarkdownShortcutPlugin/index.tsx +16 -0
- package/src/client/components/inputv3/Rte/plugins/MarkdownTransformers/index.ts +324 -0
- package/src/client/components/inputv3/Rte/plugins/MaxLengthPlugin/index.tsx +53 -0
- package/src/client/components/inputv3/Rte/plugins/MentionsPlugin/index.tsx +696 -0
- package/src/client/components/inputv3/Rte/plugins/PageBreakPlugin/index.tsx +57 -0
- package/src/client/components/inputv3/Rte/plugins/PasteLogPlugin/index.tsx +54 -0
- package/src/client/components/inputv3/Rte/plugins/PollPlugin/index.tsx +86 -0
- package/src/client/components/inputv3/Rte/plugins/SpeechToTextPlugin/index.ts +125 -0
- package/src/client/components/inputv3/Rte/plugins/StickyPlugin/index.ts +22 -0
- package/src/client/components/inputv3/Rte/plugins/TabFocusPlugin/index.tsx +65 -0
- package/src/client/components/inputv3/Rte/plugins/TableActionMenuPlugin/index.tsx +773 -0
- package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.css +12 -0
- package/src/client/components/inputv3/Rte/plugins/TableCellResizer/index.tsx +436 -0
- package/src/client/components/inputv3/Rte/plugins/TableHoverActionsPlugin/index.tsx +287 -0
- package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.css +95 -0
- package/src/client/components/inputv3/Rte/plugins/TableOfContentsPlugin/index.tsx +197 -0
- package/src/client/components/inputv3/Rte/plugins/TablePlugin.tsx +178 -0
- package/src/client/components/inputv3/Rte/plugins/TestRecorderPlugin/index.tsx +468 -0
- package/src/client/components/inputv3/Rte/plugins/TreeViewPlugin/index.tsx +26 -0
- package/src/client/components/inputv3/Rte/plugins/TwitterPlugin/index.ts +41 -0
- package/src/client/components/inputv3/Rte/plugins/TypingPerfPlugin/index.ts +117 -0
- package/src/client/components/inputv3/Rte/plugins/YouTubePlugin/index.ts +41 -0
- package/src/client/components/inputv3/Rte/shared/canUseDOM.ts +4 -0
- package/src/client/components/inputv3/Rte/shared/caretFromPoint.ts +40 -0
- package/src/client/components/inputv3/Rte/shared/environment.ts +56 -0
- package/src/client/components/inputv3/Rte/shared/invariant.ts +26 -0
- package/src/client/components/inputv3/Rte/shared/normalizeClassNames.ts +21 -0
- package/src/client/components/inputv3/Rte/shared/react-test-utils.ts +18 -0
- package/src/client/components/inputv3/Rte/shared/reactPatches.ts +22 -0
- package/src/client/components/inputv3/Rte/shared/simpleDiffWithCursor.ts +49 -0
- package/src/client/components/inputv3/Rte/shared/useLayoutEffect.ts +19 -0
- package/src/client/components/inputv3/Rte/shared/warnOnlyOnce.ts +20 -0
- package/src/client/components/inputv3/Rte/style.less +30 -60
- package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.css +13 -0
- package/src/client/components/inputv3/Rte/themes/CommentEditorTheme.ts +20 -0
- package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.css +447 -0
- package/src/client/components/inputv3/Rte/themes/PlaygroundEditorTheme.ts +120 -0
- package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.css +13 -0
- package/src/client/components/inputv3/Rte/themes/StickyEditorTheme.ts +20 -0
- package/src/client/components/inputv3/Rte/ui/ColorPicker.css +88 -0
- package/src/client/components/inputv3/Rte/ui/ColorPicker.tsx +365 -0
- package/src/client/components/inputv3/Rte/ui/ContentEditable.css +44 -0
- package/src/client/components/inputv3/Rte/ui/ContentEditable.tsx +36 -0
- package/src/client/components/inputv3/Rte/ui/DropDown.tsx +259 -0
- package/src/client/components/inputv3/Rte/ui/DropdownColorPicker.tsx +41 -0
- package/src/client/components/inputv3/Rte/ui/EquationEditor.css +38 -0
- package/src/client/components/inputv3/Rte/ui/EquationEditor.tsx +56 -0
- package/src/client/components/inputv3/Rte/ui/FileInput.tsx +38 -0
- package/src/client/components/inputv3/Rte/ui/FlashMessage.css +28 -0
- package/src/client/components/inputv3/Rte/ui/FlashMessage.tsx +29 -0
- package/src/client/components/inputv3/Rte/ui/ImageResizer.tsx +316 -0
- package/src/client/components/inputv3/Rte/ui/Input.css +32 -0
- package/src/client/components/inputv3/Rte/ui/KatexRenderer.tsx +54 -0
- package/src/client/components/inputv3/Rte/ui/Switch.tsx +36 -0
- package/src/client/components/inputv3/Rte/utils/docSerialization.ts +77 -0
- package/src/client/components/inputv3/Rte/utils/emoji-list.ts +16615 -0
- package/src/client/components/inputv3/Rte/utils/getDOMRangeRect.ts +27 -0
- package/src/client/components/inputv3/Rte/utils/getSelectedNode.ts +27 -0
- package/src/client/components/inputv3/Rte/utils/guard.ts +10 -0
- package/src/client/components/inputv3/Rte/utils/isMobileWidth.ts +7 -0
- package/src/client/components/inputv3/Rte/utils/joinClasses.ts +13 -0
- package/src/client/components/inputv3/Rte/utils/setFloatingElemPosition.ts +51 -0
- package/src/client/components/inputv3/Rte/utils/setFloatingElemPositionForLinkEditor.ts +46 -0
- package/src/client/components/inputv3/Rte/utils/swipe.ts +127 -0
- package/src/client/components/inputv3/Rte/utils/url.ts +38 -0
- package/src/client/components/inputv3/base.tsx +10 -7
- package/src/client/components/inputv3/file/index.tsx +11 -5
- package/src/client/components/inputv3/index.tsx +2 -2
- package/src/common/data/rte/nodes.ts +60 -9
- package/src/common/validation/index.ts +21 -2
- package/src/common/validation/schema.ts +34 -11
- package/src/common/validation/validator.ts +12 -4
- package/src/common/validation/validators.ts +112 -63
- package/src/server/services/router/http/multipart.ts +0 -1
- package/src/server/services/router/request/index.ts +1 -1
- package/src/server/services/schema/index.ts +26 -4
- package/src/server/services/schema/request.ts +3 -2
- package/src/server/services/schema/rte.ts +110 -0
- package/src/{common/data/rte/index.ts → server/utils/rte.ts} +27 -16
- package/src/client/components/inputv3/Rte/ExampleTheme.tsx +0 -42
- package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +0 -167
- package/src/client/components/inputv3/Rte/icons/LICENSE.md +0 -5
- package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +0 -4
- package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +0 -4
- package/src/client/components/inputv3/Rte/icons/journal-text.svg +0 -5
- package/src/client/components/inputv3/Rte/icons/justify.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/text-center.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/text-left.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/text-right.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/type-bold.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/type-italic.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +0 -3
- package/src/client/components/inputv3/Rte/icons/type-underline.svg +0 -3
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
import { CoreError, TListeErreursSaisie, InputErrorSchema } from '@common/errors';
|
|
7
7
|
|
|
8
8
|
// Specific
|
|
9
|
-
import { default as Validator, EXCLUDE_VALUE } from './validator';
|
|
9
|
+
import { default as Validator, EXCLUDE_VALUE, TValidatorDefinition } from './validator';
|
|
10
|
+
import defaultValidators, { SchemaValidators, getFieldValidator } from './validators';
|
|
10
11
|
|
|
11
12
|
/*----------------------------------
|
|
12
13
|
- TYPES
|
|
13
14
|
----------------------------------*/
|
|
14
15
|
|
|
15
|
-
export type TSchemaFields = {
|
|
16
|
+
export type TSchemaFields = {
|
|
17
|
+
[fieldName: string]: TSchemaFields | Schema<{}> | Validator<any> | TValidatorDefinition
|
|
18
|
+
}
|
|
16
19
|
|
|
17
20
|
type TSchemaOptions = {
|
|
18
21
|
opt?: boolean
|
|
@@ -25,6 +28,7 @@ export type TValidateOptions<TFields extends TSchemaFields = {}> = {
|
|
|
25
28
|
only?: (keyof TFields)[],
|
|
26
29
|
validateDeps?: boolean,
|
|
27
30
|
autoCorrect?: boolean,
|
|
31
|
+
validators?: SchemaValidators
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
export type TValidationResult<TFields extends TSchemaFields> = {
|
|
@@ -38,9 +42,15 @@ export type TSchemaData<TSchema extends Schema<{}>> =
|
|
|
38
42
|
|
|
39
43
|
export type TValidatedData<TFields extends TSchemaFields> = {
|
|
40
44
|
// For each field, the values returned by validator.validate()
|
|
41
|
-
[name in keyof TFields]:
|
|
45
|
+
[name in keyof TFields]: TFieldReturnType<TFields[name]>
|
|
42
46
|
}
|
|
43
47
|
|
|
48
|
+
type TFieldReturnType<TField> = TField extends TValidatorDefinition
|
|
49
|
+
? TField[2]
|
|
50
|
+
: TField extends Schema<infer T>
|
|
51
|
+
? TValidatedData<T>
|
|
52
|
+
: never
|
|
53
|
+
|
|
44
54
|
/*----------------------------------
|
|
45
55
|
- CONST
|
|
46
56
|
----------------------------------*/
|
|
@@ -59,12 +69,29 @@ export default class Schema<TFields extends TSchemaFields> {
|
|
|
59
69
|
|
|
60
70
|
}
|
|
61
71
|
|
|
72
|
+
public getFieldValidator(
|
|
73
|
+
fieldName: string,
|
|
74
|
+
validators: SchemaValidators = defaultValidators
|
|
75
|
+
): null | Validator<any> | Schema<{}> {
|
|
76
|
+
|
|
77
|
+
let field = this.fields[fieldName];
|
|
78
|
+
if (field === undefined) {
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
|
|
82
|
+
// TValidatorDefinition
|
|
83
|
+
} else
|
|
84
|
+
return getFieldValidator(field);
|
|
85
|
+
}
|
|
86
|
+
|
|
62
87
|
public validate<TDonnees extends TObjetDonnees>(
|
|
63
88
|
dataToValidate: Partial<TDonnees>,
|
|
64
89
|
opts: TValidateOptions<TFields> = {},
|
|
65
90
|
chemin: string[] = []
|
|
66
91
|
): TValidatedData<TFields> {
|
|
67
92
|
|
|
93
|
+
const validators = opts.validators || defaultValidators;
|
|
94
|
+
|
|
68
95
|
// Check data type
|
|
69
96
|
if (typeof dataToValidate !== 'object')
|
|
70
97
|
throw new InputErrorSchema({ [chemin.join('.')]: ['Must be an object'] });
|
|
@@ -86,16 +113,11 @@ export default class Schema<TFields extends TSchemaFields> {
|
|
|
86
113
|
let errorsCount = 0;
|
|
87
114
|
for (const fieldName of keysToValidate) {
|
|
88
115
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
let validator: Validator<any> | Schema<{}>;
|
|
92
|
-
if (field === undefined) {
|
|
116
|
+
const validator = this.getFieldValidator(fieldName, validators);
|
|
117
|
+
if (validator === null) {
|
|
93
118
|
opts.debug && console.warn(LogPrefix, '[' + fieldName + ']', 'Exclusion (pas présent dans le schéma)');
|
|
94
119
|
continue;
|
|
95
|
-
}
|
|
96
|
-
validator = new Schema(field as TSchemaFields);
|
|
97
|
-
else
|
|
98
|
-
validator = field as Validator<any>;
|
|
120
|
+
}
|
|
99
121
|
|
|
100
122
|
// Create field path
|
|
101
123
|
const cheminA = [...chemin, fieldName]
|
|
@@ -140,6 +162,7 @@ export default class Schema<TFields extends TSchemaFields> {
|
|
|
140
162
|
|
|
141
163
|
} else {
|
|
142
164
|
|
|
165
|
+
console.error(LogPrefix, '[' + cheminA + ']', error);
|
|
143
166
|
erreurs[cheminAstr] = ["Technical error while validating data"];
|
|
144
167
|
errorsCount++;
|
|
145
168
|
}
|
|
@@ -10,13 +10,21 @@ import { InputError } from '@common/errors';
|
|
|
10
10
|
|
|
11
11
|
// Specific
|
|
12
12
|
import type { TValidateOptions } from './schema';
|
|
13
|
+
import type { SchemaValidators } from './validators';
|
|
13
14
|
import type { InputBaseProps } from '@client/components/inputv3/base';
|
|
14
15
|
|
|
15
16
|
/*----------------------------------
|
|
16
17
|
- TYPES
|
|
17
18
|
----------------------------------*/
|
|
18
19
|
|
|
19
|
-
export type
|
|
20
|
+
export type TValidatorDefinition<K extends keyof SchemaValidators = keyof SchemaValidators> = [
|
|
21
|
+
type: string,
|
|
22
|
+
args: any[],
|
|
23
|
+
returnType: string
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
// TODO: remove
|
|
27
|
+
export type TValidatorOptions<TValue> = {
|
|
20
28
|
|
|
21
29
|
rendu?: TFieldRenderer,
|
|
22
30
|
|
|
@@ -48,7 +56,7 @@ type TValidationFunction<TValue, TAllValues extends {} = {}> = (
|
|
|
48
56
|
) => TValue | typeof EXCLUDE_VALUE;
|
|
49
57
|
|
|
50
58
|
type TValidateReturnType<
|
|
51
|
-
TOptions extends
|
|
59
|
+
TOptions extends TValidatorOptions<TValue>,
|
|
52
60
|
TValue extends any
|
|
53
61
|
> = TOptions extends { opt: true }
|
|
54
62
|
? (undefined | TValue)
|
|
@@ -65,8 +73,8 @@ export const EXCLUDE_VALUE = "action:exclure" as const;
|
|
|
65
73
|
----------------------------------*/
|
|
66
74
|
export default class Validator<
|
|
67
75
|
TValue,
|
|
68
|
-
TOptions extends
|
|
69
|
-
TComponent = React.FunctionComponent< InputBaseProps< TValue > >
|
|
76
|
+
TOptions extends TValidatorOptions<TValue> = TValidatorOptions<TValue>,
|
|
77
|
+
//TComponent = React.FunctionComponent< InputBaseProps< TValue > >
|
|
70
78
|
> {
|
|
71
79
|
|
|
72
80
|
public constructor(
|
|
@@ -17,29 +17,93 @@ import FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
|
17
17
|
|
|
18
18
|
// Speciific
|
|
19
19
|
import Schema, { TSchemaFields } from './schema'
|
|
20
|
-
import Validator, {
|
|
20
|
+
import Validator, { TValidatorOptions, EXCLUDE_VALUE, TValidatorDefinition } from './validator'
|
|
21
21
|
|
|
22
22
|
/*----------------------------------
|
|
23
23
|
- TYPES
|
|
24
24
|
----------------------------------*/
|
|
25
25
|
|
|
26
|
-
export type TFileValidator =
|
|
26
|
+
export type TFileValidator = TValidatorOptions<FileToUpload> & {
|
|
27
27
|
type?: string[], // Raccourci, ou liste de mimetype
|
|
28
|
-
taille?: number
|
|
28
|
+
taille?: number,
|
|
29
|
+
disk?: string, // Disk to upload files to
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
type TSchemaSubtype = Schema<{}> | TSchemaFields;
|
|
32
|
+
type TSchemaSubtype = Schema<{}> | TSchemaFields | TValidatorDefinition;
|
|
32
33
|
|
|
33
|
-
type TSubtype = TSchemaSubtype | Validator<any
|
|
34
|
+
type TSubtype = TSchemaSubtype | Validator<any> | TValidatorDefinition;
|
|
34
35
|
|
|
35
36
|
/*----------------------------------
|
|
36
37
|
- CONST
|
|
37
38
|
----------------------------------*/
|
|
38
39
|
|
|
40
|
+
export type TRichTextValidatorOptions = {
|
|
41
|
+
attachements?: TFileValidator
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const getFieldValidator = (field: TValidatorDefinition) => {
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(field)) {
|
|
47
|
+
|
|
48
|
+
const [validatorName, validatorArgs] = field;
|
|
49
|
+
const getValidator = validators[validatorName];
|
|
50
|
+
if (getValidator === undefined)
|
|
51
|
+
throw new Error('Unknown validator: ' + validatorName);
|
|
52
|
+
|
|
53
|
+
return getValidator(...validatorArgs);
|
|
54
|
+
|
|
55
|
+
// TSchemaFields
|
|
56
|
+
} else if (field.constructor === Object) {
|
|
57
|
+
|
|
58
|
+
return new Schema(field as TSchemaFields);
|
|
59
|
+
|
|
60
|
+
// Schema
|
|
61
|
+
} else
|
|
62
|
+
return field as Validator<any>
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Recursive function to validate each node
|
|
66
|
+
function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
67
|
+
|
|
68
|
+
// Each node should be an object with a `type` property
|
|
69
|
+
if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
|
|
70
|
+
throw new InputError("Invalid rich text value (3).");
|
|
71
|
+
|
|
72
|
+
// Validate text nodes
|
|
73
|
+
if (node.type === 'text') {
|
|
74
|
+
|
|
75
|
+
if (typeof node.text !== 'string')
|
|
76
|
+
throw new InputError("Invalid rich text value (4).");
|
|
77
|
+
|
|
78
|
+
// Validate paragraph, heading, or other structural nodes that may contain children
|
|
79
|
+
} else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
80
|
+
|
|
81
|
+
if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
|
|
82
|
+
throw new InputError("Invalid rich text value (5).");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Files upload
|
|
86
|
+
} else if (node.type === 'image') {
|
|
87
|
+
|
|
88
|
+
// Check if allowed
|
|
89
|
+
/*if (opts.attachements === undefined)
|
|
90
|
+
throw new InputError("Image attachments not allowed in this rich text field.");*/
|
|
91
|
+
|
|
92
|
+
// TODO: check mime
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
// Upload file
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
39
103
|
/*----------------------------------
|
|
40
104
|
- CLASS
|
|
41
105
|
----------------------------------*/
|
|
42
|
-
export
|
|
106
|
+
export class SchemaValidators {
|
|
43
107
|
|
|
44
108
|
/*----------------------------------
|
|
45
109
|
- UTILITIES
|
|
@@ -72,7 +136,7 @@ export default class SchemaValidators {
|
|
|
72
136
|
/*----------------------------------
|
|
73
137
|
- CONTENEURS
|
|
74
138
|
----------------------------------*/
|
|
75
|
-
public object = ( subtype?: TSchemaSubtype, { ...opts }:
|
|
139
|
+
public object = ( subtype?: TSchemaSubtype, { ...opts }: TValidatorOptions<object> & {
|
|
76
140
|
|
|
77
141
|
} = {}) =>
|
|
78
142
|
new Validator<object>('object', (val, options, path) => {
|
|
@@ -86,9 +150,7 @@ export default class SchemaValidators {
|
|
|
86
150
|
return val;
|
|
87
151
|
|
|
88
152
|
// If subtype is a schema
|
|
89
|
-
const schema = subtype
|
|
90
|
-
? new Schema(subtype as TSchemaFields)
|
|
91
|
-
: subtype as Schema<{}>;
|
|
153
|
+
const schema = getFieldValidator(subtype) as Schema<{}>;
|
|
92
154
|
|
|
93
155
|
// Validate schema
|
|
94
156
|
const value = schema.validate(val, options, path);
|
|
@@ -96,7 +158,7 @@ export default class SchemaValidators {
|
|
|
96
158
|
return value;
|
|
97
159
|
}, opts)
|
|
98
160
|
|
|
99
|
-
public array = ( subtype: TSubtype, { choice, min, max, ...opts }:
|
|
161
|
+
public array = ( subtype: TSubtype, { choice, min, max, ...opts }: TValidatorOptions<any[]> & {
|
|
100
162
|
choice?: any[],
|
|
101
163
|
min?: number,
|
|
102
164
|
max?: number
|
|
@@ -116,9 +178,7 @@ export default class SchemaValidators {
|
|
|
116
178
|
if (subtype === undefined)
|
|
117
179
|
return items;
|
|
118
180
|
|
|
119
|
-
const validator = subtype
|
|
120
|
-
? new Schema(subtype as TSchemaFields)
|
|
121
|
-
: subtype as Schema<{}> | Validator<any>;
|
|
181
|
+
const validator = getFieldValidator(subtype);
|
|
122
182
|
|
|
123
183
|
items = items.map( item =>
|
|
124
184
|
validator.validate( item, options, path )
|
|
@@ -130,7 +190,7 @@ export default class SchemaValidators {
|
|
|
130
190
|
//multiple: true, // Sélection multiple
|
|
131
191
|
})
|
|
132
192
|
|
|
133
|
-
public choice = (choices?: any[], { multiple, ...opts }:
|
|
193
|
+
public choice = (choices?: any[], { multiple, ...opts }: TValidatorOptions<any> & {
|
|
134
194
|
multiple?: boolean
|
|
135
195
|
} = {}) => new Validator<any>('choice', (val, options, path) => {
|
|
136
196
|
|
|
@@ -169,7 +229,7 @@ export default class SchemaValidators {
|
|
|
169
229
|
/*----------------------------------
|
|
170
230
|
- CHAINES
|
|
171
231
|
----------------------------------*/
|
|
172
|
-
public string = ({ min, max, in: choices, ...opts }:
|
|
232
|
+
public string = ({ min, max, in: choices, ...opts }: TValidatorOptions<string> & {
|
|
173
233
|
min?: number,
|
|
174
234
|
max?: number,
|
|
175
235
|
in?: string[]
|
|
@@ -205,7 +265,7 @@ export default class SchemaValidators {
|
|
|
205
265
|
|
|
206
266
|
}, opts)
|
|
207
267
|
|
|
208
|
-
public url = (opts:
|
|
268
|
+
public url = (opts: TValidatorOptions<string> & {
|
|
209
269
|
normalize?: NormalizeUrlOptions
|
|
210
270
|
} = {}) =>
|
|
211
271
|
new Validator<string>('url', (inputVal, options, path) => {
|
|
@@ -225,7 +285,7 @@ export default class SchemaValidators {
|
|
|
225
285
|
return val;
|
|
226
286
|
}, opts)
|
|
227
287
|
|
|
228
|
-
public email = (opts:
|
|
288
|
+
public email = (opts: TValidatorOptions<string> & {} = {}) =>
|
|
229
289
|
new Validator<string>('email', (inputVal, options, path) => {
|
|
230
290
|
|
|
231
291
|
let val = this.string(opts).validate(inputVal, options, path);
|
|
@@ -263,7 +323,7 @@ export default class SchemaValidators {
|
|
|
263
323
|
- NOMBRES
|
|
264
324
|
----------------------------------*/
|
|
265
325
|
// On ne spread pas min et max afin quils soient passés dans les props du composant
|
|
266
|
-
public number = (withDecimals: boolean) => ({ ...opts }:
|
|
326
|
+
public number = (withDecimals: boolean) => ({ ...opts }: TValidatorOptions<number> & {
|
|
267
327
|
min?: number,
|
|
268
328
|
max?: number,
|
|
269
329
|
step?: number,
|
|
@@ -309,7 +369,7 @@ export default class SchemaValidators {
|
|
|
309
369
|
|
|
310
370
|
public float = this.number(true)
|
|
311
371
|
|
|
312
|
-
public bool = (opts:
|
|
372
|
+
public bool = (opts: TValidatorOptions<boolean> & {} = {}) =>
|
|
313
373
|
new Validator<boolean>('bool', (val, options, path) => {
|
|
314
374
|
|
|
315
375
|
if (typeof val !== 'boolean' && !['true', 'false'].includes(val))
|
|
@@ -326,7 +386,7 @@ export default class SchemaValidators {
|
|
|
326
386
|
/*----------------------------------
|
|
327
387
|
- AUTRES
|
|
328
388
|
----------------------------------*/
|
|
329
|
-
public date = (opts:
|
|
389
|
+
public date = (opts: TValidatorOptions<Date> & {
|
|
330
390
|
|
|
331
391
|
} = {}) => new Validator<Date>('date', (val, options, path) => {
|
|
332
392
|
|
|
@@ -350,50 +410,36 @@ export default class SchemaValidators {
|
|
|
350
410
|
...opts,
|
|
351
411
|
})
|
|
352
412
|
|
|
353
|
-
public richText
|
|
354
|
-
|
|
355
|
-
} = {}) => new Validator<string>('richText', (val, options, path) => {
|
|
356
|
-
|
|
357
|
-
// Check that the root exists and has a valid type
|
|
358
|
-
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root')
|
|
359
|
-
throw new InputError("Invalid rich text value (1).");
|
|
360
|
-
|
|
361
|
-
// Check if root has children array
|
|
362
|
-
if (!Array.isArray(val.root.children))
|
|
363
|
-
throw new InputError("Invalid rich text value (2).");
|
|
364
|
-
|
|
365
|
-
// Recursive function to validate each node
|
|
366
|
-
function validateNode(node) {
|
|
367
|
-
// Each node should be an object with a `type` property
|
|
368
|
-
if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
|
|
369
|
-
throw new InputError("Invalid rich text value (3).");
|
|
370
|
-
|
|
371
|
-
// Validate text nodes
|
|
372
|
-
if (node.type === 'text') {
|
|
373
|
-
if (typeof node.text !== 'string')
|
|
374
|
-
throw new InputError("Invalid rich text value (4).");
|
|
375
|
-
}
|
|
413
|
+
public richText(opts: TValidatorOptions<string> & TRichTextValidatorOptions = {}) {
|
|
414
|
+
return new Validator<string>('richText', (val, options, path) => {
|
|
376
415
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
416
|
+
// We get a stringified json as input since the editor workds with JSON string
|
|
417
|
+
try {
|
|
418
|
+
val = JSON.parse(val);
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new InputError("Invalid rich text format.");
|
|
381
421
|
}
|
|
382
422
|
|
|
383
|
-
|
|
384
|
-
|
|
423
|
+
// Check that the root exists and has a valid type
|
|
424
|
+
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root')
|
|
425
|
+
throw new InputError("Invalid rich text value (1).");
|
|
385
426
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
427
|
+
// Check if root has children array
|
|
428
|
+
if (!Array.isArray(val.root.children))
|
|
429
|
+
throw new InputError("Invalid rich text value (2).");
|
|
390
430
|
|
|
391
|
-
|
|
431
|
+
// Validate each child node in root
|
|
432
|
+
for (const child of val.root.children) {
|
|
433
|
+
validateLexicalNode(child, opts);
|
|
434
|
+
}
|
|
392
435
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
436
|
+
return val;
|
|
437
|
+
|
|
438
|
+
}, {
|
|
439
|
+
//defaut: new Date,
|
|
440
|
+
...opts,
|
|
441
|
+
})
|
|
442
|
+
}
|
|
397
443
|
|
|
398
444
|
/*----------------------------------
|
|
399
445
|
- FICHIER
|
|
@@ -402,14 +448,14 @@ export default class SchemaValidators {
|
|
|
402
448
|
|
|
403
449
|
} = {}) => new Validator<FileToUpload>('file', (val, options, path) => {
|
|
404
450
|
|
|
405
|
-
if (!(val instanceof FileToUpload))
|
|
406
|
-
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
407
|
-
|
|
408
451
|
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
409
452
|
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
410
453
|
if (typeof val === 'string')
|
|
411
454
|
return EXCLUDE_VALUE;
|
|
412
455
|
|
|
456
|
+
if (!(val instanceof FileToUpload))
|
|
457
|
+
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
458
|
+
|
|
413
459
|
// MIME
|
|
414
460
|
if (type !== undefined) {
|
|
415
461
|
|
|
@@ -432,5 +478,8 @@ export default class SchemaValidators {
|
|
|
432
478
|
//defaut: new Date,
|
|
433
479
|
...opts,
|
|
434
480
|
})
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const validators = new SchemaValidators();
|
|
435
484
|
|
|
436
|
-
|
|
485
|
+
export default validators;
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
|
+
import type { Application } from '@server/app';
|
|
6
7
|
|
|
7
8
|
// Specific
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
import Validator, { EXCLUDE_VALUE,} from '@common/validation/validator';
|
|
9
|
+
import { SchemaValidators, TFileValidator } from '@common/validation/validators';
|
|
10
|
+
import Validator, { TValidatorOptions } from '@common/validation/validator';
|
|
11
11
|
|
|
12
12
|
import type FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
13
13
|
|
|
@@ -19,6 +19,28 @@ import type FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
|
19
19
|
/*----------------------------------
|
|
20
20
|
- SERVICE
|
|
21
21
|
----------------------------------*/
|
|
22
|
-
export default class ServerSchemaValidator extends
|
|
22
|
+
export default class ServerSchemaValidator extends SchemaValidators {
|
|
23
|
+
|
|
24
|
+
public constructor( public app: Application ) {
|
|
25
|
+
super();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public richText = (opts: TValidatorOptions<string> & {
|
|
29
|
+
attachements?: TFileValidator
|
|
30
|
+
} = {}) => new Validator<string>('richText', (val, options, path) => {
|
|
31
|
+
|
|
32
|
+
// Default validation
|
|
33
|
+
val = super.richText(opts).validate(val, options, path);
|
|
34
|
+
|
|
35
|
+
// Uploads are done in the business code since the process is specific to every case:
|
|
36
|
+
// - ID in the destination directory
|
|
37
|
+
// - Cleanup before upload
|
|
38
|
+
|
|
39
|
+
return val;
|
|
40
|
+
|
|
41
|
+
}, {
|
|
42
|
+
//defaut: new Date,
|
|
43
|
+
...opts,
|
|
44
|
+
})
|
|
23
45
|
|
|
24
46
|
}
|
|
@@ -34,7 +34,7 @@ export default class RequestValidator extends ServerSchemaValidator implements R
|
|
|
34
34
|
public app = router.app
|
|
35
35
|
) {
|
|
36
36
|
|
|
37
|
-
super();
|
|
37
|
+
super(app);
|
|
38
38
|
|
|
39
39
|
}
|
|
40
40
|
|
|
@@ -49,7 +49,8 @@ export default class RequestValidator extends ServerSchemaValidator implements R
|
|
|
49
49
|
// Les InputError seront propagées vers le middleware dédié à la gestion des erreurs
|
|
50
50
|
const values = schema.validate( this.request.data, {
|
|
51
51
|
debug: this.config.debug,
|
|
52
|
-
validateDeps: false
|
|
52
|
+
validateDeps: false,
|
|
53
|
+
validators: this
|
|
53
54
|
}, []);
|
|
54
55
|
|
|
55
56
|
return values;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
|
+
|
|
5
|
+
// Node
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import md5 from 'md5';
|
|
8
|
+
import { fromBuffer } from 'file-type';
|
|
9
|
+
|
|
10
|
+
// Npm
|
|
11
|
+
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical';
|
|
12
|
+
|
|
13
|
+
// Core
|
|
14
|
+
import type Driver from '@server/services/disks/driver';
|
|
15
|
+
|
|
16
|
+
type SerializedLexicalNodeWithSrc = SerializedLexicalNode & { src: string };
|
|
17
|
+
|
|
18
|
+
/*----------------------------------
|
|
19
|
+
- METHODS
|
|
20
|
+
----------------------------------*/
|
|
21
|
+
const uploadAttachments = async (
|
|
22
|
+
value: SerializedEditorState,
|
|
23
|
+
disk: Driver,
|
|
24
|
+
destination: string,
|
|
25
|
+
oldState?: SerializedEditorState
|
|
26
|
+
) => {
|
|
27
|
+
|
|
28
|
+
const usedFilesUrl: string[] = [];
|
|
29
|
+
|
|
30
|
+
// Deep check for images / files
|
|
31
|
+
const findFiles = async (
|
|
32
|
+
node: SerializedLexicalNode,
|
|
33
|
+
callback: (node: SerializedLexicalNodeWithSrc) => Promise<void>
|
|
34
|
+
) => {
|
|
35
|
+
|
|
36
|
+
// Attachment
|
|
37
|
+
if (node.type === 'image' || node.type === 'file') {
|
|
38
|
+
if (typeof node.src === 'string') {
|
|
39
|
+
await callback(node as SerializedLexicalNodeWithSrc);
|
|
40
|
+
}
|
|
41
|
+
// Recursion
|
|
42
|
+
} else if (node.children) {
|
|
43
|
+
for (const child of node.children) {
|
|
44
|
+
await findFiles(child, callback);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const uploadFile = async (node: SerializedLexicalNodeWithSrc) => {
|
|
50
|
+
|
|
51
|
+
// Already uploaded
|
|
52
|
+
if (!node.src.startsWith('data:')) {
|
|
53
|
+
usedFilesUrl.push(node.src);
|
|
54
|
+
return node.src;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Transform into buffer
|
|
58
|
+
node.src = node.src.replace(/^data:\w+\/\w+;base64,/, '');
|
|
59
|
+
const fileData = Buffer.from(node.src, 'base64');
|
|
60
|
+
|
|
61
|
+
// Parse file type from buffer
|
|
62
|
+
const fileType = await fromBuffer(fileData);
|
|
63
|
+
if (!fileType)
|
|
64
|
+
throw new Error('Failed to detect file type');
|
|
65
|
+
|
|
66
|
+
// Upload file to disk
|
|
67
|
+
const fileName = md5(node.src) + '.' + fileType.ext;
|
|
68
|
+
const filePath = path.join(destination, fileName);
|
|
69
|
+
const upoadedFile = await disk.outputFile('data', filePath, fileData, {
|
|
70
|
+
encoding: 'binary'
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Replace node.src with url
|
|
74
|
+
node.src = upoadedFile.path;
|
|
75
|
+
usedFilesUrl.push(node.src);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const deleteUnusedFile = async (node: SerializedLexicalNodeWithSrc) => {
|
|
79
|
+
|
|
80
|
+
// Should be a URL
|
|
81
|
+
if (node.src.startsWith('data:') || !node.src.startsWith('http'))
|
|
82
|
+
return;
|
|
83
|
+
|
|
84
|
+
// This file is used
|
|
85
|
+
if (usedFilesUrl.includes(node.src))
|
|
86
|
+
return;
|
|
87
|
+
|
|
88
|
+
// Extract file name
|
|
89
|
+
const fileName = path.basename(node.src);
|
|
90
|
+
const filePath = path.join(destination, fileName);
|
|
91
|
+
|
|
92
|
+
// Delete file from disk
|
|
93
|
+
await disk.delete('data', filePath);
|
|
94
|
+
console.log('Deleted file:', filePath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Find files in the editor state
|
|
98
|
+
for (const child of value.root.children) {
|
|
99
|
+
await findFiles(child, uploadFile);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Old state given: remove unused attachments
|
|
103
|
+
if (oldState !== undefined) {
|
|
104
|
+
for (const child of oldState.root.children) {
|
|
105
|
+
await findFiles(child, deleteUnusedFile);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default { uploadAttachments }
|