5htp-core 0.4.7 → 0.4.8-2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/package.json +6 -1
  2. package/src/client/assets/css/components/table.less +2 -0
  3. package/src/client/assets/css/text/titres.less +1 -1
  4. package/src/client/assets/css/utils/layouts.less +8 -2
  5. package/src/client/components/Dialog/Manager.tsx +65 -29
  6. package/src/client/components/Dialog/card.tsx +3 -1
  7. package/src/client/components/Dialog/index.less +1 -2
  8. package/src/client/components/Select/ChoiceSelector.tsx +7 -5
  9. package/src/client/components/Select/index.tsx +162 -130
  10. package/src/client/components/index.ts +2 -1
  11. package/src/client/components/inputv3/Rte/ExampleTheme.tsx +42 -0
  12. package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +167 -0
  13. package/src/client/components/inputv3/Rte/icons/LICENSE.md +5 -0
  14. package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +4 -0
  15. package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +4 -0
  16. package/src/client/components/inputv3/Rte/icons/journal-text.svg +5 -0
  17. package/src/client/components/inputv3/Rte/icons/justify.svg +3 -0
  18. package/src/client/components/inputv3/Rte/icons/text-center.svg +3 -0
  19. package/src/client/components/inputv3/Rte/icons/text-left.svg +3 -0
  20. package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +3 -0
  21. package/src/client/components/inputv3/Rte/icons/text-right.svg +3 -0
  22. package/src/client/components/inputv3/Rte/icons/type-bold.svg +3 -0
  23. package/src/client/components/inputv3/Rte/icons/type-italic.svg +3 -0
  24. package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +3 -0
  25. package/src/client/components/inputv3/Rte/icons/type-underline.svg +3 -0
  26. package/src/client/components/inputv3/Rte/index.tsx +163 -0
  27. package/src/client/components/inputv3/Rte/style.less +428 -0
  28. package/src/client/components/inputv3/base.less +20 -33
  29. package/src/client/components/inputv3/base.tsx +36 -2
  30. package/src/client/components/inputv3/file/index.tsx +11 -5
  31. package/src/client/components/inputv3/index.tsx +45 -44
  32. package/src/common/data/rte/index.ts +66 -0
  33. package/src/common/data/rte/nodes.ts +20 -0
  34. package/src/common/validation/validators.ts +49 -4
  35. package/src/server/services/auth/index.ts +0 -2
  36. package/src/server/services/database/index.ts +1 -1
  37. package/src/client/components/input/Rte/index.less +0 -13
  38. package/src/client/components/input/Rte/index.tsx +0 -143
  39. package/src/client/components/input/Rte/selection.ts +0 -34
  40. package/src/common/data/rte.tsx +0 -11
@@ -8,7 +8,7 @@ import { VNode, JSX } from 'preact';
8
8
  import TextareaAutosize from 'react-textarea-autosize';
9
9
 
10
10
  // Core libs
11
- import { useInput, InputBaseProps } from './base';
11
+ import { useInput, InputBaseProps, InputWrapper } from './base';
12
12
  import { default as Validator } from '../../../common/validation/validator';
13
13
  import type SchemaValidators from '@common/validation/validators';
14
14
 
