5htp-core 0.4.8 → 0.4.9
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 +2 -0
- package/src/client/components/Form.ts +1 -1
- 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 +8 -5
- package/src/client/components/inputv3/file/index.tsx +11 -5
- package/src/common/data/rte/nodes.ts +60 -9
- package/src/common/validation/index.ts +21 -2
- package/src/common/validation/schema.ts +42 -10
- package/src/common/validation/validator.ts +12 -4
- package/src/common/validation/validators.ts +82 -53
- package/src/server/services/router/http/multipart.ts +0 -1
- package/src/server/services/schema/index.ts +24 -2
- 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
|
@@ -17,15 +17,16 @@ 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 } 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
32
|
type TSchemaSubtype = Schema<{}> | TSchemaFields;
|
|
@@ -36,6 +37,48 @@ type TSubtype = TSchemaSubtype | Validator<any>;
|
|
|
36
37
|
- CONST
|
|
37
38
|
----------------------------------*/
|
|
38
39
|
|
|
40
|
+
export type TRichTextValidatorOptions = {
|
|
41
|
+
attachements?: TFileValidator
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Recursive function to validate each node
|
|
45
|
+
function validateLexicalNode(node: any, opts: TRichTextValidatorOptions ) {
|
|
46
|
+
|
|
47
|
+
// Each node should be an object with a `type` property
|
|
48
|
+
if (typeof node !== 'object' || !node.type || typeof node.type !== 'string')
|
|
49
|
+
throw new InputError("Invalid rich text value (3).");
|
|
50
|
+
|
|
51
|
+
// Validate text nodes
|
|
52
|
+
if (node.type === 'text') {
|
|
53
|
+
|
|
54
|
+
if (typeof node.text !== 'string')
|
|
55
|
+
throw new InputError("Invalid rich text value (4).");
|
|
56
|
+
|
|
57
|
+
// Validate paragraph, heading, or other structural nodes that may contain children
|
|
58
|
+
} else if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type)) {
|
|
59
|
+
|
|
60
|
+
if (!Array.isArray(node.children) || !node.children.every(children => validateLexicalNode(children, opts))) {
|
|
61
|
+
throw new InputError("Invalid rich text value (5).");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Files upload
|
|
65
|
+
} else if (node.type === 'image') {
|
|
66
|
+
|
|
67
|
+
// Check if allowed
|
|
68
|
+
/*if (opts.attachements === undefined)
|
|
69
|
+
throw new InputError("Image attachments not allowed in this rich text field.");*/
|
|
70
|
+
|
|
71
|
+
// TODO: check mime
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// Upload file
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
39
82
|
/*----------------------------------
|
|
40
83
|
- CLASS
|
|
41
84
|
----------------------------------*/
|
|
@@ -72,7 +115,7 @@ export default class SchemaValidators {
|
|
|
72
115
|
/*----------------------------------
|
|
73
116
|
- CONTENEURS
|
|
74
117
|
----------------------------------*/
|
|
75
|
-
public object = ( subtype?: TSchemaSubtype, { ...opts }:
|
|
118
|
+
public object = ( subtype?: TSchemaSubtype, { ...opts }: TValidatorOptions<object> & {
|
|
76
119
|
|
|
77
120
|
} = {}) =>
|
|
78
121
|
new Validator<object>('object', (val, options, path) => {
|
|
@@ -96,7 +139,7 @@ export default class SchemaValidators {
|
|
|
96
139
|
return value;
|
|
97
140
|
}, opts)
|
|
98
141
|
|
|
99
|
-
public array = ( subtype: TSubtype, { choice, min, max, ...opts }:
|
|
142
|
+
public array = ( subtype: TSubtype, { choice, min, max, ...opts }: TValidatorOptions<any[]> & {
|
|
100
143
|
choice?: any[],
|
|
101
144
|
min?: number,
|
|
102
145
|
max?: number
|
|
@@ -130,7 +173,7 @@ export default class SchemaValidators {
|
|
|
130
173
|
//multiple: true, // Sélection multiple
|
|
131
174
|
})
|
|
132
175
|
|
|
133
|
-
public choice = (choices?: any[], { multiple, ...opts }:
|
|
176
|
+
public choice = (choices?: any[], { multiple, ...opts }: TValidatorOptions<any> & {
|
|
134
177
|
multiple?: boolean
|
|
135
178
|
} = {}) => new Validator<any>('choice', (val, options, path) => {
|
|
136
179
|
|
|
@@ -169,7 +212,7 @@ export default class SchemaValidators {
|
|
|
169
212
|
/*----------------------------------
|
|
170
213
|
- CHAINES
|
|
171
214
|
----------------------------------*/
|
|
172
|
-
public string = ({ min, max, in: choices, ...opts }:
|
|
215
|
+
public string = ({ min, max, in: choices, ...opts }: TValidatorOptions<string> & {
|
|
173
216
|
min?: number,
|
|
174
217
|
max?: number,
|
|
175
218
|
in?: string[]
|
|
@@ -205,7 +248,7 @@ export default class SchemaValidators {
|
|
|
205
248
|
|
|
206
249
|
}, opts)
|
|
207
250
|
|
|
208
|
-
public url = (opts:
|
|
251
|
+
public url = (opts: TValidatorOptions<string> & {
|
|
209
252
|
normalize?: NormalizeUrlOptions
|
|
210
253
|
} = {}) =>
|
|
211
254
|
new Validator<string>('url', (inputVal, options, path) => {
|
|
@@ -225,7 +268,7 @@ export default class SchemaValidators {
|
|
|
225
268
|
return val;
|
|
226
269
|
}, opts)
|
|
227
270
|
|
|
228
|
-
public email = (opts:
|
|
271
|
+
public email = (opts: TValidatorOptions<string> & {} = {}) =>
|
|
229
272
|
new Validator<string>('email', (inputVal, options, path) => {
|
|
230
273
|
|
|
231
274
|
let val = this.string(opts).validate(inputVal, options, path);
|
|
@@ -263,7 +306,7 @@ export default class SchemaValidators {
|
|
|
263
306
|
- NOMBRES
|
|
264
307
|
----------------------------------*/
|
|
265
308
|
// On ne spread pas min et max afin quils soient passés dans les props du composant
|
|
266
|
-
public number = (withDecimals: boolean) => ({ ...opts }:
|
|
309
|
+
public number = (withDecimals: boolean) => ({ ...opts }: TValidatorOptions<number> & {
|
|
267
310
|
min?: number,
|
|
268
311
|
max?: number,
|
|
269
312
|
step?: number,
|
|
@@ -309,7 +352,7 @@ export default class SchemaValidators {
|
|
|
309
352
|
|
|
310
353
|
public float = this.number(true)
|
|
311
354
|
|
|
312
|
-
public bool = (opts:
|
|
355
|
+
public bool = (opts: TValidatorOptions<boolean> & {} = {}) =>
|
|
313
356
|
new Validator<boolean>('bool', (val, options, path) => {
|
|
314
357
|
|
|
315
358
|
if (typeof val !== 'boolean' && !['true', 'false'].includes(val))
|
|
@@ -326,7 +369,7 @@ export default class SchemaValidators {
|
|
|
326
369
|
/*----------------------------------
|
|
327
370
|
- AUTRES
|
|
328
371
|
----------------------------------*/
|
|
329
|
-
public date = (opts:
|
|
372
|
+
public date = (opts: TValidatorOptions<Date> & {
|
|
330
373
|
|
|
331
374
|
} = {}) => new Validator<Date>('date', (val, options, path) => {
|
|
332
375
|
|
|
@@ -350,50 +393,36 @@ export default class SchemaValidators {
|
|
|
350
393
|
...opts,
|
|
351
394
|
})
|
|
352
395
|
|
|
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
|
-
}
|
|
396
|
+
public richText(opts: TValidatorOptions<string> & TRichTextValidatorOptions = {}) {
|
|
397
|
+
return new Validator<string>('richText', (val, options, path) => {
|
|
376
398
|
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
399
|
+
// We get a stringified json as input since the editor workds with JSON string
|
|
400
|
+
try {
|
|
401
|
+
val = JSON.parse(val);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
throw new InputError("Invalid rich text format.");
|
|
381
404
|
}
|
|
382
405
|
|
|
383
|
-
|
|
384
|
-
|
|
406
|
+
// Check that the root exists and has a valid type
|
|
407
|
+
if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root')
|
|
408
|
+
throw new InputError("Invalid rich text value (1).");
|
|
385
409
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
410
|
+
// Check if root has children array
|
|
411
|
+
if (!Array.isArray(val.root.children))
|
|
412
|
+
throw new InputError("Invalid rich text value (2).");
|
|
390
413
|
|
|
391
|
-
|
|
414
|
+
// Validate each child node in root
|
|
415
|
+
for (const child of val.root.children) {
|
|
416
|
+
validateLexicalNode(child, opts);
|
|
417
|
+
}
|
|
392
418
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
419
|
+
return val;
|
|
420
|
+
|
|
421
|
+
}, {
|
|
422
|
+
//defaut: new Date,
|
|
423
|
+
...opts,
|
|
424
|
+
})
|
|
425
|
+
}
|
|
397
426
|
|
|
398
427
|
/*----------------------------------
|
|
399
428
|
- FICHIER
|
|
@@ -402,14 +431,14 @@ export default class SchemaValidators {
|
|
|
402
431
|
|
|
403
432
|
} = {}) => new Validator<FileToUpload>('file', (val, options, path) => {
|
|
404
433
|
|
|
405
|
-
if (!(val instanceof FileToUpload))
|
|
406
|
-
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
407
|
-
|
|
408
434
|
// Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
|
|
409
435
|
// NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
|
|
410
436
|
if (typeof val === 'string')
|
|
411
437
|
return EXCLUDE_VALUE;
|
|
412
438
|
|
|
439
|
+
if (!(val instanceof FileToUpload))
|
|
440
|
+
throw new InputError(`Must be a File (${typeof val} received)`);
|
|
441
|
+
|
|
413
442
|
// MIME
|
|
414
443
|
if (type !== undefined) {
|
|
415
444
|
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
----------------------------------*/
|
|
4
4
|
|
|
5
5
|
// Core
|
|
6
|
+
import type { Application } from '@server/app';
|
|
6
7
|
|
|
7
8
|
// Specific
|
|
8
9
|
import SchemaValidator, { TFileValidator } from '@common/validation/validators';
|
|
9
|
-
|
|
10
|
-
import Validator, { EXCLUDE_VALUE,} from '@common/validation/validator';
|
|
10
|
+
import Validator, { TValidatorOptions } from '@common/validation/validator';
|
|
11
11
|
|
|
12
12
|
import type FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
13
13
|
|
|
@@ -21,4 +21,26 @@ import type FileToUpload from '@client/components/inputv3/file/FileToUpload';
|
|
|
21
21
|
----------------------------------*/
|
|
22
22
|
export default class ServerSchemaValidator extends SchemaValidator {
|
|
23
23
|
|
|
24
|
+
public constructor( private 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
|
+
})
|
|
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 }
|
|
@@ -1,26 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/*----------------------------------
|
|
2
|
+
- DEPENDANCES
|
|
3
|
+
----------------------------------*/
|
|
4
4
|
|
|
5
|
+
// Npm
|
|
6
|
+
import { createHeadlessEditor } from '@lexical/headless';
|
|
7
|
+
import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
|
|
8
|
+
import { $getRoot, SerializedEditorState } from 'lexical';
|
|
5
9
|
import { JSDOM } from 'jsdom';
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
// Core
|
|
12
|
+
import editorNodes from '@common/data/rte/nodes';
|
|
13
|
+
import ExampleTheme from '@client/components/inputv3/Rte/themes/PlaygroundEditorTheme';
|
|
8
14
|
|
|
9
|
-
|
|
15
|
+
/*----------------------------------
|
|
16
|
+
- FUNCTIONS
|
|
17
|
+
----------------------------------*/
|
|
18
|
+
export const htmlToJson = async (htmlString: string): Promise<SerializedEditorState> => {
|
|
10
19
|
|
|
11
|
-
const editor = createHeadlessEditor({
|
|
12
|
-
nodes: editorNodes
|
|
20
|
+
const editor = createHeadlessEditor({
|
|
21
|
+
nodes: editorNodes,
|
|
22
|
+
theme: ExampleTheme,
|
|
13
23
|
});
|
|
14
|
-
|
|
24
|
+
|
|
15
25
|
await editor.update(() => {
|
|
16
26
|
|
|
17
27
|
const root = $getRoot();
|
|
18
28
|
|
|
19
|
-
// In a headless environment you can use a package such as JSDom to parse the HTML string.
|
|
20
29
|
const dom = new JSDOM(htmlString);
|
|
21
30
|
|
|
22
31
|
// Once you have the DOM instance it's easy to generate LexicalNodes.
|
|
23
|
-
const lexicalNodes = $generateNodesFromDOM(editor, dom.window.document);
|
|
32
|
+
const lexicalNodes = $generateNodesFromDOM(editor, dom ? dom.window.document : window.document);
|
|
24
33
|
|
|
25
34
|
lexicalNodes.forEach((node) => root.append(node));
|
|
26
35
|
});
|
|
@@ -30,18 +39,20 @@ export const htmlToJson = async (htmlString: string) => {
|
|
|
30
39
|
};
|
|
31
40
|
|
|
32
41
|
export const jsonToHtml = async (jsonString: string) => {
|
|
33
|
-
|
|
34
42
|
|
|
43
|
+
// Server side: simulate DOM environment
|
|
35
44
|
const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
|
|
36
45
|
global.window = dom.window;
|
|
37
46
|
global.document = dom.window.document;
|
|
38
47
|
global.DOMParser = dom.window.DOMParser;
|
|
39
48
|
global.MutationObserver = dom.window.MutationObserver;
|
|
40
|
-
|
|
49
|
+
|
|
41
50
|
// Create a headless Lexical editor instance
|
|
42
51
|
const editor = createHeadlessEditor({
|
|
43
52
|
namespace: 'headless',
|
|
44
53
|
editable: false,
|
|
54
|
+
nodes: editorNodes,
|
|
55
|
+
theme: ExampleTheme,
|
|
45
56
|
});
|
|
46
57
|
|
|
47
58
|
// Set the editor state from JSON
|
|
@@ -49,14 +60,14 @@ export const jsonToHtml = async (jsonString: string) => {
|
|
|
49
60
|
if (state.isEmpty())
|
|
50
61
|
return null;
|
|
51
62
|
|
|
52
|
-
editor.setEditorState(
|
|
63
|
+
editor.setEditorState(state);
|
|
53
64
|
|
|
54
65
|
// Generate HTML from the Lexical nodes
|
|
55
66
|
const html = await editor.getEditorState().read(() => {
|
|
56
67
|
return $generateHtmlFromNodes(editor);
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Clean up global variables set for JSDOM to avoid memory leaks
|
|
60
71
|
delete global.window;
|
|
61
72
|
delete global.document;
|
|
62
73
|
delete global.DOMParser;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
*/
|
|
8
|
-
export default {
|
|
9
|
-
code: 'editor-code',
|
|
10
|
-
heading: {
|
|
11
|
-
h1: 'editor-heading-h1',
|
|
12
|
-
h2: 'editor-heading-h2',
|
|
13
|
-
h3: 'editor-heading-h3',
|
|
14
|
-
h4: 'editor-heading-h4',
|
|
15
|
-
h5: 'editor-heading-h5',
|
|
16
|
-
},
|
|
17
|
-
image: 'editor-image',
|
|
18
|
-
link: 'editor-link',
|
|
19
|
-
list: {
|
|
20
|
-
listitem: 'editor-listitem',
|
|
21
|
-
nested: {
|
|
22
|
-
listitem: 'editor-nested-listitem',
|
|
23
|
-
},
|
|
24
|
-
ol: 'editor-list-ol',
|
|
25
|
-
ul: 'editor-list-ul',
|
|
26
|
-
},
|
|
27
|
-
ltr: 'ltr',
|
|
28
|
-
paragraph: 'editor-paragraph',
|
|
29
|
-
placeholder: 'editor-placeholder',
|
|
30
|
-
quote: 'editor-quote',
|
|
31
|
-
rtl: 'rtl',
|
|
32
|
-
text: {
|
|
33
|
-
bold: 'editor-text-bold',
|
|
34
|
-
code: 'editor-text-code',
|
|
35
|
-
hashtag: 'editor-text-hashtag',
|
|
36
|
-
italic: 'editor-text-italic',
|
|
37
|
-
overflowed: 'editor-text-overflowed',
|
|
38
|
-
strikethrough: 'editor-text-strikethrough',
|
|
39
|
-
underline: 'editor-text-underline',
|
|
40
|
-
underlineStrikethrough: 'editor-text-underlineStrikethrough',
|
|
41
|
-
},
|
|
42
|
-
};
|