@rh-support/components 2.5.230 → 2.5.232

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 (71) hide show
  1. package/lib/esm/ConfirmationModal/ConfirmationDialog.js +1 -1
  2. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts +0 -2
  3. package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts.map +1 -1
  4. package/lib/esm/Functional/CaseContactsSelectorExternal.js +82 -176
  5. package/lib/esm/InlineEdit/NewInlineEdit.d.ts +0 -2
  6. package/lib/esm/InlineEdit/NewInlineEdit.d.ts.map +1 -1
  7. package/lib/esm/InlineEdit/NewInlineEdit.js +2 -7
  8. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts +49 -0
  9. package/lib/esm/MarkdownEditor/MarkdownEditor.d.ts.map +1 -0
  10. package/lib/esm/MarkdownEditor/MarkdownEditor.js +601 -0
  11. package/lib/esm/MarkdownEditor/index.d.ts +2 -0
  12. package/lib/esm/MarkdownEditor/index.d.ts.map +1 -0
  13. package/lib/esm/MarkdownEditor/index.js +1 -0
  14. package/lib/esm/MarkdownEditor/markdownEditor.css +223 -0
  15. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts +0 -2
  16. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.d.ts.map +1 -1
  17. package/lib/esm/OwnerTypeaheadDropdown/OwnerTypeaheadDropdown.js +3 -8
  18. package/lib/esm/PhoneInput/PhoneInput.d.ts.map +1 -1
  19. package/lib/esm/PhoneInput/PhoneInput.js +1 -38
  20. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts +0 -2
  21. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.d.ts.map +1 -1
  22. package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.js +6 -7
  23. package/lib/esm/SupportFeedbackForm/SupportFeedbackModal.d.ts.map +1 -1
  24. package/lib/esm/SupportFeedbackForm/SupportFeedbackModal.js +80 -62
  25. package/lib/esm/Table/TablePagination.d.ts +0 -1
  26. package/lib/esm/Table/TablePagination.d.ts.map +1 -1
  27. package/lib/esm/Table/TablePagination.js +1 -1
  28. package/lib/esm/ToastNotification/ToastNotification.d.ts +1 -5
  29. package/lib/esm/ToastNotification/ToastNotification.d.ts.map +1 -1
  30. package/lib/esm/ToastNotification/ToastNotification.js +3 -16
  31. package/lib/esm/ToastNotification/toastNotification.css +0 -7
  32. package/lib/esm/helperFunctions/errorMessage.d.ts +1 -2
  33. package/lib/esm/helperFunctions/errorMessage.d.ts.map +1 -1
  34. package/lib/esm/helperFunctions/errorMessage.js +5 -19
  35. package/lib/esm/hooks/useCopyToClipboard.d.ts +1 -1
  36. package/lib/esm/hooks/useCopyToClipboard.d.ts.map +1 -1
  37. package/lib/esm/hooks/useCopyToClipboard.js +1 -3
  38. package/lib/esm/index.d.ts +1 -3
  39. package/lib/esm/index.d.ts.map +1 -1
  40. package/lib/esm/index.js +1 -4
  41. package/package.json +6 -9
  42. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.d.ts +0 -10
  43. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.d.ts.map +0 -1
  44. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.js +0 -17
  45. package/lib/esm/CaseLabelLoadErrorModal/CaseLabelLoadErrorModal.scss +0 -13
  46. package/lib/esm/CaseLabelLoadErrorModal/index.d.ts +0 -2
  47. package/lib/esm/CaseLabelLoadErrorModal/index.d.ts.map +0 -1
  48. package/lib/esm/CaseLabelLoadErrorModal/index.js +0 -1
  49. package/lib/esm/CustomDatePicker/CustomDatePicker.d.ts +0 -18
  50. package/lib/esm/CustomDatePicker/CustomDatePicker.d.ts.map +0 -1
  51. package/lib/esm/CustomDatePicker/CustomDatePicker.js +0 -27
  52. package/lib/esm/CustomDatePicker/customDatePicker.css +0 -52
  53. package/lib/esm/CustomDatePicker/index.d.ts +0 -2
  54. package/lib/esm/CustomDatePicker/index.d.ts.map +0 -1
  55. package/lib/esm/CustomDatePicker/index.js +0 -1
  56. package/lib/esm/RichTextEditor/RichTextEditor.d.ts +0 -45
  57. package/lib/esm/RichTextEditor/RichTextEditor.d.ts.map +0 -1
  58. package/lib/esm/RichTextEditor/RichTextEditor.js +0 -464
  59. package/lib/esm/RichTextEditor/autoLinkModule.d.ts +0 -18
  60. package/lib/esm/RichTextEditor/autoLinkModule.d.ts.map +0 -1
  61. package/lib/esm/RichTextEditor/autoLinkModule.js +0 -156
  62. package/lib/esm/RichTextEditor/index.d.ts +0 -4
  63. package/lib/esm/RichTextEditor/index.d.ts.map +0 -1
  64. package/lib/esm/RichTextEditor/index.js +0 -2
  65. package/lib/esm/RichTextEditor/linkUrl.d.ts +0 -7
  66. package/lib/esm/RichTextEditor/linkUrl.d.ts.map +0 -1
  67. package/lib/esm/RichTextEditor/linkUrl.js +0 -26
  68. package/lib/esm/RichTextEditor/richTextEditor.css +0 -182
  69. package/lib/esm/RichTextEditor/useQuillEditor.d.ts +0 -20
  70. package/lib/esm/RichTextEditor/useQuillEditor.d.ts.map +0 -1
  71. package/lib/esm/RichTextEditor/useQuillEditor.js +0 -180