@@ -47,17 +47,18 @@ type TInputElementProps = Omit<(
47
47
  /*----------------------------------
48
48
  - COMPOSANT
49
49
  ----------------------------------*/
50
- export default ({
51
- // Decoration
52
- icon, prefix, suffix, iconR, required, size, className = '',
53
- // State
54
- inputRef, errors,
55
- // Behavior
56
- type, choice, validator,
57
- // Actions
58
- onPressEnter,
59
- ...props
60
- }: Props & InputBaseProps<string> & TInputElementProps) => {
50
+ export default (props: Props & InputBaseProps<string> & TInputElementProps) => {
51
+
52
+ let {
53
+ // Decoration
54
+ icon, prefix, suffix, iconR, placeholder, size, className = '',
55
+ // State
56
+ inputRef, errors,
57
+ // Behavior
58
+ type, validator,
59
+ // Actions
60
+ onPressEnter
61
+ } = props;
61
62
 
62
63
  /*----------------------------------
63
64
  - INIT
@@ -128,7 +129,7 @@ export default ({
128
129
 
129
130
  } else if (type === 'longtext') {
130
131
 
131
- prefix = prefix || <i src="text" />;
132
+ // No icon because not good looking ane we want as much space as possible
132
133
  Tag = 'textarea';
133
134
  className += ' multiline';
134
135
 
@@ -170,43 +171,43 @@ export default ({
170
171
  /*----------------------------------
171
172
  - RENDER
172
173
  ----------------------------------*/
173
- return <>
174
- <div class={className} onClick={() => refInput.current?.focus()}>
174
+ return (
175
+ <InputWrapper {...props}>
176
+ <div class={className} onClick={() => refInput.current?.focus()}>
175
177
 
176
- {prefix}
178
+ {prefix}
177
179
 
178
- <div class="contValue">
180
+ <div class="contValue">
179
181
 
180
- <Tag {...fieldProps}
181
- // @ts-ignore: Property 'ref' does not exist on type 'IntrinsicAttributes'
182
- ref={refInput}
183
- value={value}
184
- onFocus={() => setState({ focus: true })}
185
- onBlur={() => setState({ focus: false })}
186
- onChange={(e) => updateValue(e.target.value)}
182
+ <Tag {...fieldProps}
187
183
 
188
- onKeyDown={(e: KeyboardEvent) => {
184
+ placeholder={placeholder || props.title}
189
185
 
190
- if (onPressEnter && e.key === 'Enter' && value !== undefined) {
191
- commitValue();
192
- onPressEnter(value)
193
- }
194
- }}
195
- />
186
+ // @ts-ignore: Property 'ref' does not exist on type 'IntrinsicAttributes'
187
+ ref={refInput}
188
+ value={value}
189
+ onFocus={() => setState({ focus: true })}
190
+ onBlur={() => setState({ focus: false })}
191
+ onChange={(e) => updateValue(e.target.value)}
192
+ onKeyDown={(e: KeyboardEvent) => {
196
193
 
197
- <label>{props.title}{required && (
198
- <span class="fg error">&nbsp;*</span>
199
- )}</label>
200
- </div>
194
+ if (onPressEnter && e.key === 'Enter' && value !== undefined) {
195
+ commitValue();
196
+ onPressEnter(value)
197
+ }
198
+ }}
199
+ />
200
+ </div>
201
201
 
202
- {suffix}
202
+ {suffix}
203
+
204
+ {errors?.length && (
205
+ <div class="bubble bg error bottom">
206
+ {errors.join('. ')}
207
+ </div>
208
+ )}
203
209
 
204
- {errors?.length && (
205
- <div class="bubble bg error bottom">
206
- {errors.join('. ')}
207
- </div>
208
- )}
209
-
210
- </div>
211
- </>
210
+ </div>
211
+ </InputWrapper>
212
+ )
212
213
  }
@@ -0,0 +1,66 @@
1
+ import { createHeadlessEditor } from '@lexical/headless';
2
+ import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
3
+ import { $getRoot } from 'lexical';
4
+
5
+ import { JSDOM } from 'jsdom';
6
+
7
+ import editorNodes from './nodes';
8
+
9
+ export const htmlToJson = async (htmlString: string) => {
10
+
11
+ const editor = createHeadlessEditor({
12
+ nodes: editorNodes
13
+ });
14
+
15
+ await editor.update(() => {
16
+
17
+ const root = $getRoot();
18
+
19
+ // In a headless environment you can use a package such as JSDom to parse the HTML string.
20
+ const dom = new JSDOM(htmlString);
21
+
22
+ // Once you have the DOM instance it's easy to generate LexicalNodes.
23
+ const lexicalNodes = $generateNodesFromDOM(editor, dom.window.document);
24
+
25
+ lexicalNodes.forEach((node) => root.append(node));
26
+ });
27
+
28
+ const state = editor.getEditorState();
29
+ return state.toJSON();
30
+ };
31
+
32
+ export const jsonToHtml = async (jsonString: string) => {
33
+
34
+
35
+ const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
36
+ global.window = dom.window;
37
+ global.document = dom.window.document;
38
+ global.DOMParser = dom.window.DOMParser;
39
+ global.MutationObserver = dom.window.MutationObserver;
40
+
41
+ // Create a headless Lexical editor instance
42
+ const editor = createHeadlessEditor({
43
+ namespace: 'headless',
44
+ editable: false,
45
+ });
46
+
47
+ // Set the editor state from JSON
48
+ const state = editor.parseEditorState(jsonString);
49
+ if (state.isEmpty())
50
+ return null;
51
+
52
+ editor.setEditorState( state );
53
+
54
+ // Generate HTML from the Lexical nodes
55
+ const html = await editor.getEditorState().read(() => {
56
+ return $generateHtmlFromNodes(editor);
57
+ });
58
+
59
+ // Clean up global variables set for JSDOM to avoid memory leaks
60
+ delete global.window;
61
+ delete global.document;
62
+ delete global.DOMParser;
63
+ delete global.MutationObserver;
64
+
65
+ return html;
66
+ }
@@ -0,0 +1,20 @@
1
+
2
+ import { HeadingNode, QuoteNode } from "@lexical/rich-text";
3
+ import { TableCellNode, TableNode, TableRowNode } from "@lexical/table";
4
+ import { ListItemNode, ListNode } from "@lexical/list";
5
+ import { CodeHighlightNode, CodeNode } from "@lexical/code";
6
+ import { AutoLinkNode, LinkNode } from "@lexical/link";
7
+
8
+ export default [
9
+ HeadingNode,
10
+ ListNode,
11
+ ListItemNode,
12
+ QuoteNode,
13
+ CodeNode,
14
+ CodeHighlightNode,
15
+ TableNode,
16
+ TableCellNode,
17
+ TableRowNode,
18
+ AutoLinkNode,
19
+ LinkNode
20
+ ]
@@ -139,7 +139,7 @@ export default class SchemaValidators {
139
139
  return undefined;
140
140
 
141
141
  // Normalize for verifications
142
- const choicesValues = choices?.map(v => v.value)
142
+ const choicesValues = choices?.map(v => typeof v === 'object' ? v.value : v)
143
143
 
144
144
  const checkChoice = ( choice: any ) => {
145
145
 
@@ -350,6 +350,51 @@ export default class SchemaValidators {
350
350
  ...opts,
351
351
  })
352
352
 
353
+ public richText = (opts: TValidator<string> & {
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
+ }
376
+
377
+ // Validate paragraph, heading, or other structural nodes that may contain children
378
+ if (['paragraph', 'heading', 'list', 'listitem'].includes(node.type))
379
+ if (!Array.isArray(node.children) || !node.children.every(validateNode)) {
380
+ throw new InputError("Invalid rich text value (5).");
381
+ }
382
+
383
+ return true;
384
+ }
385
+
386
+ // Validate each child node in root
387
+ for (const child of val.root.children) {
388
+ validateNode(child);
389
+ }
390
+
391
+ return val;
392
+
393
+ }, {
394
+ //defaut: new Date,
395
+ ...opts,
396
+ })
397
+
353
398
  /*----------------------------------
354
399
  - FICHIER
355
400
  ----------------------------------*/
@@ -357,14 +402,14 @@ export default class SchemaValidators {
357
402
 
358
403
  } = {}) => new Validator<FileToUpload>('file', (val, options, path) => {
359
404
 
360
- if (!(val instanceof FileToUpload))
361
- throw new InputError(`Must be a File (${typeof val} received)`);
362
-
363
405
  // Chaine = url ancien fichier = exclusion de la valeur pour conserver l'ancien fichier
364
406
  // NOTE: Si la valeur est présente mais undefined, alors on supprimera le fichier
365
407
  if (typeof val === 'string')
366
408
  return EXCLUDE_VALUE;
367
409
 
410
+ if (!(val instanceof FileToUpload))
411
+ throw new InputError(`Must be a File (${typeof val} received)`);
412
+
368
413
  // MIME
369
414
  if (type !== undefined) {
370
415
 
@@ -225,8 +225,6 @@ export default abstract class AuthService<
225
225
 
226
226
  this.config.debug && console.warn(LogPrefix, `Check auth, role = ${role}. Current user =`, user?.name);
227
227
 
228
- console.log({ entity, role, motivation });
229
-
230
228
  if (user === undefined) {
231
229
 
232
230
  throw new Error(`request.user has not been decoded.`);
@@ -256,7 +256,7 @@ export default class SQL extends Service<Config, Hooks, Application, Services> {
256
256
  }
257
257
 
258
258
  public equalities = (data: TObjetDonnees, keys = Object.keys(data)) =>
259
- keys.map(k => '' + k + ' = ' + mysql.escape( data[k] ))
259
+ keys.map(k => '' + k + ' = ' + this.esc( data[k] ))
260
260
 
261
261
  /*----------------------------------
262
262
  - OPERATIONS: LOW LEVELf
@@ -1,13 +0,0 @@
1
- .contChamp.rte {
2
- .corpsChamp {
3
- height: auto !important;
4
- line-height: auto !important;
5
- }
6
-
7
- #toolbar {
8
- position: absolute;
9
- background: red;
10
- height: 30px;
11
- width: 15rem;
12
- }
13
- }
@@ -1,143 +0,0 @@
1
-
2
- // INSPIRATION:
3
- // https://github.com/sstur/react-rte
4
- // https://drupalgutenberg.org/demo
5
-
6
- /*----------------------------------
7
- - DEPENDANCES
8
- ----------------------------------*/
9
-
10
- // Npm
11
- import React from 'react';
12
- import {
13
-
14
- Editor,
15
- EditorState,
16
-
17
- SelectionState,
18
-
19
- convertToRaw,
20
- KeyBindingUtil,
21
- RawDraftContentState,
22
-
23
- ContentBlock,
24
- EditorBlock
25
- } from 'draft-js';
26
-
27
- // Libs
28
- import Champ from '../Base';
29
- import { getSelection, getSelectionRect } from './selection';
30
-
31
- /*----------------------------------
32
- - TYPES
33
- ----------------------------------*/
34
- type TValeur = EditorState
35
- const valeurDefaut = '' as string;
36
- type TValeurDefaut = EditorState;
37
- type TValeurOut = string;
38
-
39
- export type Props = {
40
- valeur: TValeur,
41
- }
42
-
43
- /*----------------------------------
44
- - COMPOSANT
45
- ----------------------------------*/
46
- import './index.less';
47
- export default Champ<Props, TValeurDefaut, TValeurOut>('rte', {
48
- valeurDefaut,
49
- saisieManuelle: true
50
- }, ({
51
- // Spread TOUTES les props dont on a besoin pour éviter les problèmes de référence avec props
52
- placeholder, attrsChamp
53
- }, { valeur, state, setState, commitCurrentValue }, rendre) => {
54
-
55
- /*----------------------------------
56
- - CONSTRUCTION CHAMP
57
- ----------------------------------*/
58
-
59
- const refEditeur = React.useRef<Editor>(null);
60
-
61
- // Init state
62
- if (!valeur)
63
- valeur = EditorState.createEmpty();
64
-
65
- /*const updateToolbar = (selection: SelectionState) => {
66
-
67
- if (selection.isCollapsed()) {
68
- return;
69
- }
70
- // eslint-disable-next-line no-undef
71
- const nativeSelection = getSelection(window);
72
- if (!nativeSelection.rangeCount) {
73
- return;
74
- }
75
- const selectionBoundary = getSelectionRect(nativeSelection);
76
-
77
- console.log('selectionBoundary', selectionBoundary);
78
-
79
- const toolbarNode = document.getElementById('toolbar');
80
- if (!toolbarNode) return;
81
- const toolbarBoundary = toolbarNode.getBoundingClientRect();
82
-
83
- // parent = conteneur éditeur draft
84
- const parent = document.getElementById('toolbar');
85
- if (!parent) return;
86
- const parentBoundary = parent.getBoundingClientRect();
87
-
88
- toolbarNode.style.top =
89
- `${(selectionBoundary.top - parentBoundary.top - toolbarBoundary.height - 5)}px`;
90
- toolbarNode.style.width = `${toolbarBoundary.width}px`;
91
-
92
- // The left side of the tooltip should be:
93
- // center of selection relative to parent - half width of toolbar
94
- const selectionCenter = (selectionBoundary.left + (selectionBoundary.width / 2)) - parentBoundary.left;
95
- let left = selectionCenter - (toolbarBoundary.width / 2);
96
- const screenLeft = parentBoundary.left + left;
97
- if (screenLeft < 0) {
98
- // If the toolbar would be off-screen
99
- // move it as far left as it can without going off-screen
100
- left = -parentBoundary.left;
101
- }
102
- toolbarNode.style.left = `${left}px`;
103
-
104
-
105
- }*/
106
-
107
- const onChange = (state: EditorState) => {
108
-
109
- /*const selection = state.getSelection();
110
- updateToolbar(selection);*/
111
-
112
- // Détection changement de contenu
113
- /*const currentContentState = valeur.getCurrentContent()
114
- const newContentState = state.getCurrentContent()
115
- if (currentContentState === newContentState)*/
116
-
117
- setState({ valeur: state });
118
-
119
- }
120
-
121
- /*----------------------------------
122
- - RENDU DU CHAMP
123
- ----------------------------------*/
124
- return rendre((
125
- <div className="champ typographie" onClick={() => refEditeur.current?.focus()}>
126
- <Editor
127
- ref={refEditeur}
128
- editorState={valeur}
129
-
130
- onChange={onChange}
131
- onBlur={() => commitCurrentValue()}
132
-
133
- placeholder={placeholder}
134
- textAlignment="left"
135
- textDirectionality="LTR"
136
-
137
- />
138
-
139
- {/* <div id="toolbar">coucou</div> */}
140
- </div>
141
- // Les propétés modifiées sont passées ici
142
- ), { });
143
- })
@@ -1,34 +0,0 @@
1
- // https://github.com/brijeshb42/medium-draft/blob/c5350bb76489fd4f2f90f1dc69db92224138d20a/src/util/index.js
2
-
3
- /*
4
- Returns the `boundingClientRect` of the passed selection.
5
- */
6
- export const getSelectionRect = (selected): DOMRect => {
7
- const _rect = selected.getRangeAt(0).getBoundingClientRect();
8
- // selected.getRangeAt(0).getBoundingClientRect()
9
- let rect = _rect && _rect.top ? _rect : selected.getRangeAt(0).getClientRects()[0];
10
- if (!rect) {
11
- if (selected.anchorNode && selected.anchorNode.getBoundingClientRect) {
12
- rect = selected.anchorNode.getBoundingClientRect();
13
- rect.isEmptyline = true;
14
- } else {
15
- return null;
16
- }
17
- }
18
- return rect;
19
- };
20
-
21
- /*
22
- Returns the native selection node.
23
- */
24
- export const getSelection = (root) => {
25
- let t = null;
26
- if (root.getSelection) {
27
- t = root.getSelection();
28
- } else if (root.document.getSelection) {
29
- t = root.document.getSelection();
30
- } else if (root.document.selection) {
31
- t = root.document.selection.createRange().text;
32
- }
33
- return t;
34
- };
@@ -1,11 +0,0 @@
1
- import mediumDraftExporter from '@client/components/Rte/exporter';
2
- import { convertFromRaw } from 'draft-js';
3
- export { convertFromRaw } from 'draft-js';
4
-
5
- export const convertir = (
6
- state: any,
7
- amp?: boolean
8
- ) => mediumDraftExporter(convertFromRaw( state ), amp);
9
-
10
- export const versHtml = (state: any): string => convertir(state);
11
- export const versAmp = (state: any): string => convertir(state, true);