@rh-support/components 2.5.230 → 2.5.231

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
@@ -1,464 +0,0 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- import './richTextEditor.css';
11
- import { Button, ButtonVariant, Dropdown, DropdownItem, DropdownList, FormGroup, MenuToggle, Modal, ModalBody, ModalFooter, ModalHeader, ModalVariant, TextInput, Toolbar, ToolbarGroup, } from '@patternfly/react-core';
12
- import AlignCenterIcon from '@patternfly/react-icons/dist/js/icons/align-center-icon';
13
- import AlignJustifyIcon from '@patternfly/react-icons/dist/js/icons/align-justify-icon';
14
- import AlignLeftIcon from '@patternfly/react-icons/dist/js/icons/align-left-icon';
15
- import AlignRightIcon from '@patternfly/react-icons/dist/js/icons/align-right-icon';
16
- import CaretDownIcon from '@patternfly/react-icons/dist/js/icons/caret-down-icon';
17
- import IndentIcon from '@patternfly/react-icons/dist/js/icons/indent-icon';
18
- import LinkIcon from '@patternfly/react-icons/dist/js/icons/link-icon';
19
- import ListOlIcon from '@patternfly/react-icons/dist/js/icons/list-ol-icon';
20
- import ListUlIcon from '@patternfly/react-icons/dist/js/icons/list-ul-icon';
21
- import OutdentIcon from '@patternfly/react-icons/dist/js/icons/outdent-icon';
22
- import RemoveFormatIcon from '@patternfly/react-icons/dist/js/icons/remove-format-icon';
23
- import StrikethroughIcon from '@patternfly/react-icons/dist/js/icons/strikethrough-icon';
24
- import React, { useCallback, useEffect, useRef, useState } from 'react';
25
- import { useTranslation } from 'react-i18next';
26
- import { ToastNotification } from '../ToastNotification';
27
- import { isValidUrl, normalizeLinkUrl } from './linkUrl';
28
- import { useQuillEditor } from './useQuillEditor';
29
- const defaultCharLimit = 32000;
30
- const defaultMinCharsForCounter = 31800;
31
- const ALLOWED_IMAGE_TYPES = new Set(['image/jpeg', 'image/png', 'image/gif', 'image/svg+xml']);
32
- const IMAGE_SIZE_LIMIT = 2 * 1024 * 1024; // 2 MB
33
- export function RichTextEditor(props) {
34
- const { t } = useTranslation();
35
- const { value, onChange, onChangePlainText, placeholder = '', disabled = false, className = '', id = 'rich-text-editor', hideToolbar = false, charLimit = defaultCharLimit, minCharsForCounter = defaultMinCharsForCounter, onCharLimitExceeded, height, onFocus, onBlur, bindEditor, 'aria-label': ariaLabel, onImageUpload, } = props;
36
- const [isHeadingDropdownOpen, setIsHeadingDropdownOpen] = useState(false);
37
- const [charCount, setCharCount] = useState(0);
38
- const charLimitExceededRef = useRef(false);
39
- const [isLinkModalOpen, setIsLinkModalOpen] = useState(false);
40
- const [linkUrl, setLinkUrl] = useState('');
41
- const [linkText, setLinkText] = useState('');
42
- const savedSelectionRef = useRef(null);
43
- const [isImageModalOpen, setIsImageModalOpen] = useState(false);
44
- const [imageUrl, setImageUrl] = useState('');
45
- const [imageUrlError, setImageUrlError] = useState('');
46
- const savedImageSelectionRef = useRef(null);
47
- const [, setIsImageUploading] = useState(false);
48
- const fileInputRef = useRef(null);
49
- const { editorRef, quillRef, activeFormat, setActiveFormat, setContents, setReadOnly } = useQuillEditor({
50
- placeholder,
51
- readOnly: disabled,
52
- onChange: (html) => {
53
- onChange(html);
54
- },
55
- onChangePlainText: (text) => {
56
- onChangePlainText === null || onChangePlainText === void 0 ? void 0 : onChangePlainText(text);
57
- setCharCount(text.length);
58
- },
59
- onFocus,
60
- onBlur,
61
- });
62
- // Sync external value into Quill when it changes externally
63
- useEffect(() => {
64
- setContents(value);
65
- }, [value, setContents]);
66
- // Sync disabled state
67
- useEffect(() => {
68
- setReadOnly(disabled);
69
- }, [disabled, setReadOnly]);
70
- // Bind editor ref externally
71
- useEffect(() => {
72
- if (quillRef.current && bindEditor) {
73
- bindEditor(quillRef.current);
74
- }
75
- }, [bindEditor, quillRef]);
76
- // Character limit exceeded callback
77
- useEffect(() => {
78
- const exceeded = charCount > charLimit;
79
- if (exceeded !== charLimitExceededRef.current) {
80
- charLimitExceededRef.current = exceeded;
81
- onCharLimitExceeded === null || onCharLimitExceeded === void 0 ? void 0 : onCharLimitExceeded(exceeded);
82
- }
83
- }, [charCount, charLimit, onCharLimitExceeded]);
84
- const onImageUploadRef = useRef(onImageUpload);
85
- onImageUploadRef.current = onImageUpload;
86
- useEffect(() => {
87
- const editorEl = editorRef.current;
88
- if (!editorEl || !onImageUploadRef.current)
89
- return;
90
- const handlePaste = (e) => __awaiter(this, void 0, void 0, function* () {
91
- var _a;
92
- if (!((_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.items))
93
- return;
94
- const imageItem = Array.from(e.clipboardData.items).find((item) => item.type.startsWith('image/') && ALLOWED_IMAGE_TYPES.has(item.type));
95
- if (!imageItem)
96
- return;
97
- const file = imageItem.getAsFile();
98
- if (!file)
99
- return;
100
- if (file.size > IMAGE_SIZE_LIMIT) {
101
- e.preventDefault();
102
- e.stopPropagation();
103
- return;
104
- }
105
- e.preventDefault();
106
- e.stopPropagation();
107
- const quill = quillRef.current;
108
- if (!quill)
109
- return;
110
- const range = quill.getSelection() || { index: quill.getLength() - 1, length: 0 };
111
- setIsImageUploading(true);
112
- try {
113
- const url = yield onImageUploadRef.current(file);
114
- if (url) {
115
- quill.insertEmbed(range.index, 'image', url);
116
- quill.setSelection(range.index + 1, 0, 'silent');
117
- }
118
- }
119
- catch (_b) {
120
- // Error handled by the consumer's upload implementation
121
- }
122
- finally {
123
- setIsImageUploading(false);
124
- }
125
- });
126
- editorEl.addEventListener('paste', handlePaste);
127
- return () => {
128
- editorEl.removeEventListener('paste', handlePaste);
129
- };
130
- // eslint-disable-next-line react-hooks/exhaustive-deps
131
- }, [editorRef, quillRef]);
132
- const formatToggle = useCallback((format, value) => {
133
- const quill = quillRef.current;
134
- if (!quill)
135
- return;
136
- const selection = quill.getSelection();
137
- if (!selection)
138
- return;
139
- const newValue = value !== null && value !== void 0 ? value : !activeFormat[format];
140
- quill.format(format, newValue);
141
- setActiveFormat((prev) => {
142
- const next = Object.assign({}, prev);
143
- if (newValue) {
144
- next[format] = newValue;
145
- }
146
- else {
147
- delete next[format];
148
- }
149
- return next;
150
- });
151
- }, [activeFormat, quillRef, setActiveFormat]);
152
- const onLinkClick = useCallback(() => {
153
- const quill = quillRef.current;
154
- if (!quill)
155
- return;
156
- const range = quill.getSelection();
157
- if (!range)
158
- return;
159
- if (activeFormat.link) {
160
- quill.format('link', false);
161
- setActiveFormat((prev) => {
162
- const next = Object.assign({}, prev);
163
- delete next.link;
164
- return next;
165
- });
166
- }
167
- else {
168
- savedSelectionRef.current = { index: range.index, length: range.length };
169
- setLinkText(range.length > 0 ? quill.getText(range.index, range.length).trim() : '');
170
- setLinkUrl('');
171
- setIsLinkModalOpen(true);
172
- }
173
- }, [activeFormat, quillRef, setActiveFormat]);
174
- const onLinkInsert = useCallback(() => {
175
- const quill = quillRef.current;
176
- const saved = savedSelectionRef.current;
177
- const url = normalizeLinkUrl(linkUrl);
178
- if (!quill || !saved || !url)
179
- return;
180
- const text = linkText.trim() || url;
181
- if (saved.length > 0) {
182
- quill.setSelection(saved.index, saved.length, 'silent');
183
- quill.format('link', url);
184
- }
185
- else {
186
- quill.insertText(saved.index, text, 'link', url, 'user');
187
- quill.setSelection(saved.index + text.length, 0, 'silent');
188
- }
189
- setActiveFormat((prev) => (Object.assign(Object.assign({}, prev), { link: url })));
190
- savedSelectionRef.current = null;
191
- setIsLinkModalOpen(false);
192
- setLinkUrl('');
193
- setLinkText('');
194
- }, [linkUrl, linkText, quillRef, setActiveFormat]);
195
- const onLinkCancel = useCallback(() => {
196
- savedSelectionRef.current = null;
197
- setIsLinkModalOpen(false);
198
- setLinkText('');
199
- setLinkUrl('');
200
- }, []);
201
- const handleFileInputChange = useCallback((e) => __awaiter(this, void 0, void 0, function* () {
202
- var _a;
203
- const file = (_a = e.target.files) === null || _a === void 0 ? void 0 : _a[0];
204
- if (fileInputRef.current)
205
- fileInputRef.current.value = '';
206
- if (!file || !onImageUpload)
207
- return;
208
- if (!ALLOWED_IMAGE_TYPES.has(file.type)) {
209
- ToastNotification.addDangerMessage(t('Invalid file type. Only JPEG, PNG, GIF, and SVG images are allowed.'));
210
- return;
211
- }
212
- if (file.size > IMAGE_SIZE_LIMIT) {
213
- ToastNotification.addDangerMessage(t('File is too large. Maximum size is 2 MB.'));
214
- return;
215
- }
216
- const quill = quillRef.current;
217
- const saved = savedImageSelectionRef.current;
218
- if (!quill || !saved)
219
- return;
220
- setIsImageUploading(true);
221
- try {
222
- const url = yield onImageUpload(file);
223
- if (url) {
224
- quill.focus();
225
- quill.setSelection(saved.index, saved.length, 'silent');
226
- quill.insertEmbed(saved.index, 'image', url);
227
- }
228
- }
229
- catch (_b) {
230
- ToastNotification.addDangerMessage(t('Failed to upload image.'));
231
- }
232
- finally {
233
- setIsImageUploading(false);
234
- savedImageSelectionRef.current = null;
235
- }
236
- }), [onImageUpload, quillRef, t]);
237
- const onImageInsert = useCallback(() => {
238
- const quill = quillRef.current;
239
- const saved = savedImageSelectionRef.current;
240
- if (!quill || !saved || !imageUrl.trim())
241
- return;
242
- const url = imageUrl.trim();
243
- try {
244
- const parsed = new URL(url);
245
- if (parsed.hostname !== 'redhat.com' && !parsed.hostname.endsWith('.redhat.com')) {
246
- setImageUrlError(t('Only *.redhat.com URLs are allowed'));
247
- return;
248
- }
249
- }
250
- catch (_a) {
251
- setImageUrlError(t('Only *.redhat.com URLs are allowed'));
252
- return;
253
- }
254
- quill.setSelection(saved.index, saved.length, 'silent');
255
- quill.insertEmbed(saved.index, 'image', url);
256
- savedImageSelectionRef.current = null;
257
- setIsImageModalOpen(false);
258
- setImageUrl('');
259
- setImageUrlError('');
260
- }, [imageUrl, quillRef, t]);
261
- const onImageCancel = useCallback(() => {
262
- savedImageSelectionRef.current = null;
263
- setIsImageModalOpen(false);
264
- setImageUrl('');
265
- setImageUrlError('');
266
- }, []);
267
- const onImageUrlChange = useCallback((_e, val) => {
268
- setImageUrl(val);
269
- if (val.trim()) {
270
- try {
271
- const parsed = new URL(val.trim());
272
- if (parsed.hostname !== 'redhat.com' && !parsed.hostname.endsWith('.redhat.com')) {
273
- setImageUrlError(t('Only *.redhat.com URLs are allowed'));
274
- }
275
- else {
276
- setImageUrlError('');
277
- }
278
- }
279
- catch (_a) {
280
- setImageUrlError(t('Only *.redhat.com URLs are allowed'));
281
- }
282
- }
283
- else {
284
- setImageUrlError('');
285
- }
286
- }, [t]);
287
- const onImageUrlKeyDown = useCallback((e) => {
288
- if (e.key === 'Enter') {
289
- e.preventDefault();
290
- onImageInsert();
291
- }
292
- }, [onImageInsert]);
293
- const onLinkTextChange = useCallback((_e, val) => {
294
- setLinkText(val);
295
- }, []);
296
- const onLinkUrlChange = useCallback((_e, val) => {
297
- setLinkUrl(val);
298
- }, []);
299
- const onLinkUrlKeyDown = useCallback((e) => {
300
- if (e.key === 'Enter') {
301
- e.preventDefault();
302
- onLinkInsert();
303
- }
304
- }, [onLinkInsert]);
305
- const onToolbarMouseDown = useCallback((e) => {
306
- e.preventDefault();
307
- }, []);
308
- const onBoldClick = useCallback(() => formatToggle('bold'), [formatToggle]);
309
- const onItalicClick = useCallback(() => formatToggle('italic'), [formatToggle]);
310
- const onUnderlineClick = useCallback(() => formatToggle('underline'), [formatToggle]);
311
- const onStrikethroughClick = useCallback(() => formatToggle('strike'), [formatToggle]);
312
- const onIndentClick = useCallback(() => {
313
- const quill = quillRef.current;
314
- if (!quill)
315
- return;
316
- const range = quill.getSelection();
317
- if (!range)
318
- return;
319
- const current = quill.getFormat(range).indent || 0;
320
- quill.format('indent', current + 1);
321
- setActiveFormat((prev) => (Object.assign(Object.assign({}, prev), { indent: current + 1 })));
322
- }, [quillRef, setActiveFormat]);
323
- const onOutdentClick = useCallback(() => {
324
- const quill = quillRef.current;
325
- if (!quill)
326
- return;
327
- const range = quill.getSelection();
328
- if (!range)
329
- return;
330
- const current = quill.getFormat(range).indent || 0;
331
- if (current > 0) {
332
- quill.format('indent', current - 1 || false);
333
- setActiveFormat((prev) => (Object.assign(Object.assign({}, prev), { indent: current - 1 || false })));
334
- }
335
- }, [quillRef, setActiveFormat]);
336
- const onAlignClick = useCallback((alignment) => {
337
- const quill = quillRef.current;
338
- if (!quill)
339
- return;
340
- const range = quill.getSelection();
341
- if (!range)
342
- return;
343
- const currentAlign = activeFormat.align || false;
344
- const newValue = currentAlign === alignment ? false : alignment;
345
- quill.format('align', newValue);
346
- setActiveFormat((prev) => {
347
- const next = Object.assign({}, prev);
348
- if (newValue) {
349
- next.align = newValue;
350
- }
351
- else {
352
- delete next.align;
353
- }
354
- return next;
355
- });
356
- }, [activeFormat.align, quillRef, setActiveFormat]);
357
- const onAlignLeftClick = useCallback(() => onAlignClick(false), [onAlignClick]);
358
- const onAlignCenterClick = useCallback(() => onAlignClick('center'), [onAlignClick]);
359
- const onAlignRightClick = useCallback(() => onAlignClick('right'), [onAlignClick]);
360
- const onAlignJustifyClick = useCallback(() => onAlignClick('justify'), [onAlignClick]);
361
- const onRemoveFormatClick = useCallback(() => {
362
- const quill = quillRef.current;
363
- if (!quill)
364
- return;
365
- const range = quill.getSelection();
366
- if (!range)
367
- return;
368
- if (range.length === 0) {
369
- const [line, offset] = quill.getLine(range.index);
370
- if (line) {
371
- quill.removeFormat(range.index - offset, line.length());
372
- }
373
- }
374
- else {
375
- quill.removeFormat(range.index, range.length);
376
- }
377
- setActiveFormat(quill.getFormat(range));
378
- }, [quillRef, setActiveFormat]);
379
- const onHeadingSelect = useCallback(() => setIsHeadingDropdownOpen(false), []);
380
- const onHeadingToggleClick = useCallback(() => setIsHeadingDropdownOpen((prev) => !prev), []);
381
- const onHeading1Click = useCallback(() => formatToggle('header', 1), [formatToggle]);
382
- const onHeading2Click = useCallback(() => formatToggle('header', 2), [formatToggle]);
383
- const onHeading3Click = useCallback(() => formatToggle('header', 3), [formatToggle]);
384
- const onHeadingNormalClick = useCallback(() => formatToggle('header', false), [formatToggle]);
385
- const onBulletListClick = useCallback(() => formatToggle('list', activeFormat.list === 'bullet' ? false : 'bullet'), [formatToggle, activeFormat.list]);
386
- const onOrderedListClick = useCallback(() => formatToggle('list', activeFormat.list === 'ordered' ? false : 'ordered'), [formatToggle, activeFormat.list]);
387
- const headingToggleRenderer = useCallback((toggleRef) => {
388
- const headingLevel = activeFormat.header;
389
- const label = headingLevel ? `Heading ${headingLevel}` : 'Normal';
390
- return (React.createElement(MenuToggle, { ref: toggleRef, onClick: onHeadingToggleClick, isDisabled: disabled, variant: "plain", "aria-label": `Text style: ${label}`, className: `richtext-toolbar-btn ${headingLevel ? 'pf-m-active' : ''}` },
391
- React.createElement("span", { className: "toggle-content" },
392
- label,
393
- " ",
394
- React.createElement(CaretDownIcon, { className: "dropdown-caret" }))));
395
- }, [activeFormat.header, disabled, onHeadingToggleClick]);
396
- const editorStyle = {};
397
- if (height) {
398
- editorStyle.height = typeof height === 'number' ? `${height}px` : height;
399
- }
400
- const showCounter = charCount >= minCharsForCounter;
401
- const isOverLimit = charCount > charLimit;
402
- return (React.createElement("div", { id: id, className: `richtext-editor-container ${disabled ? 'richtext-editor-container--disabled' : ''} ${className}` },
403
- !hideToolbar && (React.createElement(Toolbar, { className: "richtext-toolbar", role: "toolbar", "aria-label": "Formatting options", onMouseDown: onToolbarMouseDown },
404
- React.createElement(ToolbarGroup, { className: "richtext-toolbar-left-section" },
405
- React.createElement(Button, { variant: ButtonVariant.plain, isClicked: !!activeFormat.bold, isDisabled: disabled, onClick: onBoldClick, "aria-label": "Bold", "aria-pressed": !!activeFormat.bold, className: "richtext-toolbar-btn" },
406
- React.createElement("strong", null, "B")),
407
- React.createElement(Button, { variant: ButtonVariant.plain, isClicked: !!activeFormat.italic, isDisabled: disabled, onClick: onItalicClick, "aria-label": "Italic", "aria-pressed": !!activeFormat.italic, className: "richtext-toolbar-btn" },
408
- React.createElement("em", null, "I")),
409
- React.createElement(Button, { variant: ButtonVariant.plain, isClicked: !!activeFormat.underline, isDisabled: disabled, onClick: onUnderlineClick, "aria-label": "Underline", "aria-pressed": !!activeFormat.underline, className: "richtext-toolbar-btn" },
410
- React.createElement("u", null, "U")),
411
- React.createElement(Button, { icon: React.createElement(StrikethroughIcon, null), variant: ButtonVariant.plain, isClicked: !!activeFormat.strike, isDisabled: disabled, onClick: onStrikethroughClick, "aria-label": "Strikethrough", "aria-pressed": !!activeFormat.strike, className: "richtext-toolbar-btn" }),
412
- React.createElement("div", { className: "btn-group-separator" }),
413
- React.createElement(Dropdown, { isOpen: isHeadingDropdownOpen, onSelect: onHeadingSelect, onOpenChange: setIsHeadingDropdownOpen, toggle: headingToggleRenderer },
414
- React.createElement(DropdownList, null,
415
- React.createElement(DropdownItem, { key: "h1", onClick: onHeading1Click }, "Heading 1"),
416
- React.createElement(DropdownItem, { key: "h2", onClick: onHeading2Click }, "Heading 2"),
417
- React.createElement(DropdownItem, { key: "h3", onClick: onHeading3Click }, "Heading 3"),
418
- React.createElement(DropdownItem, { key: "normal", onClick: onHeadingNormalClick }, "Normal"))),
419
- React.createElement("div", { className: "btn-group-separator" }),
420
- React.createElement(Button, { icon: React.createElement(ListUlIcon, null), variant: ButtonVariant.plain, isClicked: activeFormat.list === 'bullet', isDisabled: disabled, onClick: onBulletListClick, "aria-label": "Bullet List", "aria-pressed": activeFormat.list === 'bullet', className: "richtext-toolbar-btn" }),
421
- React.createElement(Button, { icon: React.createElement(ListOlIcon, null), variant: ButtonVariant.plain, isClicked: activeFormat.list === 'ordered', isDisabled: disabled, onClick: onOrderedListClick, "aria-label": "Ordered List", "aria-pressed": activeFormat.list === 'ordered', className: "richtext-toolbar-btn" }),
422
- React.createElement(Button, { icon: React.createElement(IndentIcon, null), variant: ButtonVariant.plain, isDisabled: disabled, onClick: onIndentClick, "aria-label": "Indent", className: "richtext-toolbar-btn" }),
423
- React.createElement(Button, { icon: React.createElement(OutdentIcon, null), variant: ButtonVariant.plain, isDisabled: disabled, onClick: onOutdentClick, "aria-label": "Outdent", className: "richtext-toolbar-btn" }),
424
- React.createElement("div", { className: "btn-group-separator" }),
425
- React.createElement(Button, { icon: React.createElement(AlignLeftIcon, null), variant: ButtonVariant.plain, isClicked: !activeFormat.align, isDisabled: disabled, onClick: onAlignLeftClick, "aria-label": "Align Left", "aria-pressed": !activeFormat.align, className: "richtext-toolbar-btn" }),
426
- React.createElement(Button, { icon: React.createElement(AlignCenterIcon, null), variant: ButtonVariant.plain, isClicked: activeFormat.align === 'center', isDisabled: disabled, onClick: onAlignCenterClick, "aria-label": "Align Center", "aria-pressed": activeFormat.align === 'center', className: "richtext-toolbar-btn" }),
427
- React.createElement(Button, { icon: React.createElement(AlignRightIcon, null), variant: ButtonVariant.plain, isClicked: activeFormat.align === 'right', isDisabled: disabled, onClick: onAlignRightClick, "aria-label": "Align Right", "aria-pressed": activeFormat.align === 'right', className: "richtext-toolbar-btn" }),
428
- React.createElement(Button, { icon: React.createElement(AlignJustifyIcon, null), variant: ButtonVariant.plain, isClicked: activeFormat.align === 'justify', isDisabled: disabled, onClick: onAlignJustifyClick, "aria-label": "Justify", "aria-pressed": activeFormat.align === 'justify', className: "richtext-toolbar-btn" }),
429
- React.createElement("div", { className: "btn-group-separator" }),
430
- React.createElement(Button, { icon: React.createElement(LinkIcon, null), variant: ButtonVariant.plain, isClicked: !!activeFormat.link, isDisabled: disabled, onClick: onLinkClick, "aria-label": "Link", "aria-pressed": !!activeFormat.link, className: "richtext-toolbar-btn" }),
431
- React.createElement(Button, { icon: React.createElement(RemoveFormatIcon, null), variant: ButtonVariant.plain, isDisabled: disabled, onClick: onRemoveFormatClick, "aria-label": "Remove Format", className: "richtext-toolbar-btn" })))),
432
- React.createElement("div", { ref: editorRef, role: "textbox", className: "richtext-editor-content", style: editorStyle, "aria-label": ariaLabel, tabIndex: -1, onClick: () => { var _a, _b; return (_b = (_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('[contenteditable="true"]')) === null || _b === void 0 ? void 0 : _b.focus(); }, onKeyDown: (e) => {
433
- var _a, _b;
434
- if (e.key === 'Enter' || e.key === ' ') {
435
- (_b = (_a = editorRef.current) === null || _a === void 0 ? void 0 : _a.querySelector('[contenteditable="true"]')) === null || _b === void 0 ? void 0 : _b.focus();
436
- }
437
- } }),
438
- showCounter && (React.createElement("div", { className: `richtext-char-counter ${isOverLimit ? 'richtext-char-counter--exceeded' : ''}` },
439
- charCount,
440
- "/",
441
- charLimit)),
442
- React.createElement(Modal, { variant: ModalVariant.small, isOpen: isLinkModalOpen, onClose: onLinkCancel, "aria-labelledby": "insert-link-title" },
443
- React.createElement(ModalHeader, { title: "Insert link", labelId: "insert-link-title" }),
444
- React.createElement(ModalBody, null,
445
- React.createElement(FormGroup, { label: "Display text", fieldId: "link-text-input" },
446
- React.createElement(TextInput, { id: "link-text-input", value: linkText, onChange: onLinkTextChange, placeholder: "Link text (defaults to URL)" })),
447
- React.createElement(FormGroup, { label: "URL", isRequired: true, fieldId: "link-url-input" },
448
- React.createElement(TextInput, { id: "link-url-input", type: "url", value: linkUrl, onChange: onLinkUrlChange, placeholder: "https://", onKeyDown: onLinkUrlKeyDown }))),
449
- React.createElement(ModalFooter, null,
450
- React.createElement(Button, { variant: ButtonVariant.primary, onClick: onLinkInsert, isDisabled: !linkUrl.trim() || !isValidUrl(linkUrl.trim()) }, "Insert"),
451
- React.createElement(Button, { variant: ButtonVariant.link, onClick: onLinkCancel }, "Cancel"))),
452
- React.createElement("input", { ref: fileInputRef, type: "file", accept: "image/jpeg,image/png,image/gif,image/svg+xml", style: { display: 'none' }, onChange: handleFileInputChange, tabIndex: -1 }),
453
- !onImageUpload && (React.createElement(Modal, { variant: ModalVariant.small, isOpen: isImageModalOpen, onClose: onImageCancel, "aria-labelledby": "insert-image-title" },
454
- React.createElement(ModalHeader, { title: "Insert image", labelId: "insert-image-title" }),
455
- React.createElement(ModalBody, null,
456
- React.createElement(FormGroup, { label: "Image URL", isRequired: true, fieldId: "image-url-input" },
457
- React.createElement(TextInput, { id: "image-url-input", type: "url", value: imageUrl, validated: imageUrlError ? 'error' : 'default', onChange: onImageUrlChange, placeholder: "https://example.redhat.com/image.png", onKeyDown: onImageUrlKeyDown }),
458
- imageUrlError && (React.createElement(FormGroup, null,
459
- React.createElement("span", { className: "pf-v6-u-danger-color-100 pf-v6-u-font-size-sm" }, imageUrlError))))),
460
- React.createElement(ModalFooter, null,
461
- React.createElement(Button, { variant: ButtonVariant.primary, onClick: onImageInsert, isDisabled: !imageUrl.trim() || !!imageUrlError }, "Insert"),
462
- React.createElement(Button, { variant: ButtonVariant.link, onClick: onImageCancel }, "Cancel"))))));
463
- }
464
- export default RichTextEditor;
@@ -1,18 +0,0 @@
1
- import type { default as Quill } from 'quill';
2
- /**
3
- * Registers auto-link behaviour on a Quill instance:
4
- *
5
- * 1. **Type** — after a delimiter character, checks the preceding token
6
- * for URL / case-ID / BZ patterns.
7
- * 2. **Paste** — when the insert is longer than one character, scans the
8
- * full inserted range.
9
- * 3. **Blur** — a full-document scan so trailing tokens without a
10
- * delimiter still get linked before submit.
11
- */
12
- export declare function registerAutoLinkModule(quill: Quill): void;
13
- /**
14
- * Scans the entire document for unlinkified tokens.
15
- * Call this on blur so trailing URLs / IDs without a delimiter are caught.
16
- */
17
- export declare function linkifyFullDocument(quill: Quill): void;
18
- //# sourceMappingURL=autoLinkModule.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"autoLinkModule.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/autoLinkModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,KAAK,EAA8B,MAAM,OAAO,CAAC;AAyE1E;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CA0EzD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAItD"}
@@ -1,156 +0,0 @@
1
- function getCaseIdHref(caseId) {
2
- const host = typeof window !== 'undefined' ? window.location.host : 'access.redhat.com';
3
- return `https://${host}/support/cases/#/case/${caseId}`;
4
- }
5
- function getBugzillaHref(bugId) {
6
- return `https://bugzilla.redhat.com/show_bug.cgi?id=${bugId}`;
7
- }
8
- const DELIMITER_RE = /[\s.,;:!?)}\]>]/;
9
- const CASE_ID_RE = /(?:^|[\s(])(\d{8})$/;
10
- const BZ_RE = /(?:^|[\s(])(?:BZ[\s-]*|Bug[#\s]?)(\d{7,})$/i;
11
- const URL_RE = /(?:^|[\s(])(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])/gi;
12
- const URL_AT_END_RE = /(https?:\/\/[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])$/i;
13
- const WORD_CASE_ID_RE = /^(\d{8})$/;
14
- const WORD_BZ_RE = /^(?:BZ[\s-]*|Bug[#\s]?)(\d{7,})$/i;
15
- /**
16
- * Scans a text range inside the Quill document and applies link formatting
17
- * to all recognised URL, case-ID, and BZ patterns that aren't already linked.
18
- *
19
- * Used by the blur scan and paste scan to cover text that was not preceded
20
- * by a delimiter character.
21
- */
22
- function linkifyRange(quill, rangeStart, rangeLength) {
23
- const text = quill.getText(rangeStart, rangeLength);
24
- URL_RE.lastIndex = 0;
25
- let urlMatch;
26
- while ((urlMatch = URL_RE.exec(text)) !== null) {
27
- const url = urlMatch[1];
28
- const matchOffset = urlMatch.index + urlMatch[0].length - url.length;
29
- const absStart = rangeStart + matchOffset;
30
- const format = quill.getFormat(absStart, url.length);
31
- if (!format.link) {
32
- quill.formatText(absStart, url.length, 'link', url, 'api');
33
- }
34
- }
35
- const lines = text.split('\n');
36
- let offset = 0;
37
- for (const line of lines) {
38
- const words = line.split(/(\s+)/);
39
- let wordOffset = offset;
40
- for (const word of words) {
41
- if (/\S/.test(word)) {
42
- const caseMatch = WORD_CASE_ID_RE.exec(word);
43
- if (caseMatch) {
44
- const absStart = rangeStart + wordOffset;
45
- const fmt = quill.getFormat(absStart, 8);
46
- if (!fmt.link) {
47
- quill.formatText(absStart, 8, 'link', getCaseIdHref(caseMatch[1]), 'api');
48
- }
49
- }
50
- else {
51
- const bzMatch = WORD_BZ_RE.exec(word);
52
- if (bzMatch) {
53
- const absStart = rangeStart + wordOffset;
54
- const fmt = quill.getFormat(absStart, word.length);
55
- if (!fmt.link) {
56
- quill.formatText(absStart, word.length, 'link', getBugzillaHref(bzMatch[1]), 'api');
57
- }
58
- }
59
- }
60
- }
61
- wordOffset += word.length;
62
- }
63
- offset += line.length + 1;
64
- }
65
- }
66
- /**
67
- * Registers auto-link behaviour on a Quill instance:
68
- *
69
- * 1. **Type** — after a delimiter character, checks the preceding token
70
- * for URL / case-ID / BZ patterns.
71
- * 2. **Paste** — when the insert is longer than one character, scans the
72
- * full inserted range.
73
- * 3. **Blur** — a full-document scan so trailing tokens without a
74
- * delimiter still get linked before submit.
75
- */
76
- export function registerAutoLinkModule(quill) {
77
- quill.on('text-change', (delta, _oldDelta, source) => {
78
- if (source !== 'user')
79
- return;
80
- const ops = delta.ops;
81
- if (!ops || ops.length === 0)
82
- return;
83
- const lastOp = ops.at(-1);
84
- if (!lastOp || typeof lastOp.insert !== 'string')
85
- return;
86
- const insertedText = lastOp.insert;
87
- // Paste: multi-char insert — scan the full inserted range
88
- if (insertedText.length > 1) {
89
- let insertOffset = 0;
90
- for (const op of ops) {
91
- if (!op.retain)
92
- break;
93
- insertOffset += op.retain;
94
- }
95
- setTimeout(() => linkifyRange(quill, insertOffset, insertedText.length), 0);
96
- return;
97
- }
98
- // Type: single-char delimiter
99
- if (!DELIMITER_RE.test(insertedText))
100
- return;
101
- const selection = quill.getSelection();
102
- if (!selection)
103
- return;
104
- const cursorIndex = selection.index;
105
- const text = quill.getText(0, cursorIndex);
106
- let lineStart = text.lastIndexOf('\n', cursorIndex - 2);
107
- if (lineStart === -1)
108
- lineStart = 0;
109
- else
110
- lineStart += 1;
111
- const lineText = text.slice(lineStart, cursorIndex - 1);
112
- // URL — try first so 8-digit IDs inside URLs are not separately linked
113
- const urlMatch = URL_AT_END_RE.exec(lineText);
114
- if (urlMatch) {
115
- const url = urlMatch[1];
116
- const matchStart = lineStart + lineText.length - url.length;
117
- const format = quill.getFormat(matchStart, url.length);
118
- if (!format.link) {
119
- quill.formatText(matchStart, url.length, 'link', url, 'api');
120
- }
121
- return;
122
- }
123
- // Case ID (8-digit number)
124
- const caseMatch = CASE_ID_RE.exec(lineText);
125
- if (caseMatch) {
126
- const caseId = caseMatch[1];
127
- const matchStart = lineStart + lineText.length - caseId.length;
128
- const format = quill.getFormat(matchStart, caseId.length);
129
- if (!format.link) {
130
- quill.formatText(matchStart, caseId.length, 'link', getCaseIdHref(caseId), 'api');
131
- }
132
- return;
133
- }
134
- // Bugzilla
135
- const bzMatch = BZ_RE.exec(lineText);
136
- if (bzMatch) {
137
- const bugId = bzMatch[1];
138
- const fullMatch = bzMatch[0].trimStart();
139
- const matchStart = lineStart + lineText.length - fullMatch.length;
140
- const format = quill.getFormat(matchStart, fullMatch.length);
141
- if (!format.link) {
142
- quill.formatText(matchStart, fullMatch.length, 'link', getBugzillaHref(bugId), 'api');
143
- }
144
- }
145
- });
146
- }
147
- /**
148
- * Scans the entire document for unlinkified tokens.
149
- * Call this on blur so trailing URLs / IDs without a delimiter are caught.
150
- */
151
- export function linkifyFullDocument(quill) {
152
- const length = quill.getLength();
153
- if (length <= 1)
154
- return;
155
- linkifyRange(quill, 0, length - 1);
156
- }
@@ -1,4 +0,0 @@
1
- export * from './RichTextEditor';
2
- export { useQuillEditor } from './useQuillEditor';
3
- export type { UseQuillEditorOptions } from './useQuillEditor';
4
- //# sourceMappingURL=index.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,YAAY,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,2 +0,0 @@
1
- export * from './RichTextEditor';
2
- export { useQuillEditor } from './useQuillEditor';