@@ -0,0 +1,601 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import './markdownEditor.css';
13
+ import { parseCommentMarkdown } from '@cee-eng/ui-toolkit';
14
+ import { Button, ButtonVariant, Content, ContentVariants, Divider, DrilldownMenu, Dropdown, DropdownItem, DropdownList, Menu, MenuContainer, MenuContent, MenuItem, MenuList, MenuToggle, Spinner, Toolbar, ToolbarGroup, ToolbarItem, } from '@patternfly/react-core';
15
+ import CaretDownIcon from '@patternfly/react-icons/dist/js/icons/caret-down-icon';
16
+ import CodeIcon from '@patternfly/react-icons/dist/js/icons/code-icon';
17
+ import EllipsisVIcon from '@patternfly/react-icons/dist/js/icons/ellipsis-v-icon';
18
+ import ImageIcon from '@patternfly/react-icons/dist/js/icons/image-icon';
19
+ import LinkIcon from '@patternfly/react-icons/dist/js/icons/link-icon';
20
+ import ListUlIcon from '@patternfly/react-icons/dist/js/icons/list-ul-icon';
21
+ import QuoteRightIcon from '@patternfly/react-icons/dist/js/icons/quote-right-icon';
22
+ import { emojiPattern, haltEvent, isEmojiPattern, isImageFile } from '@rh-support/utils';
23
+ import cx from 'classnames';
24
+ import DOMPurify from 'dompurify';
25
+ import isEmpty from 'lodash/isEmpty';
26
+ import React, { useEffect, useRef, useState } from 'react';
27
+ import { Trans, useTranslation } from 'react-i18next';
28
+ import { useDebounce, usePrevious, useUndo } from '../hooks';
29
+ import { ToastNotification } from '../index';
30
+ export var EditorMode;
31
+ (function (EditorMode) {
32
+ EditorMode["PLAIN"] = "plaintext";
33
+ EditorMode["MARKDOWN"] = "markdown";
34
+ })(EditorMode || (EditorMode = {}));
35
+ const defaultProps = {
36
+ value: '',
37
+ onChange: () => { },
38
+ id: '',
39
+ className: '',
40
+ hidePreviewText: 'Hide preview',
41
+ showPreviewText: 'Show preview',
42
+ textAreaClassName: '',
43
+ mdPlaceholder: 'Add a comment using markdown',
44
+ plainTextPlaceholder: 'Add a comment to this case',
45
+ allowEmoji: true,
46
+ hideHeadingOptions: false,
47
+ showMarkdownPlainTextToggle: false,
48
+ editorMode: EditorMode.PLAIN,
49
+ charLimit: 32000,
50
+ minCharsForCounter: 31800,
51
+ fileSelectorProps: {
52
+ showFileSelectorInToolbar: false,
53
+ },
54
+ };
55
+ const MAX_FILE_SIZE = 2 * 1024; // 2MB in KB
56
+ const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png', 'image/gif'];
57
+ export const validateFile = (file) => {
58
+ // Check file type
59
+ if (!ALLOWED_FILE_TYPES.includes(file.fileType)) {
60
+ // ToastNotification.addDangerMessage(t('Invalid file type, Only JPEG, PNG, GIF files are allowed.'));
61
+ return {
62
+ isValid: false,
63
+ errorMsg: 'Invalid file type, Only JPEG, PNG, GIF files are allowed inside Markdown Editor',
64
+ };
65
+ }
66
+ // Check file size
67
+ if (file.sizeKB > MAX_FILE_SIZE) {
68
+ // ToastNotification.addDangerMessage(t('File too large, File size must be less than 2MB'));
69
+ return { isValid: false, errorMsg: 'File too large, File size must be less than 2MB inside Markdown Editor' };
70
+ }
71
+ return { isValid: true, errorMsg: '' };
72
+ };
73
+ function MarkdownEditor(props) {
74
+ const { t } = useTranslation();
75
+ const { hidePreviewText, showPreviewText, mdPlaceholder, plainTextPlaceholder, textAreaClassName, bindTextArea, markedownOptions, allowEmoji, hideHeadingOptions, rows, showMarkdownPlainTextToggle, editorMode, className, charLimit, minCharsForCounter, id, value, onCommentExceedCharsLimit, fileSelectorProps: { onClipboardPaste, onFileSelect, isSupportedFile, showFileSelectorInToolbar = false, isUploadingFile = false, filesList = [], attachFileBtn, } } = props, textAreaProps = __rest(props, ["hidePreviewText", "showPreviewText", "mdPlaceholder", "plainTextPlaceholder", "textAreaClassName", "bindTextArea", "markedownOptions", "allowEmoji", "hideHeadingOptions", "rows", "showMarkdownPlainTextToggle", "editorMode", "className", "charLimit", "minCharsForCounter", "id", "value", "onCommentExceedCharsLimit", "fileSelectorProps"]);
76
+ const [isPreview, setIsPreview] = useState(false);
77
+ const [mdValue, setMdValue] = useState(props.value || '');
78
+ const previousFileList = usePrevious(filesList);
79
+ const [isPlainModeEnabled, setIsPlainModeEnabled] = useState(showMarkdownPlainTextToggle && props.editorMode === EditorMode.PLAIN ? true : false);
80
+ const textareaRef = useRef(null);
81
+ const { state, setURValue, undo, redo, resetURValue } = useUndo(props.value);
82
+ // for access inside event handlers
83
+ const isPlainModeEnabledRef = useRef(isPlainModeEnabled);
84
+ const [isFileSelectorOpen, setIsFileSelectorOpen] = useState(false);
85
+ const [isFileFromOnPaste, setIsFileFromOnPaste] = useState(false);
86
+ const onFileSelectLocal = () => {
87
+ setIsFileSelectorOpen((open) => !open);
88
+ };
89
+ const onFileSelectToggle = (isOpen) => {
90
+ setIsFileSelectorOpen(isOpen);
91
+ };
92
+ useEffect(() => {
93
+ if (isFileFromOnPaste) {
94
+ onFileInsert(filesList[0]);
95
+ }
96
+ if ((previousFileList || []).length < (filesList || []).length && isUploadingFile) {
97
+ onFileInsert(filesList[0]);
98
+ }
99
+ return () => {
100
+ setIsFileFromOnPaste(false);
101
+ };
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [filesList.length]);
104
+ const onFileInsert = (file) => {
105
+ const { isValid, errorMsg } = validateFile(file);
106
+ if (!isValid) {
107
+ ToastNotification.addDangerMessage(t(errorMsg));
108
+ return;
109
+ }
110
+ // Create markdown image syntax
111
+ const template = isImageFile(file.fileType)
112
+ ? `![${file.fileName}](${file.link})`
113
+ : `[${file.fileName}](${file.link})`;
114
+ insert(template, 0, 0);
115
+ const currentStart = textareaRef.current.selectionStart;
116
+ textareaRef.current.setSelectionRange(currentStart + template.length, currentStart + template.length);
117
+ onFileSelect && onFileSelect(file);
118
+ };
119
+ function pastedFile(items) {
120
+ for (const item of items) {
121
+ if (isSupportedFile && isSupportedFile(item.type)) {
122
+ return item.getAsFile();
123
+ }
124
+ }
125
+ return null;
126
+ }
127
+ const onPaste = (event) => {
128
+ if (!event.clipboardData || isPlainModeEnabledRef.current)
129
+ return;
130
+ if (!event.clipboardData.items)
131
+ return;
132
+ const file = pastedFile(event.clipboardData.items);
133
+ if (!file)
134
+ return;
135
+ const files = [file];
136
+ event.preventDefault();
137
+ onClipboardPaste(files);
138
+ setIsFileFromOnPaste(true);
139
+ };
140
+ // To handle dropped images
141
+ const onDrop = (event) => {
142
+ if (!event.dataTransfer.files)
143
+ return;
144
+ const file = event.dataTransfer.files[0];
145
+ if (!file)
146
+ return;
147
+ const files = [file];
148
+ event.preventDefault();
149
+ onClipboardPaste(files);
150
+ setIsFileFromOnPaste(true);
151
+ };
152
+ const getFileSelectorList = (isMobile = false) => {
153
+ const filesAsDropdownItems = filesList
154
+ .filter((file) => validateFile(file).isValid)
155
+ .map((file) => (React.createElement(DropdownItem, { key: file.id, value: file, onClick: () => {
156
+ onFileInsert(file);
157
+ } },
158
+ React.createElement(Content, { component: ContentVariants.small }, file.fileName))));
159
+ if (isMobile) {
160
+ return [
161
+ React.createElement(DropdownItem, { key: "separated link", className: "pf-v6-u-p-0 pf-v6-u-m-0 pf-v6-u-w-100" }, attachFileBtn),
162
+ ...(filesAsDropdownItems.length > 0 ? [React.createElement(Divider, { key: "separator" })] : []),
163
+ ...filesAsDropdownItems,
164
+ ];
165
+ }
166
+ else {
167
+ return [
168
+ ...filesAsDropdownItems,
169
+ ...(filesAsDropdownItems.length > 0 ? [React.createElement(Divider, { key: "separator" })] : []),
170
+ React.createElement(DropdownItem, { key: "separated link", className: "pf-v6-u-p-0 pf-v6-u-m-0 pf-v6-u-w-100" }, attachFileBtn),
171
+ ];
172
+ }
173
+ };
174
+ const bindTextAreaRef = (ref) => {
175
+ textareaRef.current = ref;
176
+ if (props.bindTextArea)
177
+ props.bindTextArea(ref);
178
+ };
179
+ const onModeChange = (isPlainMode) => {
180
+ setIsPlainModeEnabled(isPlainMode);
181
+ isPlainModeEnabledRef.current = isPlainMode;
182
+ isPlainMode && isPreview && setIsPreview(false);
183
+ const mode = isPlainMode ? EditorMode.PLAIN : EditorMode.MARKDOWN;
184
+ props.onChange(mdValue, mode);
185
+ };
186
+ // Add this useEffect to properly initialize the mdValue when component mounts
187
+ useEffect(() => {
188
+ setMdValue(props.value || '');
189
+ }, [props.value]);
190
+ // Update the existing useEffect to handle both value and editorMode changes
191
+ useEffect(() => {
192
+ if (value !== mdValue) {
193
+ setMdValue(value);
194
+ }
195
+ // eslint-disable-next-line react-hooks/exhaustive-deps
196
+ }, [value]);
197
+ useEffect(() => {
198
+ const isPlainMode = props.editorMode === EditorMode.PLAIN;
199
+ if (isPlainMode !== isPlainModeEnabled) {
200
+ setIsPlainModeEnabled(isPlainMode);
201
+ isPlainModeEnabledRef.current = isPlainMode;
202
+ if (isPlainMode && isPreview) {
203
+ setIsPreview(false);
204
+ }
205
+ }
206
+ // eslint-disable-next-line react-hooks/exhaustive-deps
207
+ }, [props.editorMode]);
208
+ useEffect(() => {
209
+ // update state on undo redo
210
+ if (mdValue !== state.present) {
211
+ onValueChangeLocal(state.present);
212
+ }
213
+ // eslint-disable-next-line react-hooks/exhaustive-deps
214
+ }, [state.present]);
215
+ useDebounce(() => {
216
+ if (isEmpty(mdValue))
217
+ return;
218
+ !isPlainModeEnabled && setURValue(mdValue);
219
+ }, [mdValue], 500);
220
+ const onValueChangeLocal = (text) => {
221
+ const mode = showMarkdownPlainTextToggle && isPlainModeEnabled ? EditorMode.PLAIN : EditorMode.MARKDOWN;
222
+ setMdValue(text);
223
+ props.onChange(text, mode);
224
+ };
225
+ const textChanged = (event) => {
226
+ let value = event.target.value;
227
+ if (!props.allowEmoji && isEmojiPattern(value)) {
228
+ value = value.replace(emojiPattern, '').trim();
229
+ }
230
+ onValueChangeLocal(value);
231
+ // Add a null check before calling onCommentExceedCharsLimit
232
+ if (props.onCommentExceedCharsLimit) {
233
+ charLimit - value.length < 0
234
+ ? props.onCommentExceedCharsLimit(true)
235
+ : props.onCommentExceedCharsLimit(false);
236
+ }
237
+ };
238
+ const hasSelection = () => {
239
+ return textareaRef.current.selectionStart !== textareaRef.current.selectionEnd;
240
+ };
241
+ const wrapSelection = (templateStart, templateEnd) => {
242
+ const currentText = textareaRef.current.value;
243
+ const posStart = textareaRef.current.selectionStart;
244
+ const posEnd = textareaRef.current.selectionEnd;
245
+ const selectedText = currentText.substring(posStart, posEnd);
246
+ const whitespaceEdges = selectedText.match(/^\s*|\s*$/g) || [];
247
+ const leadingWhitespace = whitespaceEdges[0] || '';
248
+ const trailingWhitespace = whitespaceEdges[1] || '';
249
+ const trimmedText = selectedText.trim();
250
+ let coreText = trimmedText;
251
+ // Check existing formatting
252
+ const existingFormats = {
253
+ h1: coreText.startsWith('# '),
254
+ h2: coreText.startsWith('## '),
255
+ h3: coreText.startsWith('### '),
256
+ bold: coreText.startsWith('**') && coreText.endsWith('**'),
257
+ italic: coreText.startsWith('_') && coreText.endsWith('_'),
258
+ quote: coreText.startsWith('> '),
259
+ link: coreText.match(/^\[.*\]\(.*\)$/),
260
+ };
261
+ // Remove existing formatting to get core text
262
+ if (existingFormats.h1)
263
+ coreText = coreText.substring(2);
264
+ else if (existingFormats.h2)
265
+ coreText = coreText.substring(3);
266
+ else if (existingFormats.h3)
267
+ coreText = coreText.substring(4);
268
+ if (existingFormats.bold)
269
+ coreText = coreText.substring(2, coreText.length - 2);
270
+ if (existingFormats.italic)
271
+ coreText = coreText.substring(1, coreText.length - 1);
272
+ if (existingFormats.quote)
273
+ coreText = coreText.substring(2);
274
+ if (existingFormats.link) {
275
+ const match = coreText.match(/^\[(.*)\]\((.*)\)$/);
276
+ if (match)
277
+ coreText = match[1];
278
+ }
279
+ // Determine what formatting we're adding
280
+ const newFormat = {
281
+ h1: templateStart === '# ' && templateEnd === '',
282
+ h2: templateStart === '## ' && templateEnd === '',
283
+ h3: templateStart === '### ' && templateEnd === '',
284
+ bold: templateStart === '**' && templateEnd === '**',
285
+ italic: templateStart === '_' && templateEnd === '_',
286
+ quote: templateStart === '> ' && (templateEnd === '' || templateEnd === '\n'),
287
+ link: templateEnd.includes(']('),
288
+ };
289
+ // Apply formatting
290
+ let formattedText = coreText;
291
+ // First apply inline formatting (bold, italic)
292
+ if (newFormat.italic && !existingFormats.italic) {
293
+ formattedText = `_${formattedText}_`;
294
+ }
295
+ if (existingFormats.italic && !newFormat.italic) {
296
+ formattedText = `_${formattedText}_`;
297
+ }
298
+ if (newFormat.bold && !existingFormats.bold) {
299
+ formattedText = `**${formattedText}**`;
300
+ }
301
+ if (existingFormats.bold && !newFormat.bold) {
302
+ formattedText = `**${formattedText}**`;
303
+ }
304
+ // Then apply block-level formatting (headings, quotes)
305
+ if (newFormat.h1 || existingFormats.h1) {
306
+ formattedText = `# ${formattedText}`;
307
+ }
308
+ else if (newFormat.h2 || existingFormats.h2) {
309
+ formattedText = `## ${formattedText}`;
310
+ }
311
+ else if (newFormat.h3 || existingFormats.h3) {
312
+ formattedText = `### ${formattedText}`;
313
+ }
314
+ // Handle quotes - keep them at the start of the line
315
+ if (newFormat.quote || existingFormats.quote) {
316
+ formattedText = `> ${formattedText}`;
317
+ }
318
+ // Special handling for links
319
+ if (newFormat.link) {
320
+ formattedText = `[${formattedText}](URL "TITLE")`;
321
+ }
322
+ if (existingFormats.link && !newFormat.link) {
323
+ const match = trimmedText.match(/^\[(.*)\]\((.*)\)$/);
324
+ if (match)
325
+ formattedText = `[${formattedText}](${match[2]})`;
326
+ }
327
+ const newText = currentText.substring(0, posStart) +
328
+ leadingWhitespace +
329
+ formattedText +
330
+ trailingWhitespace +
331
+ currentText.substring(posEnd);
332
+ // Set the value in the textarea
333
+ textareaRef.current.value = newText;
334
+ textareaRef.current.selectionStart = posStart + leadingWhitespace.length;
335
+ textareaRef.current.selectionEnd = posStart + leadingWhitespace.length + formattedText.length;
336
+ textareaRef.current.focus();
337
+ // Call onValueChangeLocal only once
338
+ onValueChangeLocal(newText);
339
+ };
340
+ const wrapBasedOnNewLines = () => {
341
+ const currentText = textareaRef.current.value;
342
+ const posStart = textareaRef.current.selectionStart;
343
+ const posEnd = textareaRef.current.selectionEnd;
344
+ const selectedText = currentText.substring(posStart, posEnd);
345
+ const splitText = selectedText.split('\n');
346
+ const newText = currentText
347
+ .substring(0, posStart)
348
+ .concat(splitText.map((v) => `- ${v}`).join('\n'), '\n', currentText.substring(posEnd));
349
+ onValueChangeLocal(newText);
350
+ };
351
+ const insert = (template, selectionStart, selectionEnd) => {
352
+ const currentText = textareaRef.current.value;
353
+ const pos = textareaRef.current.selectionStart;
354
+ const newText = currentText.substring(0, pos) + template + currentText.substring(pos);
355
+ onValueChangeLocal(newText);
356
+ textareaRef.current.value = newText;
357
+ textareaRef.current.selectionStart = pos + selectionStart;
358
+ textareaRef.current.selectionEnd = pos + (selectionEnd || selectionStart);
359
+ textareaRef.current.focus();
360
+ onValueChangeLocal(newText);
361
+ };
362
+ const bold = () => {
363
+ if (hasSelection()) {
364
+ wrapSelection('**', '**');
365
+ }
366
+ else {
367
+ insert('**TEXT**', 2, 6);
368
+ }
369
+ };
370
+ const italic = () => {
371
+ if (hasSelection()) {
372
+ wrapSelection('_', '_');
373
+ }
374
+ else {
375
+ insert('_TEXT_', 1, 5);
376
+ }
377
+ };
378
+ const h1 = () => {
379
+ if (hasSelection()) {
380
+ wrapSelection('# ', '');
381
+ }
382
+ else {
383
+ insert('# HEADING', 2, 9);
384
+ }
385
+ };
386
+ const h2 = () => {
387
+ if (hasSelection()) {
388
+ wrapSelection('## ', '');
389
+ }
390
+ else {
391
+ insert('## HEADING', 3, 10);
392
+ }
393
+ };
394
+ const h3 = () => {
395
+ if (hasSelection()) {
396
+ wrapSelection('### ', '');
397
+ }
398
+ else {
399
+ insert('### HEADING', 4, 11);
400
+ }
401
+ };
402
+ const list = () => {
403
+ if (hasSelection()) {
404
+ wrapBasedOnNewLines();
405
+ }
406
+ else {
407
+ insert('- ITEM', 2, 6);
408
+ }
409
+ };
410
+ const link = () => {
411
+ if (hasSelection()) {
412
+ wrapSelection('[', '](URL "TITLE")');
413
+ }
414
+ else {
415
+ insert('[LINK TEXT](URL "TITLE")', 1, 10);
416
+ }
417
+ };
418
+ const quote = () => {
419
+ if (hasSelection()) {
420
+ wrapSelection('> ', '');
421
+ }
422
+ else {
423
+ insert('> QUOTE', 2, 7);
424
+ }
425
+ };
426
+ const code = () => {
427
+ if (hasSelection()) {
428
+ wrapSelection('~~~\n', '\n~~~\n');
429
+ }
430
+ else {
431
+ insert('~~~\nCODE\n~~~\n', 4, 8);
432
+ }
433
+ };
434
+ const preview = () => {
435
+ setIsPreview((previewFlag) => !previewFlag);
436
+ };
437
+ const previewMDText = (mdText) => {
438
+ // Preprocess to insert a blank line between list and non-list lines
439
+ const fixedMdText = mdText.replace(/(^|\n)(-\s.+)(\n(?![-\s]))/g, '$1$2\n\n');
440
+ const markeddownOptions = Object.assign(Object.assign({}, props.markedownOptions), { openLinksInNewTab: true, gfm: true, breaks: true });
441
+ return parseCommentMarkdown(fixedMdText, { openImagesInNewTab: false, lazyLoadImages: false }, markeddownOptions);
442
+ };
443
+ const keydown = (event) => {
444
+ if (event.ctrlKey) {
445
+ switch (event.key) {
446
+ case 'b':
447
+ bold();
448
+ haltEvent(event);
449
+ break;
450
+ case 'i':
451
+ italic();
452
+ haltEvent(event);
453
+ break;
454
+ case '1':
455
+ h1();
456
+ haltEvent(event);
457
+ break;
458
+ case '2':
459
+ h2();
460
+ haltEvent(event);
461
+ break;
462
+ case '3':
463
+ h3();
464
+ haltEvent(event);
465
+ break;
466
+ case 'z':
467
+ undo();
468
+ haltEvent(event);
469
+ break;
470
+ case 'y':
471
+ redo();
472
+ haltEvent(event);
473
+ break;
474
+ }
475
+ }
476
+ };
477
+ const getCharCountText = (comment) => {
478
+ const charCountLeft = charLimit - comment.length;
479
+ return (React.createElement("span", { className: `character-count-wrapper ${charCountLeft <= 0 ? 'red' : ''}` },
480
+ "(",
481
+ t(`{{charCountLeft}} characters left`, { charCountLeft }),
482
+ ")"));
483
+ };
484
+ const contentRows = (props.value && props.value.split('\n').length + 1) || 4;
485
+ // reset undo redo value after post comment
486
+ props.disabled && !isEmpty(state.present) && resetURValue('');
487
+ // editor is disabled and preview is true -> set preview to false
488
+ props.disabled && isPreview && setIsPreview(false);
489
+ const disablePreviewButton = mdValue === '';
490
+ const [isHeadingDropdownOpen, setIsHeadingDropdownOpen] = React.useState(false);
491
+ const onToggle = (isOpen) => {
492
+ setIsHeadingDropdownOpen(isOpen);
493
+ };
494
+ const onHeadingSelect = () => {
495
+ setIsHeadingDropdownOpen(false);
496
+ };
497
+ const [isDrillMenuOpen, setIsDrillMenuOpen] = React.useState(false);
498
+ const [activeMenu, setActiveMenu] = useState('toolsKebabMenu');
499
+ const [menuDrilledIn, setMenuDrilledIn] = useState([]);
500
+ const [drilldownPath, setDrilldownPath] = useState([]);
501
+ const [menuHeights, setMenuHeights] = useState({});
502
+ const toggleRef = useRef(null);
503
+ const drillMenuRef = useRef(null);
504
+ const onToggleClick = (e) => {
505
+ e.stopPropagation();
506
+ setIsDrillMenuOpen(!isDrillMenuOpen);
507
+ setMenuDrilledIn([]);
508
+ setDrilldownPath([]);
509
+ setActiveMenu('toolsKebabMenu');
510
+ };
511
+ const drillIn = (_event, fromMenuId, toMenuId, pathId) => {
512
+ setMenuDrilledIn([...menuDrilledIn, fromMenuId]);
513
+ setDrilldownPath([...drilldownPath, pathId]);
514
+ setActiveMenu(toMenuId);
515
+ };
516
+ const drillOut = (_event, toMenuId) => {
517
+ setMenuDrilledIn(menuDrilledIn.slice(0, menuDrilledIn.length - 1));
518
+ setDrilldownPath(drilldownPath.slice(0, drilldownPath.length - 1));
519
+ setActiveMenu(toMenuId);
520
+ };
521
+ const setHeight = (menuId, height) => {
522
+ if (!menuHeights[menuId] || (menuId !== 'toolsKebabMenu' && menuHeights[menuId] !== height)) {
523
+ setMenuHeights(Object.assign(Object.assign({}, menuHeights), { [menuId]: height }));
524
+ }
525
+ };
526
+ const markdownGuidanceBtn = (React.createElement("a", { className: "pf-v6-c-button pf-m-link pf-m-inline markdown-guidance", rel: "noopener noreferrer", target: "_blank", href: "/articles/4729621", "data-tracking-id": "md-guidance" },
527
+ React.createElement(Trans, null, "Markdown guidance")));
528
+ return (React.createElement("div", { id: "markdown-editor" },
529
+ !isPlainModeEnabled && React.createElement("div", { className: "markdown-guidance-container" }, markdownGuidanceBtn),
530
+ React.createElement("div", { className: `markdown-editor-container ${props.className}`, id: props.id, "data-tracking-id": "md-editor-container", onDrop: onDrop, onPaste: onPaste },
531
+ React.createElement("div", { className: "toolbar-container border-bottom" }, !isPlainModeEnabled && (React.createElement(React.Fragment, null,
532
+ React.createElement(Toolbar, { className: "desktop-markdown-toolbar pf-v6-l-flex" },
533
+ React.createElement(ToolbarGroup, { "data-tracking-id": "md-editor-toolbar", className: "markdown-toolbar-left-section pf-m-wrap" },
534
+ React.createElement(Button, { icon: "B", variant: ButtonVariant.plain, tabIndex: -1, isDisabled: props.disabled || isPreview, onClick: bold, "data-tracking-id": "md-editor-toolbar-b", title: "Bold" }),
535
+ React.createElement(Button, { icon: "I", variant: ButtonVariant.plain, tabIndex: -1, isDisabled: props.disabled || isPreview, onClick: italic, "data-tracking-id": "md-editor-toolbar-i", title: "Italic" }),
536
+ React.createElement("div", { className: "btn-group-separator" }),
537
+ !props.hideHeadingOptions && (React.createElement(React.Fragment, null,
538
+ React.createElement(Button, { icon: "H1", tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: h1, title: "Heading 1", "data-tracking-id": "md-editor-toolbar-h1" }),
539
+ React.createElement(Button, { icon: "H2", tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: h2, title: "Heading 2", "data-tracking-id": "md-editor-toolbar-h2" }),
540
+ React.createElement(Button, { icon: "H3", tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: h3, title: "Heading 3", "data-tracking-id": "md-editor-toolbar-h3" }),
541
+ React.createElement("div", { className: "btn-group-separator" }))),
542
+ React.createElement(Button, { icon: React.createElement(ListUlIcon, null), tabIndex: -1, isDisabled: props.disabled || isPreview, variant: ButtonVariant.plain, onClick: list, title: "Bullet List", "data-tracking-id": "md-editor-toolbar-bul-list" }),
543
+ React.createElement("div", { className: "btn-group-separator" }),
544
+ React.createElement(Button, { icon: React.createElement(LinkIcon, null), tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: link, title: "Link", "data-tracking-id": "md-editor-toolbar-link" }),
545
+ React.createElement(Button, { icon: React.createElement(QuoteRightIcon, null), tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: quote, title: "Quote", "data-tracking-id": "md-editor-toolbar-quote" }),
546
+ React.createElement(Button, { icon: React.createElement(CodeIcon, null), tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: code, title: "Code", "data-tracking-id": "md-editor-toolbar-code" }),
547
+ showFileSelectorInToolbar && (React.createElement(Dropdown, { isOpen: isFileSelectorOpen, onSelect: onFileSelectLocal, onOpenChange: (isOpen) => onFileSelectToggle(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: () => onFileSelectToggle(!isFileSelectorOpen), isExpanded: isFileSelectorOpen, isDisabled: props.disabled || isPreview, "aria-label": "file selector", variant: "plain" },
548
+ React.createElement("span", { className: "toggle-content" },
549
+ React.createElement(ImageIcon, null),
550
+ React.createElement(CaretDownIcon, { className: "dropdown-caret" })))), id: "md-editor-toolbar-file-selector", "data-tracking-id": "md-editor-toolbar-file-selector", shouldFocusFirstItemOnOpen: false },
551
+ React.createElement(DropdownList, null, getFileSelectorList())))),
552
+ React.createElement(ToolbarItem, null, markdownGuidanceBtn)),
553
+ React.createElement(Toolbar, { className: "mobile-markdown-toolbar" },
554
+ React.createElement("div", { className: "mobile-markdown-toolbar" },
555
+ React.createElement(ToolbarGroup, { "data-tracking-id": "mobile-md-editor-toolbar", className: "markdown-toolbar-left-section" },
556
+ !props.hideHeadingOptions && (React.createElement(Dropdown, { isOpen: isHeadingDropdownOpen, onSelect: onHeadingSelect, onOpenChange: (isOpen) => onToggle(isOpen), toggle: (toggleRef) => (React.createElement(MenuToggle, { ref: toggleRef, onClick: () => onToggle(!isHeadingDropdownOpen), isExpanded: isHeadingDropdownOpen, isDisabled: props.disabled || isPreview, "aria-label": "heading selector", variant: "plain", id: "heading-selector" },
557
+ React.createElement("span", { className: "toggle-content" },
558
+ "Heading",
559
+ React.createElement(CaretDownIcon, { className: "dropdown-caret" })))), id: "md-editor-toolbar-heading-selector", "data-tracking-id": "md-editor-toolbar-heading-selector", shouldFocusFirstItemOnOpen: false },
560
+ React.createElement(DropdownList, null,
561
+ React.createElement(DropdownItem, { key: "heading-1", isDisabled: props.disabled || isPreview, onClick: h1, "data-tracking-id": "mobile-md-editor-toolbar-h1" }, "Heading 1"),
562
+ React.createElement(DropdownItem, { key: "heading-2", isDisabled: props.disabled || isPreview, onClick: h2, "data-tracking-id": "mobile-md-editor-toolbar-h2" }, "Heading 2"),
563
+ React.createElement(DropdownItem, { key: "heading-3", isDisabled: props.disabled || isPreview, onClick: h3, "data-tracking-id": "mobile-md-editor-toolbar-h3" }, "Heading 3")))),
564
+ React.createElement(Button, { icon: "B", variant: ButtonVariant.plain, tabIndex: -1, isDisabled: props.disabled || isPreview, onClick: bold, "data-tracking-id": "mobile-md-editor-toolbar-b", title: "Bold", className: "mobile-markdown-tool-item" }),
565
+ React.createElement(Button, { icon: "I", variant: ButtonVariant.plain, tabIndex: -1, isDisabled: props.disabled || isPreview, onClick: italic, "data-tracking-id": "mobile-md-editor-toolbar-i", title: "Italic", className: "mobile-markdown-tool-item" }),
566
+ React.createElement(Button, { icon: React.createElement(ListUlIcon, null), tabIndex: -1, isDisabled: props.disabled || isPreview, variant: ButtonVariant.plain, onClick: list, title: "Bullet List", "data-tracking-id": "mobile-md-editor-toolbar-bul-list", className: "mobile-markdown-tool-item" }),
567
+ React.createElement(Button, { icon: React.createElement(CodeIcon, null), tabIndex: -1, variant: ButtonVariant.plain, isDisabled: props.disabled || isPreview, onClick: code, title: "Code", "data-tracking-id": "mobile-md-editor-toolbar-code", className: "mobile-markdown-tool-item" })),
568
+ React.createElement(ToolbarItem, { id: "tools-kebab-menu-container", className: "mobile-markdown-tool-item", gap: { default: 'gapSm' } },
569
+ React.createElement(MenuContainer, { isOpen: isDrillMenuOpen, onOpenChange: (isOpen) => setIsDrillMenuOpen(isOpen), popperProps: { position: 'left' }, menu: React.createElement(Menu, { id: "toolsKebabMenu", containsDrilldown: true, drilldownItemPath: drilldownPath, drilledInMenus: menuDrilledIn, activeMenu: activeMenu, onDrillIn: drillIn, onDrillOut: drillOut, onGetMenuHeight: setHeight, ref: drillMenuRef },
570
+ React.createElement(MenuContent, { menuHeight: `${menuHeights[activeMenu]}px` },
571
+ React.createElement(MenuList, null,
572
+ React.createElement(MenuItem, { itemId: "link-btn-menu-item", isDisabled: props.disabled || isPreview, onClick: link, "data-tracking-id": "mobile-md-editor-toolbar-link", icon: React.createElement(LinkIcon, { "aria-hidden": true }) }, "Link"),
573
+ React.createElement(MenuItem, { itemId: "code-btn-menu-item", isDisabled: props.disabled || isPreview, onClick: quote, "data-tracking-id": "mobile-md-editor-toolbar-quote", icon: React.createElement(QuoteRightIcon, { "aria-hidden": true }) }, "Quote"),
574
+ React.createElement(MenuItem, { itemId: "group:manage_attachments", direction: "down", icon: React.createElement(ImageIcon, { "aria-hidden": true }), drilldownMenu: React.createElement(DrilldownMenu, { id: "drilldownMenuStart" },
575
+ React.createElement(MenuItem, { itemId: "group:manage_attachments_breadcrumb", direction: "up" }, "Manage attachments"),
576
+ React.createElement("div", { className: "attachments-items" }, getFileSelectorList(true))) }, "Manage Attachments")))), menuRef: drillMenuRef, toggle: React.createElement(MenuToggle, { variant: "plain", ref: toggleRef, onClick: onToggleClick, isExpanded: isDrillMenuOpen },
577
+ React.createElement(EllipsisVIcon, null)), toggleRef: toggleRef }))))))),
578
+ React.createElement("div", { className: "markdown-content" }, !isPreview ? (React.createElement("textarea", Object.assign({}, textAreaProps, { onKeyDown: keydown, placeholder: isPlainModeEnabled ? t(props.plainTextPlaceholder) : t(props.mdPlaceholder), ref: bindTextAreaRef, className: cx('form-control', 'markdown-text-area', props.textAreaClassName, 'textArea'), value: mdValue, onChange: textChanged, rows: props.rows || contentRows, style: { height: '313px' } }))) : (React.createElement("div", { className: "markdown-preview-placeholder", style: { height: '313px' } },
579
+ React.createElement("div", { className: "markdown-preview", style: { maxHeight: '313px' } },
580
+ React.createElement("div", { dangerouslySetInnerHTML: {
581
+ __html: DOMPurify.sanitize(previewMDText(mdValue)),
582
+ } }))))),
583
+ React.createElement("div", null, props.showMarkdownPlainTextToggle && (React.createElement("div", { className: `markdown-toggle-toolbar pf-v6-l-flex` },
584
+ React.createElement("span", { className: "markdown-toolbar-left-section pf-m-wrap" },
585
+ !isPlainModeEnabled && (React.createElement(Button, { className: "markdown-preview-btn", tabIndex: -1, variant: ButtonVariant.plain, onClick: preview, title: "Show preview", "data-tracking-id": `md-preview-${isPreview ? 'hide' : 'show'}`, isDisabled: disablePreviewButton, icon: React.createElement(Trans, null, isPreview ? (React.createElement("span", null,
586
+ " ",
587
+ t(hidePreviewText))) : (React.createElement("span", null,
588
+ " ",
589
+ t(showPreviewText)))) })),
590
+ isUploadingFile && (React.createElement(React.Fragment, null,
591
+ React.createElement(Trans, null,
592
+ React.createElement("small", { className: "pf-v6-u-pl-md uploading-spinner-text" },
593
+ React.createElement(Trans, null, "Uploading File"))),
594
+ React.createElement(Spinner, { className: "pf-v6-u-ml-sm", size: "sm" }))),
595
+ mdValue.length > props.minCharsForCounter && getCharCountText(mdValue)),
596
+ React.createElement("span", null,
597
+ React.createElement(Button, { icon: React.createElement(Trans, null, "Plain text"), variant: "plain", className: `${isPlainModeEnabled ? 'markdown-active-mode' : ''} plain-mode-btn`, onClick: () => onModeChange(true), isDisabled: isPlainModeEnabled || isUploadingFile, "data-tracking-id": "md-editor-plaintext" }),
598
+ React.createElement(Button, { icon: React.createElement(Trans, null, "Markdown"), variant: "plain", className: `${!isPlainModeEnabled ? 'markdown-active-mode' : ''} markdown-mode-btn`, onClick: () => onModeChange(false), isDisabled: !isPlainModeEnabled, "data-tracking-id": "md-editor-md" }))))))));
599
+ }
600
+ MarkdownEditor.defaultProps = defaultProps;
601
+ export { MarkdownEditor };
@@ -0,0 +1,2 @@
1
+ export * from './MarkdownEditor';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MarkdownEditor/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './MarkdownEditor';