5htp-core 0.4.7 → 0.4.8
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 +6 -1
- package/src/client/assets/css/text/titres.less +1 -1
- package/src/client/assets/css/utils/layouts.less +8 -2
- package/src/client/components/Dialog/Manager.tsx +65 -29
- package/src/client/components/Dialog/card.tsx +3 -1
- package/src/client/components/Dialog/index.less +1 -2
- package/src/client/components/Select/ChoiceSelector.tsx +7 -5
- package/src/client/components/Select/index.tsx +162 -130
- package/src/client/components/index.ts +2 -1
- package/src/client/components/inputv3/Rte/ExampleTheme.tsx +42 -0
- package/src/client/components/inputv3/Rte/ToolbarPlugin.tsx +167 -0
- package/src/client/components/inputv3/Rte/icons/LICENSE.md +5 -0
- package/src/client/components/inputv3/Rte/icons/arrow-clockwise.svg +4 -0
- package/src/client/components/inputv3/Rte/icons/arrow-counterclockwise.svg +4 -0
- package/src/client/components/inputv3/Rte/icons/journal-text.svg +5 -0
- package/src/client/components/inputv3/Rte/icons/justify.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/text-center.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/text-left.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/text-paragraph.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/text-right.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/type-bold.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/type-italic.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/type-strikethrough.svg +3 -0
- package/src/client/components/inputv3/Rte/icons/type-underline.svg +3 -0
- package/src/client/components/inputv3/Rte/index.tsx +163 -0
- package/src/client/components/inputv3/Rte/style.less +428 -0
- package/src/client/components/inputv3/base.less +20 -33
- package/src/client/components/inputv3/base.tsx +36 -2
- package/src/client/components/inputv3/index.tsx +45 -44
- package/src/common/data/rte/index.ts +66 -0
- package/src/common/data/rte/nodes.ts +20 -0
- package/src/common/validation/validators.ts +46 -1
- package/src/server/services/auth/index.ts +0 -2
- package/src/server/services/database/index.ts +1 -1
- package/src/client/components/input/Rte/index.less +0 -13
- package/src/client/components/input/Rte/index.tsx +0 -143
- package/src/client/components/input/Rte/selection.ts +0 -34
- package/src/common/data/rte.tsx +0 -11
|
@@ -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
|
----------------------------------*/
|
|
@@ -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 + ' = ' +
|
|
259
|
+
keys.map(k => '' + k + ' = ' + this.esc( data[k] ))
|
|
260
260
|
|
|
261
261
|
/*----------------------------------
|
|
262
262
|
- OPERATIONS: LOW LEVELf
|
|
@@ -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
|
-
};
|
package/src/common/data/rte.tsx
DELETED
|
@@ -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);
|