@centreon/ui 23.10.52 → 23.10.53

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "23.10.52",
3
+ "version": "23.10.53",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
@@ -102,7 +102,9 @@
102
102
  },
103
103
  "dependencies": {
104
104
  "@lexical/link": "0.11.3",
105
+ "@lexical/list": "^0.11.3",
105
106
  "@lexical/react": "0.11.3",
107
+ "@lexical/rich-text": "^0.11.3",
106
108
  "@lexical/selection": "0.11.3",
107
109
  "@lexical/utils": "0.11.3",
108
110
  "@visx/curve": "^2.1.0",
@@ -47,6 +47,7 @@ const useStyles = makeStyles<StyleProps>()(
47
47
 
48
48
  interface Props {
49
49
  className?: string;
50
+ disabled: boolean;
50
51
  editable: boolean;
51
52
  editorState?: string;
52
53
  error?: string;
@@ -72,13 +73,13 @@ const ContentEditable = ({
72
73
  initialEditorState,
73
74
  error,
74
75
  onBlur,
75
- className
76
+ className,
77
+ disabled
76
78
  }: Props): JSX.Element => {
77
79
  const { classes, cx } = useStyles({ editable, error, minInputHeight });
78
80
  const { t } = useTranslation();
79
81
 
80
82
  const [editor] = useLexicalComposerContext();
81
- const [isEditable, setEditable] = useState(false);
82
83
  const [isFocused, setFocused] = useState(false);
83
84
  const [root, setRoot] = useState('');
84
85
 
@@ -90,17 +91,11 @@ const ContentEditable = ({
90
91
  );
91
92
 
92
93
  useLayoutEffect(() => {
93
- setEditable(editor.isEditable());
94
-
95
94
  if (editorState && !editable) {
96
95
  const newEditorState = editor.parseEditorState(editorState);
97
96
 
98
97
  editor.setEditorState(newEditorState);
99
98
  }
100
-
101
- return editor.registerEditableListener((currentIsEditable) => {
102
- setEditable(currentIsEditable);
103
- });
104
99
  }, [editor, editorState]);
105
100
 
106
101
  useEffect(() => {
@@ -128,13 +123,21 @@ const ContentEditable = ({
128
123
  editor.setEditorState(newEditorState);
129
124
  }, [editorState]);
130
125
 
131
- const isTextEmpty = isEmpty(root);
126
+ const isTextEmpty =
127
+ isEmpty(root) &&
128
+ !editor.getEditorState().toJSON().root.children?.[0]?.children?.length;
132
129
 
133
130
  const handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
134
131
  setFocused(false);
135
132
  onBlur?.(event);
136
133
  };
137
134
 
135
+ const isEditable = editor.isEditable();
136
+
137
+ useEffect(() => {
138
+ editor.setEditable(!disabled);
139
+ }, [disabled]);
140
+
138
141
  return (
139
142
  <div
140
143
  className={cx(
@@ -144,9 +144,37 @@ describe('Rich Text Editor', () => {
144
144
  cy.wrap(element).should('not.have.attr', 'target');
145
145
  });
146
146
  });
147
+
148
+ it('does not allow to open links in a new tab', () => {
149
+ cy.mount({
150
+ Component: <RichTextEditor editable openLinkInNewTab={false} />
151
+ });
152
+
153
+ cy.get('[data-testid="RichTextEditor"]').type('cypress');
154
+ cy.get('[data-testid="RichTextEditor"]').focus().type('{selectAll}');
155
+
156
+ cy.get('#link').click();
157
+
158
+ cy.findByLabelText('Saved link').should('have.text', 'https://');
159
+
160
+ cy.findByLabelText('Edit link').click();
161
+ cy.get('#InputLinkField').type('www.centreon.com').type('{enter}');
162
+
163
+ cy.findByText('cypress').parent().should('not.have.attr', 'target');
164
+
165
+ cy.get('[data-testid="RichTextEditor"]').focus().clear();
166
+
167
+ cy.get('[data-testid="RichTextEditor"]')
168
+ .focus()
169
+ .type('https://centreon.com');
170
+
171
+ cy.findByText('https://centreon.com')
172
+ .parent()
173
+ .should('not.have.attr', 'target');
174
+ });
147
175
  });
148
176
 
149
- describe('Disabled Rich Text Editor', () => {
177
+ describe('Rich Text Editor not editable', () => {
150
178
  beforeEach(() => {
151
179
  cy.mount({
152
180
  Component: (
@@ -172,6 +200,33 @@ describe('Rich Text Editor', () => {
172
200
  });
173
201
  });
174
202
 
203
+ describe('Rich Text Editor is disabled', () => {
204
+ beforeEach(() => {
205
+ cy.mount({
206
+ Component: (
207
+ <RichTextEditor
208
+ disabled
209
+ editable
210
+ initialEditorState={mockInititalStateToEditor}
211
+ />
212
+ )
213
+ });
214
+ });
215
+
216
+ it('displays editor when editable props is false and an initialState exist', () => {
217
+ cy.get('[data-testid="RichTextEditor"]')
218
+ .invoke('attr', 'contenteditable')
219
+ .should('eq', 'true');
220
+ cy.findByLabelText('Undo').should('be.disabled');
221
+ cy.findByLabelText('Redo').should('be.disabled');
222
+ cy.findByLabelText('bold').should('be.disabled');
223
+ cy.findByLabelText('italic').should('be.disabled');
224
+ cy.findByLabelText('underline').should('be.disabled');
225
+ cy.findByLabelText('strikethrough').should('be.disabled');
226
+ cy.findByLabelText('link').should('be.disabled');
227
+ });
228
+ });
229
+
175
230
  describe('Custom style editable rich text editor ', () => {
176
231
  beforeEach(() => {
177
232
  cy.mount({
@@ -230,4 +285,57 @@ describe('Rich Text Editor', () => {
230
285
  });
231
286
  });
232
287
  });
288
+
289
+ describe('Block type', () => {
290
+ beforeEach(() => {
291
+ cy.mount({
292
+ Component: <RichTextEditor editable />
293
+ });
294
+ });
295
+
296
+ const testCases = [
297
+ {
298
+ blockType: 'Heading 1',
299
+ tag: 'h1'
300
+ },
301
+ {
302
+ blockType: 'Heading 2',
303
+ tag: 'h2'
304
+ },
305
+ {
306
+ blockType: 'Heading 3',
307
+ tag: 'h3'
308
+ },
309
+ {
310
+ blockType: 'Heading 4',
311
+ tag: 'h4'
312
+ },
313
+ {
314
+ blockType: 'Heading 5',
315
+ tag: 'h5'
316
+ },
317
+ {
318
+ blockType: 'Heading 6',
319
+ tag: 'h6'
320
+ },
321
+ {
322
+ blockType: 'Bullet List',
323
+ tag: 'ul'
324
+ },
325
+ {
326
+ blockType: 'Number List',
327
+ tag: 'ol'
328
+ }
329
+ ];
330
+
331
+ testCases.forEach(({ blockType, tag }) => {
332
+ it(`displays ${blockType} when the corresponding block type button is selected`, () => {
333
+ cy.get('[data-testid="RichTextEditor"]').type('Example');
334
+ cy.findByTestId('Block type').click();
335
+ cy.findByText(blockType).click();
336
+
337
+ cy.get('[data-testid="RichTextEditor"]').find(tag).should('be.visible');
338
+ });
339
+ });
340
+ });
233
341
  });
@@ -3,11 +3,15 @@ import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin';
3
3
  import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin';
4
4
  import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary';
5
5
  import { AutoLinkNode, LinkNode } from '@lexical/link';
6
+ import { HeadingNode } from '@lexical/rich-text';
7
+ import { ListItemNode, ListNode } from '@lexical/list';
6
8
  import anylogger from 'anylogger';
7
9
  import { makeStyles } from 'tss-react/mui';
8
10
  import { EditorState } from 'lexical';
9
11
  import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin';
10
12
  import { equals } from 'ramda';
13
+ import { ListPlugin } from '@lexical/react/LexicalListPlugin';
14
+ import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin';
11
15
 
12
16
  import { Typography } from '@mui/material';
13
17
 
@@ -18,6 +22,7 @@ import FloatingLinkEditorPlugin from './plugins/FloatingLinkEditorPlugin';
18
22
 
19
23
  export interface RichTextEditorProps {
20
24
  contentClassName?: string;
25
+ disabled?: boolean;
21
26
  displayMacrosButton?: boolean;
22
27
  editable: boolean;
23
28
  editorState?: string;
@@ -28,6 +33,7 @@ export interface RichTextEditorProps {
28
33
  minInputHeight?: number;
29
34
  namespace?: string;
30
35
  onBlur?: (e: string) => void;
36
+ openLinkInNewTab?: boolean;
31
37
  placeholder?: string;
32
38
  resetEditorToInitialStateCondition?: () => boolean;
33
39
  toolbarPositions?: 'start' | 'end';
@@ -94,7 +100,9 @@ const RichTextEditor = ({
94
100
  error,
95
101
  onBlur,
96
102
  contentClassName,
97
- displayMacrosButton = false
103
+ displayMacrosButton = false,
104
+ disabled = false,
105
+ openLinkInNewTab = true
98
106
  }: RichTextEditorProps): JSX.Element => {
99
107
  const { classes } = useStyles({ toolbarPositions });
100
108
 
@@ -106,7 +114,7 @@ const RichTextEditor = ({
106
114
  editable,
107
115
  editorState: initialEditorState,
108
116
  namespace,
109
- nodes: [AutoLinkNode, LinkNode],
117
+ nodes: [AutoLinkNode, LinkNode, HeadingNode, ListNode, ListItemNode],
110
118
  onError,
111
119
  theme: {
112
120
  link: classes.link,
@@ -125,9 +133,9 @@ const RichTextEditor = ({
125
133
  <div className={classes.container}>
126
134
  <div className={classes.toolbar}>
127
135
  <ToolbarPlugin
136
+ disabled={disabled}
128
137
  displayMacrosButton={displayMacrosButton}
129
138
  editable={editable}
130
- getEditorState={getEditorState}
131
139
  />
132
140
  </div>
133
141
  <div>
@@ -136,6 +144,7 @@ const RichTextEditor = ({
136
144
  contentEditable={
137
145
  <ContentEditable
138
146
  className={contentClassName || ''}
147
+ disabled={disabled}
139
148
  editable={editable}
140
149
  editorState={editorState}
141
150
  error={error}
@@ -155,8 +164,13 @@ const RichTextEditor = ({
155
164
  />
156
165
  <HistoryPlugin />
157
166
  <LinkPlugin />
158
- <AutoCompleteLinkPlugin />
159
- <FloatingLinkEditorPlugin editable={editable} />
167
+ <ListPlugin />
168
+ <OnChangePlugin onChange={getEditorState} />
169
+ <AutoCompleteLinkPlugin openLinkInNewTab={openLinkInNewTab} />
170
+ <FloatingLinkEditorPlugin
171
+ editable={editable}
172
+ openLinkInNewTab={openLinkInNewTab}
173
+ />
160
174
  {error && <Typography className={classes.error}>{error}</Typography>}
161
175
  </div>
162
176
  </div>
@@ -1,4 +1,7 @@
1
- import { AutoLinkPlugin } from '@lexical/react/LexicalAutoLinkPlugin';
1
+ import {
2
+ AutoLinkPlugin,
3
+ LinkMatcher
4
+ } from '@lexical/react/LexicalAutoLinkPlugin';
2
5
 
3
6
  interface LinkAttributes {
4
7
  rel?: null | string;
@@ -19,13 +22,15 @@ const URL_MATCHER =
19
22
  const EMAIL_MATCHER =
20
23
  /(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))/;
21
24
 
22
- const MATCHERS = [
25
+ const getMatchers = (openLinkInNewTab: boolean): Array<LinkMatcher> => [
23
26
  (text: string): LinkMatcherResult | null => {
24
27
  const match = URL_MATCHER.exec(text);
25
28
 
26
29
  return (
27
30
  match && {
28
- attributes: { rel: 'noreferrer', target: '_blank' },
31
+ attributes: openLinkInNewTab
32
+ ? { rel: 'noreferrer', target: '_blank' }
33
+ : undefined,
29
34
  index: match.index,
30
35
  length: match[0].length,
31
36
  text: match[0],
@@ -47,8 +52,12 @@ const MATCHERS = [
47
52
  }
48
53
  ];
49
54
 
50
- const AutoCompleteLinkPlugin = (): JSX.Element => {
51
- return <AutoLinkPlugin matchers={MATCHERS} />;
55
+ const AutoCompleteLinkPlugin = ({
56
+ openLinkInNewTab
57
+ }: {
58
+ openLinkInNewTab: boolean;
59
+ }): JSX.Element => {
60
+ return <AutoLinkPlugin matchers={getMatchers(openLinkInNewTab)} />;
52
61
  };
53
62
 
54
63
  export default AutoCompleteLinkPlugin;
@@ -28,6 +28,7 @@ import {
28
28
 
29
29
  interface FloatingLinkEditorPluginProps {
30
30
  editable: boolean;
31
+ openLinkInNewTab: boolean;
31
32
  }
32
33
 
33
34
  interface UseFloatingLinkEditorProps extends FloatingLinkEditorPluginProps {
@@ -36,6 +37,7 @@ interface UseFloatingLinkEditorProps extends FloatingLinkEditorPluginProps {
36
37
 
37
38
  interface FloatingLinkEditorProps {
38
39
  editor: LexicalEditor;
40
+ openLinkInNewTab: boolean;
39
41
  }
40
42
 
41
43
  interface TooltipPosition {
@@ -44,7 +46,8 @@ interface TooltipPosition {
44
46
  }
45
47
 
46
48
  const FloatingLinkEditor = ({
47
- editor
49
+ editor,
50
+ openLinkInNewTab
48
51
  }: FloatingLinkEditorProps): JSX.Element | null => {
49
52
  const nativeSelection = window.getSelection();
50
53
  const rootElement = editor.getRootElement();
@@ -67,7 +70,7 @@ const FloatingLinkEditor = ({
67
70
 
68
71
  if (value !== '') {
69
72
  editor.dispatchCommand(TOGGLE_LINK_COMMAND, {
70
- target: '_blank',
73
+ target: openLinkInNewTab ? '_blank' : undefined,
71
74
  url: value
72
75
  });
73
76
  }
@@ -144,7 +147,7 @@ const FloatingLinkEditor = ({
144
147
  aria-label={labelSavedLink}
145
148
  href={linkUrl}
146
149
  rel="noreferrer noopener"
147
- target="_blank"
150
+ target={openLinkInNewTab ? '_blank' : undefined}
148
151
  variant="button"
149
152
  >
150
153
  {linkUrl}
@@ -166,7 +169,8 @@ const FloatingLinkEditor = ({
166
169
 
167
170
  const useFloatingTextFormatToolbar = ({
168
171
  editor,
169
- editable
172
+ editable,
173
+ openLinkInNewTab
170
174
  }: UseFloatingLinkEditorProps): JSX.Element | null => {
171
175
  const [isText, setIsText] = useState(false);
172
176
  const isInsertingLink = useAtomValue(isInsertingLinkAtom);
@@ -241,15 +245,18 @@ const useFloatingTextFormatToolbar = ({
241
245
  return null;
242
246
  }
243
247
 
244
- return <FloatingLinkEditor editor={editor} />;
248
+ return (
249
+ <FloatingLinkEditor editor={editor} openLinkInNewTab={openLinkInNewTab} />
250
+ );
245
251
  };
246
252
 
247
253
  const FloatingActionsToolbarPlugin = ({
248
- editable
254
+ editable,
255
+ openLinkInNewTab
249
256
  }: FloatingLinkEditorPluginProps): JSX.Element | null => {
250
257
  const [editor] = useLexicalComposerContext();
251
258
 
252
- return useFloatingTextFormatToolbar({ editable, editor });
259
+ return useFloatingTextFormatToolbar({ editable, editor, openLinkInNewTab });
253
260
  };
254
261
 
255
262
  export default FloatingActionsToolbarPlugin;
@@ -0,0 +1,197 @@
1
+ import { useCallback, useEffect, useState } from 'react';
2
+
3
+ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
4
+ import {
5
+ $isListNode,
6
+ INSERT_ORDERED_LIST_COMMAND,
7
+ INSERT_UNORDERED_LIST_COMMAND,
8
+ ListNode,
9
+ REMOVE_LIST_COMMAND
10
+ } from '@lexical/list';
11
+ import {
12
+ $createParagraphNode,
13
+ $getSelection,
14
+ $isRangeSelection,
15
+ $isRootOrShadowRoot,
16
+ COMMAND_PRIORITY_CRITICAL,
17
+ SELECTION_CHANGE_COMMAND
18
+ } from 'lexical';
19
+ import {
20
+ $createHeadingNode,
21
+ $isHeadingNode,
22
+ HeadingTagType
23
+ } from '@lexical/rich-text';
24
+ import { $setBlocksType } from '@lexical/selection';
25
+ import { T, always, cond, equals, isNil } from 'ramda';
26
+ import {
27
+ $findMatchingParent,
28
+ $getNearestNodeOfType,
29
+ mergeRegister
30
+ } from '@lexical/utils';
31
+
32
+ import { SingleAutocompleteField } from '../../..';
33
+
34
+ import { useBlockButtonsStyles } from './ToolbarPlugin.styles';
35
+
36
+ interface Props {
37
+ disabled: boolean;
38
+ }
39
+
40
+ const blockTypeToBlockName = {
41
+ bullet: 'Bullet List',
42
+ h1: 'Heading 1',
43
+ h2: 'Heading 2',
44
+ h3: 'Heading 3',
45
+ h4: 'Heading 4',
46
+ h5: 'Heading 5',
47
+ h6: 'Heading 6',
48
+ number: 'Number List',
49
+ paragraph: 'Normal'
50
+ };
51
+
52
+ const blockTypes = [
53
+ 'h1',
54
+ 'h2',
55
+ 'h3',
56
+ 'h4',
57
+ 'h5',
58
+ 'h6',
59
+ 'bullet',
60
+ 'number',
61
+ 'paragraph'
62
+ ];
63
+
64
+ const blockTypeOptions = blockTypes.map((blockType) => ({
65
+ id: blockType,
66
+ name: blockTypeToBlockName[blockType]
67
+ }));
68
+
69
+ const BlockButtons = ({ disabled }: Props): JSX.Element => {
70
+ const { classes } = useBlockButtonsStyles();
71
+
72
+ const [blockType, setBlockType] =
73
+ useState<keyof typeof blockTypeToBlockName>('paragraph');
74
+
75
+ const [editor] = useLexicalComposerContext();
76
+
77
+ const formatParagraph = (): void => {
78
+ editor.update(() => {
79
+ const selection = $getSelection();
80
+ $setBlocksType(selection, () => $createParagraphNode());
81
+ });
82
+ };
83
+
84
+ const formatBulletList = (): void => {
85
+ if (blockType !== 'bullet') {
86
+ editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);
87
+ } else {
88
+ editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
89
+ }
90
+ };
91
+
92
+ const formatNumberedList = (): void => {
93
+ if (blockType !== 'number') {
94
+ editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);
95
+ } else {
96
+ editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined);
97
+ }
98
+ };
99
+
100
+ const formatHeading = (headingSize: HeadingTagType): void => {
101
+ if (blockType !== headingSize) {
102
+ editor.update(() => {
103
+ const selection = $getSelection();
104
+ if ($isRangeSelection(selection)) {
105
+ $setBlocksType(selection, () => $createHeadingNode(headingSize));
106
+ }
107
+ });
108
+ }
109
+ };
110
+
111
+ const changeBlockType = (_, newBlockType): void => {
112
+ const formatFunction = cond<Array<string>, (value?) => void>([
113
+ [equals('bullet'), always(formatBulletList)],
114
+ [equals('number'), always(formatNumberedList)],
115
+ [equals('h1'), always(() => formatHeading('h1'))],
116
+ [equals('h2'), always(() => formatHeading('h2'))],
117
+ [equals('h3'), always(() => formatHeading('h3'))],
118
+ [equals('h4'), always(() => formatHeading('h4'))],
119
+ [equals('h5'), always(() => formatHeading('h5'))],
120
+ [equals('h6'), always(() => formatHeading('h6'))],
121
+ [T, always(formatParagraph)]
122
+ ])(newBlockType?.id || '');
123
+
124
+ formatFunction();
125
+ };
126
+
127
+ const updateToolbar = useCallback(() => {
128
+ const selection = $getSelection();
129
+ const anchorNode = selection?.anchor.getNode();
130
+ const element = equals(anchorNode?.getKey(), 'root')
131
+ ? anchorNode
132
+ : $findMatchingParent(anchorNode, (e) => {
133
+ const parent = e.getParent();
134
+
135
+ return parent !== null && $isRootOrShadowRoot(parent);
136
+ }) || anchorNode?.getTopLevelElementOrThrow();
137
+
138
+ const elementKey = element?.getKey();
139
+ const elementDOM = editor.getElementByKey(elementKey);
140
+
141
+ if (isNil(elementDOM)) {
142
+ return;
143
+ }
144
+
145
+ if ($isListNode(element)) {
146
+ const parentList = $getNearestNodeOfType(anchorNode, ListNode);
147
+ const type = parentList
148
+ ? parentList.getListType()
149
+ : element.getListType();
150
+ setBlockType(type);
151
+
152
+ return;
153
+ }
154
+ const type = $isHeadingNode(element) ? element.getTag() : element.getType();
155
+ if (type in blockTypeToBlockName) {
156
+ setBlockType(type as keyof typeof blockTypeToBlockName);
157
+ }
158
+ }, [editor]);
159
+
160
+ const value = blockTypeOptions.find((option) => option.id === blockType);
161
+
162
+ useEffect(() => {
163
+ return editor.registerCommand(
164
+ SELECTION_CHANGE_COMMAND,
165
+ () => {
166
+ updateToolbar();
167
+
168
+ return false;
169
+ },
170
+ COMMAND_PRIORITY_CRITICAL
171
+ );
172
+ }, [editor, updateToolbar]);
173
+
174
+ useEffect(() => {
175
+ return mergeRegister(
176
+ editor.registerUpdateListener(({ editorState }) => {
177
+ editorState.read(() => {
178
+ updateToolbar();
179
+ });
180
+ })
181
+ );
182
+ }, [editor, updateToolbar]);
183
+
184
+ return (
185
+ <SingleAutocompleteField
186
+ className={classes.autocomplete}
187
+ dataTestId="Block type"
188
+ disabled={disabled}
189
+ label=""
190
+ options={blockTypeOptions}
191
+ value={value}
192
+ onChange={changeBlockType}
193
+ />
194
+ );
195
+ };
196
+
197
+ export default BlockButtons;
@@ -5,7 +5,6 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
5
5
  import {
6
6
  $getSelection,
7
7
  $isRangeSelection,
8
- EditorState,
9
8
  FORMAT_TEXT_COMMAND,
10
9
  RangeSelection,
11
10
  SELECTION_CHANGE_COMMAND,
@@ -61,10 +60,10 @@ const getSelectedNode = (selection: RangeSelection): ElementNode | TextNode => {
61
60
  };
62
61
 
63
62
  interface Props {
64
- getEditorState?: (editorState: EditorState) => void;
63
+ disabled: boolean;
65
64
  }
66
65
 
67
- const FormatButtons = ({ getEditorState }: Props): JSX.Element => {
66
+ const FormatButtons = ({ disabled }: Props): JSX.Element => {
68
67
  const { classes, cx } = useStyles();
69
68
 
70
69
  const [isBold, setIsBold] = useState(false);
@@ -104,13 +103,6 @@ const FormatButtons = ({ getEditorState }: Props): JSX.Element => {
104
103
 
105
104
  useEffect(() => {
106
105
  return mergeRegister(
107
- editor.registerUpdateListener(({ editorState }) => {
108
- editorState.read(() => {
109
- updateToolbar();
110
- });
111
-
112
- getEditorState?.(editorState);
113
- }),
114
106
  editor.registerCommand(
115
107
  SELECTION_CHANGE_COMMAND,
116
108
  () => {
@@ -169,6 +161,7 @@ const FormatButtons = ({ getEditorState }: Props): JSX.Element => {
169
161
  <IconButton
170
162
  ariaLabel={type}
171
163
  className={cx(isSelected && classes.buttonSelected)}
164
+ disabled={disabled}
172
165
  key={type}
173
166
  size="medium"
174
167
  title={type}
@@ -11,6 +11,10 @@ import { useStyles } from '.';
11
11
 
12
12
  const LowPriority = 1;
13
13
 
14
+ interface Props {
15
+ disabled: boolean;
16
+ }
17
+
14
18
  export const standardMacros = [
15
19
  '{{SHORTDATETIME}}',
16
20
  '{{LONGDATETIME}}',
@@ -22,7 +26,7 @@ export const standardMacros = [
22
26
  '{{STATETYPE}}'
23
27
  ];
24
28
 
25
- const MacrosButton = (): JSX.Element => {
29
+ const MacrosButton = ({ disabled }: Props): JSX.Element => {
26
30
  const { classes } = useStyles();
27
31
  const [arMacrosDiplayed, setAreMacrosDiplayed] = useState(false);
28
32
 
@@ -65,6 +69,7 @@ const MacrosButton = (): JSX.Element => {
65
69
  <IconButton
66
70
  ariaLabel="Macros"
67
71
  className={classes.macrosButton}
72
+ disabled={disabled}
68
73
  title="Macros"
69
74
  tooltipPlacement="top"
70
75
  onClick={displayMacros}
@@ -0,0 +1,7 @@
1
+ import { makeStyles } from 'tss-react/mui';
2
+
3
+ export const useBlockButtonsStyles = makeStyles()((theme) => ({
4
+ autocomplete: {
5
+ width: theme.spacing(17)
6
+ }
7
+ }));
@@ -18,7 +18,11 @@ import { labelRedo, labelUndo } from '../../translatedLabels';
18
18
 
19
19
  const LowPriority = 1;
20
20
 
21
- const UndoRedoButtons = (): JSX.Element => {
21
+ interface Props {
22
+ disabled: boolean;
23
+ }
24
+
25
+ const UndoRedoButtons = ({ disabled }: Props): JSX.Element => {
22
26
  const { t } = useTranslation();
23
27
 
24
28
  const [canUndo, setCanUndo] = useState(false);
@@ -64,7 +68,7 @@ const UndoRedoButtons = (): JSX.Element => {
64
68
  <>
65
69
  <IconButton
66
70
  ariaLabel={translatedUndo}
67
- disabled={!canUndo}
71
+ disabled={!canUndo || disabled}
68
72
  title={translatedUndo}
69
73
  tooltipPlacement="top"
70
74
  onClick={undo}
@@ -73,7 +77,7 @@ const UndoRedoButtons = (): JSX.Element => {
73
77
  </IconButton>
74
78
  <IconButton
75
79
  ariaLabel={translatedRedo}
76
- disabled={!canRedo}
80
+ disabled={!canRedo || disabled}
77
81
  title={translatedRedo}
78
82
  tooltipPlacement="top"
79
83
  onClick={redo}
@@ -1,4 +1,3 @@
1
- import { EditorState } from 'lexical';
2
1
  import { makeStyles } from 'tss-react/mui';
3
2
 
4
3
  import { Divider } from '@mui/material';
@@ -6,11 +5,12 @@ import { Divider } from '@mui/material';
6
5
  import FormatButtons from './FormatButtons';
7
6
  import UndoRedoButtons from './UndoRedoButtons';
8
7
  import MacrosButton from './MacrosButton';
8
+ import BlockButtons from './BlockButtons';
9
9
 
10
10
  interface Props {
11
+ disabled: boolean;
11
12
  displayMacrosButton?: boolean;
12
13
  editable: boolean;
13
- getEditorState?: (editorState: EditorState) => void;
14
14
  }
15
15
 
16
16
  export const useStyles = makeStyles()((theme) => ({
@@ -31,21 +31,23 @@ export const useStyles = makeStyles()((theme) => ({
31
31
  }));
32
32
 
33
33
  const ToolbarPlugin = ({
34
- getEditorState,
35
34
  editable,
36
- displayMacrosButton
35
+ displayMacrosButton,
36
+ disabled
37
37
  }: Props): JSX.Element | null => {
38
38
  const { classes } = useStyles();
39
39
 
40
40
  return editable ? (
41
41
  <div className={classes.container}>
42
- <UndoRedoButtons />
42
+ <UndoRedoButtons disabled={disabled} />
43
43
  <Divider flexItem orientation="vertical" />
44
- <FormatButtons getEditorState={getEditorState} />
44
+ <BlockButtons disabled={disabled} />
45
+ <Divider flexItem orientation="vertical" />
46
+ <FormatButtons disabled={disabled} />
45
47
  {displayMacrosButton && (
46
48
  <>
47
49
  <Divider flexItem orientation="vertical" />
48
- <MacrosButton />
50
+ <MacrosButton disabled={disabled} />
49
51
  </>
50
52
  )}
51
53
  </div>