@centreon/ui 23.10.51 → 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.
Files changed (43) hide show
  1. package/package.json +3 -1
  2. package/src/Dashboard/Layout.tsx +4 -2
  3. package/src/Graph/BasicComponents/Axes/index.tsx +1 -1
  4. package/src/Graph/BasicComponents/Lines/RegularLines/index.tsx +6 -3
  5. package/src/Graph/BasicComponents/Lines/StackedLines/index.tsx +6 -3
  6. package/src/Graph/BasicComponents/Lines/Threshold/BasicThreshold.tsx +5 -3
  7. package/src/Graph/BasicComponents/Lines/Threshold/ThresholdWithPatternLines.tsx +5 -1
  8. package/src/Graph/BasicComponents/Lines/Threshold/ThresholdWithVariation.tsx +5 -1
  9. package/src/Graph/BasicComponents/Lines/Threshold/index.tsx +5 -1
  10. package/src/Graph/BasicComponents/Lines/index.tsx +6 -1
  11. package/src/Graph/BasicComponents/Lines/models.ts +3 -0
  12. package/src/Graph/Graph.tsx +40 -20
  13. package/src/Graph/Header/index.tsx +7 -7
  14. package/src/Graph/InteractiveComponents/AnchorPoint/TooltipAnchorPoint.tsx +48 -22
  15. package/src/Graph/InteractiveComponents/AnchorPoint/index.tsx +14 -12
  16. package/src/Graph/InteractiveComponents/TimeShiftZones/TimeShiftZone.tsx +1 -1
  17. package/src/Graph/index.stories.tsx +10 -0
  18. package/src/Graph/index.tsx +35 -23
  19. package/src/Graph/models.ts +1 -1
  20. package/src/InputField/Select/Autocomplete/Connected/index.tsx +9 -1
  21. package/src/InputField/Select/Autocomplete/Multi/index.tsx +7 -2
  22. package/src/RichTextEditor/ContentEditable.tsx +12 -9
  23. package/src/RichTextEditor/RichTextEditor.cypress.spec.tsx +109 -1
  24. package/src/RichTextEditor/RichTextEditor.tsx +19 -5
  25. package/src/RichTextEditor/plugins/AutoLinkPlugin/index.tsx +14 -5
  26. package/src/RichTextEditor/plugins/FloatingLinkEditorPlugin.tsx +14 -7
  27. package/src/RichTextEditor/plugins/ToolbarPlugin/BlockButtons.tsx +197 -0
  28. package/src/RichTextEditor/plugins/ToolbarPlugin/FormatButtons.tsx +3 -10
  29. package/src/RichTextEditor/plugins/ToolbarPlugin/MacrosButton.tsx +6 -1
  30. package/src/RichTextEditor/plugins/ToolbarPlugin/ToolbarPlugin.styles.ts +7 -0
  31. package/src/RichTextEditor/plugins/ToolbarPlugin/UndoRedoButtons.tsx +7 -3
  32. package/src/RichTextEditor/plugins/ToolbarPlugin/index.tsx +9 -7
  33. package/src/api/useGraphQuery/index.ts +92 -0
  34. package/src/api/useGraphQuery/models.ts +12 -0
  35. package/src/components/ItemComposition/Item.tsx +37 -0
  36. package/src/components/ItemComposition/ItemComposition.stories.tsx +78 -0
  37. package/src/components/ItemComposition/ItemComposition.styles.ts +26 -0
  38. package/src/components/ItemComposition/ItemComposition.tsx +41 -0
  39. package/src/components/ItemComposition/index.ts +6 -0
  40. package/src/components/Modal/Modal.styles.ts +1 -1
  41. package/src/components/index.ts +1 -0
  42. package/src/index.ts +3 -0
  43. package/src/Graph/InteractiveComponents/AnchorPoint/AnchorPoint.styles.ts +0 -58
@@ -1,6 +1,6 @@
1
1
  import { MutableRefObject, useRef } from 'react';
2
2
 
3
- import { Responsive } from '@visx/visx';
3
+ import { Curve, Responsive } from '@visx/visx';
4
4
  import dayjs from 'dayjs';
5
5
  import 'dayjs/locale/en';
6
6
  import 'dayjs/locale/es';
@@ -14,16 +14,19 @@ import Graph from './Graph';
14
14
  import LoadingSkeleton from './LoadingSkeleton';
15
15
  import { GlobalAreaLines, GraphData, GraphProps, LegendModel } from './models';
16
16
  import useGraphData from './useGraphData';
17
+ import { CurveType } from './BasicComponents/Lines/models';
17
18
 
18
19
  dayjs.extend(localizedFormat);
19
20
  dayjs.extend(utcPlugin);
20
21
  dayjs.extend(timezonePlugin);
21
22
 
22
23
  interface Props extends Partial<GraphProps> {
24
+ curve?: CurveType;
23
25
  data?: GraphData;
24
26
  end: string;
25
27
  legend: LegendModel;
26
28
  loading: boolean;
29
+ marginBottom?: number;
27
30
  shapeLines?: GlobalAreaLines;
28
31
  start: string;
29
32
  }
