5htp-core 0.4.8-2 → 0.4.8-3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp-core",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.4.8-2",
4
+ "version": "0.4.8-3",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp-core.git",
7
7
  "license": "MIT",
@@ -0,0 +1,40 @@
1
+ import { createEditor, LexicalEditor } from 'lexical';
2
+ import { $generateHtmlFromNodes } from '@lexical/html';
3
+ import editorNodes from '@common/data/rte/nodes';
4
+
5
+ class RichEditorUtils {
6
+
7
+ public active: {
8
+ title: string,
9
+ close: () => void
10
+ } | null = null;
11
+
12
+ private virtualEditor: LexicalEditor | null = null;
13
+
14
+ public async jsonToHtml( value: {} ): Promise<string | null> {
15
+
16
+ if (!this.virtualEditor) {
17
+ // Create a headless Lexical editor instance
18
+ this.virtualEditor = createEditor({
19
+ nodes: editorNodes
20
+ });
21
+ }
22
+
23
+ // Set the editor state from JSON
24
+ const state = this.virtualEditor.parseEditorState(value);
25
+ if (state.isEmpty())
26
+ return null;
27
+
28
+ this.virtualEditor.setEditorState(state);
29
+
30
+ // Generate HTML from the Lexical nodes
31
+ const html = await this.virtualEditor.getEditorState().read(() => {
32
+ return $generateHtmlFromNodes(this.virtualEditor);
33
+ });
34
+
35
+ return html;
36
+ }
37
+
38
+ }
39
+
40
+ export default new RichEditorUtils();
@@ -5,7 +5,8 @@
5
5
  // Npm
6
6
  import React from 'react';
7
7
 
8
- import { EditorState, $getRoot } from 'lexical';
8
+ import { EditorState, createEditor } from 'lexical';
9
+ import { $generateHtmlFromNodes } from '@lexical/html';
9
10
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
10
11
  import { AutoFocusPlugin } from '@lexical/react/LexicalAutoFocusPlugin';
11
12
  import { LexicalComposer } from '@lexical/react/LexicalComposer';
@@ -14,6 +15,7 @@ import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary';
14
15
  import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
15
16
  import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
16
17
  import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
18
+ import RichEditorUtils from './currentEditor';
17
19
 
18
20
  // Core libs
19
21
  import { useInput, InputBaseProps, InputWrapper } from '../base';
@@ -31,7 +33,7 @@ const EMPTY_STATE = '{"root":{"children":[{"children":[],"direction":null,"forma
31
33
  ----------------------------------*/
32
34
 
33
35
  export type Props = {
34
- onPressEnter?: (value: string) => void,
36
+ preview?: boolean,
35
37
  }
36
38
 