@@ -43,7 +46,9 @@ const WrapperGraph = ({
43
46
  tooltip,
44
47
  annotationEvent,
45
48
  legend,
46
- header
49
+ header,
50
+ curve = Curve.curveLinear,
51
+ marginBottom = 0
47
52
  }: Props): JSX.Element | null => {
48
53
  const { adjustedData } = useGraphData({ data, end, start });
49
54
  const graphRef = useRef<HTMLDivElement | null>(null);
@@ -52,36 +57,43 @@ const WrapperGraph = ({
52
57
  return (
53
58
  <LoadingSkeleton
54
59
  displayTitleSkeleton={header?.displayTitle ?? true}
55
- graphHeight={height}
60
+ graphHeight={height || 200}
56
61
  />
57
62
  );
58
63
  }
59
64
 
60
65
  return (
61
- <div ref={graphRef as MutableRefObject<HTMLDivElement>}>
66
+ <div
67
+ ref={graphRef as MutableRefObject<HTMLDivElement>}
68
+ style={{ height: '100%', width: '100%' }}
69
+ >
62
70
  <Responsive.ParentSize>
63
71
  {({
64
72
  height: responsiveHeight,
65
73
  width: responsiveWidth
66
- }): JSX.Element => (
67
- <Graph
68
- annotationEvent={annotationEvent}
69
- axis={axis}
70
- displayAnchor={displayAnchor}
71
- graphData={adjustedData}
72
- graphInterval={{ end, start }}
73
- graphRef={graphRef}
74
- header={header}
75
- height={height ?? responsiveHeight}
76
- legend={legend}
77
- loading={loading}
78
- shapeLines={shapeLines}
79
- timeShiftZones={timeShiftZones}
80
- tooltip={tooltip}
81
- width={width ?? responsiveWidth}
82
- zoomPreview={zoomPreview}
83
- />
84
- )}
74
+ }): JSX.Element => {
75
+ return (
76
+ <Graph
77
+ annotationEvent={annotationEvent}
78
+ axis={axis}
79
+ curve={curve}
80
+ displayAnchor={displayAnchor}
81
+ graphData={adjustedData}
82
+ graphInterval={{ end, start }}
83
+ graphRef={graphRef}
84
+ header={header}
85
+ height={height || responsiveHeight}
86
+ legend={legend}
87
+ loading={loading}
88
+ marginBottom={marginBottom}
89
+ shapeLines={shapeLines}
90
+ timeShiftZones={timeShiftZones}
91
+ tooltip={tooltip}
92
+ width={width ?? responsiveWidth}
93
+ zoomPreview={zoomPreview}
94
+ />
95
+ );
96
+ }}
85
97
  </Responsive.ParentSize>
86
98
  </div>
87
99
  );
@@ -91,7 +91,7 @@ export interface GraphProps {
91
91
  axis?: Axis;
92
92
  displayAnchor?: DisplayAnchor;
93
93
  header?: GraphHeader;
94
- height?: number;
94
+ height?: number | null;
95
95
  loading: boolean;
96
96
  timeShiftZones?: InteractedZone;
97
97
  tooltip?: Tooltip;
@@ -37,6 +37,7 @@ export interface ConnectedAutoCompleteFieldProps<TData> {
37
37
  getRequestHeaders?: HeadersInit;
38
38
  initialPage: number;
39
39
  labelKey?: string;
40
+ queryKey?: string;
40
41
  searchConditions?: Array<ConditionsSearchParameter>;
41
42
  }
42
43
 
@@ -55,6 +56,7 @@ const ConnectedAutocompleteField = (
55
56
  getRenderedOptionText = (option): string => option.name?.toString(),
56
57
  getRequestHeaders,
57
58
  displayOptionThumbnail,
59
+ queryKey,
58
60
  ...props
59
61
  }: ConnectedAutoCompleteFieldProps<TData> &
60
62
  Omit<AutocompleteFieldProps, 'options'>): JSX.Element => {
@@ -89,10 +91,16 @@ const ConnectedAutocompleteField = (
89
91
  search: searchParameter
90
92
  });
91
93
  },
92
- getQueryKey: () => [`autocomplete-${props.label}`, page, searchParameter],
94
+ getQueryKey: () => [
95
+ `autocomplete-${queryKey || props.label}`,
96
+ page,
97
+ searchParameter
98
+ ],
93
99
  isPaginated: true,
94
100
  queryOptions: {
101
+ cacheTime: 0,
95
102
  enabled: false,
103
+ staleTime: 0,
96
104
  suspense: false
97
105
  }
98
106
  });
@@ -34,12 +34,17 @@ export interface Props
34
34
  'multiple'
35
35
  > {
36
36
  disableSortedOptions?: boolean;
37
+ getTagLabel?: (option) => string;
38
+ optionProperty?: string;
37
39
  }
38
40
 
39
41
  const MultiAutocompleteField = ({
40
42
  value,
41
43
  options,
42
44
  disableSortedOptions = false,
45
+ optionProperty = 'name',
46
+ getOptionLabel = (option): string => option.name,
47
+ getTagLabel = (option): string => option[optionProperty],
43
48
  ...props
44
49
  }: Props): JSX.Element => {
45
50
  const { classes } = useStyles();
@@ -52,7 +57,7 @@ const MultiAutocompleteField = ({
52
57
  root: classes.tag
53
58
  }}
54
59
  key={option.id}
55
- label={option.name}
60
+ label={getTagLabel(option)}
56
61
  size="medium"
57
62
  {...getTagProps({ index })}
58
63
  />
@@ -84,7 +89,7 @@ const MultiAutocompleteField = ({
84
89
  key={option.id}
85
90
  {...(renderProps as React.HTMLAttributes<HTMLLIElement>)}
86
91
  >
87
- <Option checkboxSelected={selected}>{option.name}</Option>
92
+ <Option checkboxSelected={selected}>{getOptionLabel(option)}</Option>
88
93
  </li>
89
94
  )}
90
95
  renderTags={renderTags}
@@ -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;