37
39
  const ValueControlPlugin = ({ props, value }) => {
@@ -52,21 +54,25 @@ const ValueControlPlugin = ({ props, value }) => {
52
54
  /*----------------------------------
53
55
  - COMPOSANT
54
56
  ----------------------------------*/
55
- export default (props: Props & InputBaseProps<{}>) => {
57
+ export default (props: Props & InputBaseProps<string>) => {
56
58
 
57
59
  let {
58
60
  // Decoration
59
61
  required, size, title, className = '',
60
62
  // State
61
- inputRef, errors,
63
+ errors,
62
64
  // Actions
63
- onPressEnter
65
+ preview = true
64
66
  } = props;
65
67
 
66
68
  /*----------------------------------
67
69
  - INIT
68
70
  ----------------------------------*/
69
71
 
72
+ const [isPreview, setIsPreview] = React.useState(preview);
73
+
74
+ const [html, setHTML] = React.useState();
75
+
70
76
  const [{ value }, setValue] = useInput(props, EMPTY_STATE, true);
71
77
 
72
78
  // Trigger onchange oly when finished typing
@@ -78,16 +84,52 @@ export default (props: Props & InputBaseProps<{}>) => {
78
84
  - ACTIONS
79
85
  ----------------------------------*/
80
86
 
87
+ React.useEffect(async () => {
88
+
89
+ if (isPreview)
90
+ renderPreview(value);
91
+
92
+ }, [value, isPreview]);
93
+
94
+ // When isPreview changes, close the active editor
95
+ React.useEffect(() => {
96
+ if (!isPreview) {
97
+
98
+ // Close active editor
99
+ if (RichEditorUtils.active && RichEditorUtils.active?.title !== title)
100
+ RichEditorUtils.active.close();
101
+
102
+ // Set active editor
103
+ RichEditorUtils.active = {
104
+ title,
105
+ close: () => setIsPreview(true)
106
+ }
107
+
108
+ }
109
+ }, [isPreview]);
110
+
111
+ const renderPreview = async (value: {}) => {
112
+
113
+ if (typeof document === 'undefined')
114
+ throw new Error("HTML preview disabled in server side.");
115
+
116
+ const html = await RichEditorUtils.jsonToHtml(value);
117
+
118
+ setHTML(html);
119
+ }
120
+
81
121
  const onChange = (editorState: EditorState) => {
82
122
  editorState.read(() => {
83
123
 
84
- const stateJson = editorState.toJSON();
85
-
86
124
  if (refCommit.current !== null)
87
125
  clearTimeout(refCommit.current);
88
126
 
89
127
  refCommit.current = setTimeout(() => {
128
+
129
+ const stateJson = JSON.stringify(editorState.toJSON());
130
+
90
131
  setValue(stateJson);
132
+
91
133
  }, 100);
92
134
  });
93
135
  };
@@ -99,9 +141,20 @@ export default (props: Props & InputBaseProps<{}>) => {
99
141
  <InputWrapper {...props}>
100
142
  <div class={className}>
101
143
 
102
- {typeof window !== 'undefined' && (
144
+ {isPreview ? (
145
+
146
+ !html ? (
147
+ <div class="col al-center h-4">
148
+ <i src="spin" />
149
+ </div>
150
+ ) : (
151
+ <div class="h-4 scrollable col clickable"
152
+ onClick={() => setIsPreview(false)}
153
+ dangerouslySetInnerHTML={{ __html: html }} />
154
+ )
155
+
156
+ ) : typeof window !== 'undefined' && (
103
157
  <LexicalComposer initialConfig={{
104
- namespace: 'React.js Demo',
105
158
  editorState: value || EMPTY_STATE,
106
159
  nodes: editorNodes,
107
160
  // Handling of errors during update
@@ -125,7 +178,7 @@ export default (props: Props & InputBaseProps<{}>) => {
125
178
  ErrorBoundary={LexicalErrorBoundary}
126
179
  />
127
180
  <HistoryPlugin />
128
- {/* <AutoFocusPlugin /> */}
181
+ <AutoFocusPlugin />
129
182
  <OnChangePlugin onChange={onChange} />
130
183
  <ValueControlPlugin props={props} value={value} />
131
184
  </div>
@@ -354,6 +354,13 @@ export default class SchemaValidators {
354
354
 
355
355
  } = {}) => new Validator<string>('richText', (val, options, path) => {
356
356
 
357
+ // We get a stringified json as input since the editor workds with JSON string
358
+ try {
359
+ val = JSON.parse(val);
360
+ } catch (error) {
361
+ throw new InputError("Invalid rich text format.");
362
+ }
363
+
357
364
  // Check that the root exists and has a valid type
358
365
  if (!val || typeof val !== 'object' || typeof val.root !== 'object' || val.root.type !== 'root')
359
366
  throw new InputError("Invalid rich text value (1).");
@@ -1,26 +1,33 @@
1
+ /*----------------------------------
2
+ - DEPENDANCES
3
+ ----------------------------------*/
4
+
5
+ // Npm
1
6
  import { createHeadlessEditor } from '@lexical/headless';
2
- import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
7
+ import { $generateNodesFromDOM, $generateHtmlFromNodes } from '@lexical/html';
3
8
  import { $getRoot } from 'lexical';
4
-
5
9
  import { JSDOM } from 'jsdom';
6
10
 
7
- import editorNodes from './nodes';
11
+ // Core
12
+ import editorNodes from '@common/data/rte/nodes';
8
13
 
14
+ /*----------------------------------
15
+ - FUNCTIONS
16
+ ----------------------------------*/
9
17
  export const htmlToJson = async (htmlString: string) => {
10
18
 
11
- const editor = createHeadlessEditor({
12
- nodes: editorNodes
19
+ const editor = createHeadlessEditor({
20
+ nodes: editorNodes
13
21
  });
14
-
22
+
15
23
  await editor.update(() => {
16
24
 
17
25
  const root = $getRoot();
18
26
 
19
- // In a headless environment you can use a package such as JSDom to parse the HTML string.
20
27
  const dom = new JSDOM(htmlString);
21
28
 
22
29
  // Once you have the DOM instance it's easy to generate LexicalNodes.
23
- const lexicalNodes = $generateNodesFromDOM(editor, dom.window.document);
30
+ const lexicalNodes = $generateNodesFromDOM(editor, dom ? dom.window.document : window.document);
24
31
 
25
32
  lexicalNodes.forEach((node) => root.append(node));
26
33
  });
@@ -30,18 +37,19 @@ export const htmlToJson = async (htmlString: string) => {
30
37
  };
31
38
 
32
39
  export const jsonToHtml = async (jsonString: string) => {
33
-
34
40
 
41
+ // Server side: simulate DOM environment
35
42
  const dom = new JSDOM(`<!DOCTYPE html><body></body>`);
36
43
  global.window = dom.window;
37
44
  global.document = dom.window.document;
38
45
  global.DOMParser = dom.window.DOMParser;
39
46
  global.MutationObserver = dom.window.MutationObserver;
40
-
47
+
41
48
  // Create a headless Lexical editor instance
42
49
  const editor = createHeadlessEditor({
43
50
  namespace: 'headless',
44
51
  editable: false,
52
+ nodes: editorNodes
45
53
  });
46
54
 
47
55
  // Set the editor state from JSON
@@ -49,14 +57,14 @@ export const jsonToHtml = async (jsonString: string) => {
49
57
  if (state.isEmpty())
50
58
  return null;
51
59
 
52
- editor.setEditorState( state );
60
+ editor.setEditorState(state);
53
61
 
54
62
  // Generate HTML from the Lexical nodes
55
63
  const html = await editor.getEditorState().read(() => {
56
64
  return $generateHtmlFromNodes(editor);
57
- });
58
-
59
- // Clean up global variables set for JSDOM to avoid memory leaks
65
+ });
66
+
67
+ // Clean up global variables set for JSDOM to avoid memory leaks
60
68
  delete global.window;
61
69
  delete global.document;
62
70
  delete global.DOMParser;