@rh-support/components 2.5.226 → 2.5.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/Functional/CaseContactsSelectorExternal.d.ts.map +1 -1
- package/lib/esm/Functional/CaseContactsSelectorExternal.js +33 -13
- package/lib/esm/RichTextEditor/RichTextEditor.d.ts.map +1 -1
- package/lib/esm/RichTextEditor/RichTextEditor.js +4 -3
- package/lib/esm/RichTextEditor/autoLinkModule.d.ts +18 -0
- package/lib/esm/RichTextEditor/autoLinkModule.d.ts.map +1 -0
- package/lib/esm/RichTextEditor/autoLinkModule.js +156 -0
- package/lib/esm/RichTextEditor/linkUrl.d.ts +7 -0
- package/lib/esm/RichTextEditor/linkUrl.d.ts.map +1 -0
- package/lib/esm/RichTextEditor/linkUrl.js +26 -0
- package/lib/esm/RichTextEditor/richTextEditor.css +36 -7
- package/lib/esm/RichTextEditor/useQuillEditor.d.ts.map +1 -1
- package/lib/esm/RichTextEditor/useQuillEditor.js +102 -3
- package/lib/esm/SingleSelectDropdown/SingleSelectDropdown.js +1 -3
- package/lib/esm/ToastNotification/ToastNotification.d.ts +5 -1
- package/lib/esm/ToastNotification/ToastNotification.d.ts.map +1 -1
- package/lib/esm/ToastNotification/ToastNotification.js +16 -3
- package/lib/esm/ToastNotification/toastNotification.css +7 -0
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CaseContactsSelectorExternal.d.ts","sourceRoot":"","sources":["../../../src/Functional/CaseContactsSelectorExternal.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAuB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAcxD,OAAO,KAA4D,MAAM,OAAO,CAAC;AAGjF,OAAO,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAEzF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AACzE,UAAU,MAAO,SAAQ,gBAAgB;IACrC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,CAAC;IACpD,QAAQ,EAAE,CAAC,gBAAgB,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,KAAK,IAAI,CAAC;IAChF,kBAAkB,EAAE,QAAQ,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,MAAM,KAAA,EAAE,KAAK,KAAA,EAAE,GAAG,KAAA,KAAK,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACnF,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;CAClD;AAED,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,eAAO,MAAM,uBAAuB,QAAyB,CAAC;AAE9D,eAAO,MAAM,oBAAoB,GAAI,MAAM,oBAAoB,KAAG,IAAI,IAAI,0BAEzE,CAAC;AAMF,QAAA,MAAM,oBAAoB,GAAI,MAAM,oBAAoB,KAAG,MAa1D,CAAC;AAEF,iBAAS,4BAA4B,CAAC,KAAK,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"CaseContactsSelectorExternal.d.ts","sourceRoot":"","sources":["../../../src/Functional/CaseContactsSelectorExternal.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,yCAAyC,CAAC;AACxF,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAC;AAClE,OAAO,EAAuB,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjF,OAAO,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAcxD,OAAO,KAA4D,MAAM,OAAO,CAAC;AAGjF,OAAO,EAAE,+BAA+B,EAAE,MAAM,wCAAwC,CAAC;AAEzF,MAAM,MAAM,oBAAoB,GAAG,QAAQ,GAAG,0BAA0B,CAAC;AACzE,UAAU,MAAO,SAAQ,gBAAgB;IACrC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,CAAC;IACpD,QAAQ,EAAE,CAAC,gBAAgB,EAAE,CAAC,QAAQ,GAAG,0BAA0B,CAAC,EAAE,KAAK,IAAI,CAAC;IAChF,kBAAkB,EAAE,QAAQ,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,CAAC,MAAM,KAAA,EAAE,KAAK,KAAA,EAAE,GAAG,KAAA,KAAK,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,6BAA6B,EAAE,CAAC;IAC/C,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IAEjC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,+BAA+B,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IACnF,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7C,iBAAiB,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IAChD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;CAClD;AAED,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,eAAO,MAAM,uBAAuB,QAAyB,CAAC;AAE9D,eAAO,MAAM,oBAAoB,GAAI,MAAM,oBAAoB,KAAG,IAAI,IAAI,0BAEzE,CAAC;AAMF,QAAA,MAAM,oBAAoB,GAAI,MAAM,oBAAoB,KAAG,MAa1D,CAAC;AAEF,iBAAS,4BAA4B,CAAC,KAAK,EAAE,MAAM,qBAyUlD;AAED,OAAO,EAAE,4BAA4B,EAAE,oBAAoB,EAAE,CAAC"}
|
|
@@ -130,6 +130,11 @@ function CaseContactsSelectorExternal(props) {
|
|
|
130
130
|
});
|
|
131
131
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
132
|
const debouncedSearch = useCallback(debounce(searchContacts, 400), [apolloClient]);
|
|
133
|
+
const fetchAllActiveContacts = () => {
|
|
134
|
+
currentSearchQuery.current = '';
|
|
135
|
+
debouncedSearch.cancel();
|
|
136
|
+
searchContacts('');
|
|
137
|
+
};
|
|
133
138
|
useEffect(() => {
|
|
134
139
|
const { selectedAccountNumber, loggedInUserRights, isExportingPDF } = props;
|
|
135
140
|
const isNonOrgAdminSameAccount = loggedInUserRights.isExternal() && loggedInUserRights.isNonOrgAdminWithSameAccount(selectedAccountNumber);
|
|
@@ -223,32 +228,47 @@ function CaseContactsSelectorExternal(props) {
|
|
|
223
228
|
})),
|
|
224
229
|
];
|
|
225
230
|
}, [displayedContacts, getCustomEmails]);
|
|
231
|
+
const isOwnerSelectionComplete = (query, options) => {
|
|
232
|
+
if (isEmpty(query)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
const selectedLabel = !isEmpty(selected) ? getHydraContactLabel(selected[0]) : '';
|
|
236
|
+
if (query === selectedLabel) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
return options.some((option) => getHydraContactLabel(option.contact) === query);
|
|
240
|
+
};
|
|
226
241
|
/**
|
|
227
242
|
* update the shown options in the dropdown and reset the amount of options show to the default.
|
|
228
243
|
* @param query The user search term
|
|
229
244
|
*/
|
|
230
245
|
const onQueryUpdated = (query) => {
|
|
231
246
|
setHasNextPage(false);
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
247
|
+
if (isOwnerSelectionComplete(query, allOptions)) {
|
|
248
|
+
fetchAllActiveContacts();
|
|
249
|
+
setFilteredContacts(allOptions);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const localFiltered = filter(allOptions, (option) => getHydraContactLabel(option.contact).toLowerCase().includes(query.toLowerCase()));
|
|
253
|
+
setFilteredContacts(localFiltered);
|
|
254
|
+
debouncedSearch(query);
|
|
255
|
+
};
|
|
256
|
+
const onDropdownOpenChange = (isOpen) => {
|
|
257
|
+
if (!isOpen) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (currentSearchQuery.current) {
|
|
261
|
+
fetchAllActiveContacts();
|
|
236
262
|
}
|
|
237
263
|
else {
|
|
238
|
-
|
|
239
|
-
setFilteredContacts(localFiltered);
|
|
240
|
-
debouncedSearch(query);
|
|
264
|
+
setFilteredContacts(allOptions);
|
|
241
265
|
}
|
|
242
266
|
};
|
|
243
267
|
/**
|
|
244
268
|
* Reset the filtered contacts and the current offset.
|
|
245
269
|
*/
|
|
246
270
|
const onClearQuery = () => {
|
|
247
|
-
|
|
248
|
-
currentSearchQuery.current = '';
|
|
249
|
-
debouncedSearch.cancel();
|
|
250
|
-
searchContacts('');
|
|
251
|
-
}
|
|
271
|
+
fetchAllActiveContacts();
|
|
252
272
|
setFilteredContacts(allOptions);
|
|
253
273
|
};
|
|
254
274
|
/**
|
|
@@ -270,6 +290,6 @@ function CaseContactsSelectorExternal(props) {
|
|
|
270
290
|
useEffect(() => {
|
|
271
291
|
setFilteredContacts(allOptions);
|
|
272
292
|
}, [displayedContacts, customEmails, allOptions, selected]);
|
|
273
|
-
return (React.createElement(OwnerTypeaheadDropdown, { id: props.id || '', hasClearButton: true, multiple: !!props.multiple, contactSsoUsername: contactSsoUsername, groupContactsAndEmails: groupContactsAndEmails, selected: !isEmpty(props.selected) ? props.selected : [], options: filteredContacts, placeholder: props.placeholder || 'Search by name or username', "data-tracking-id": "external-case-contact-selector", isDisabled: isDisabled, status: isInvalid ? 'danger' : undefined, onChipRemoved: onChipRemoved, onClearQuery: onClearQuery, onQueryUpdated: onQueryUpdated, onSelect: onCustomerContactSelect, canAddNew: canAddNew, getCreateNewText: props === null || props === void 0 ? void 0 : props.getCreateNewText, isLoading: isLoading || isLoadingMore, onHandleLoadMore: loadMoreContacts, isShowMoreOptionVisible: hasNextPage }));
|
|
293
|
+
return (React.createElement(OwnerTypeaheadDropdown, { id: props.id || '', hasClearButton: true, multiple: !!props.multiple, contactSsoUsername: contactSsoUsername, groupContactsAndEmails: groupContactsAndEmails, selected: !isEmpty(props.selected) ? props.selected : [], options: filteredContacts, placeholder: props.placeholder || 'Search by name or username', "data-tracking-id": "external-case-contact-selector", isDisabled: isDisabled, status: isInvalid ? 'danger' : undefined, onChipRemoved: onChipRemoved, onClearQuery: onClearQuery, onOpenChange: onDropdownOpenChange, onQueryUpdated: onQueryUpdated, onSelect: onCustomerContactSelect, canAddNew: canAddNew, getCreateNewText: props === null || props === void 0 ? void 0 : props.getCreateNewText, isLoading: isLoading || isLoadingMore, onHandleLoadMore: loadMoreContacts, isShowMoreOptionVisible: hasNextPage }));
|
|
274
294
|
}
|
|
275
295
|
export { CaseContactsSelectorExternal, getHydraContactLabel };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/RichTextEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+B9B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAmD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"RichTextEditor.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/RichTextEditor.tsx"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+B9B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,KAAmD,MAAM,OAAO,CAAC;AAOxE,MAAM,WAAW,oBAAoB;IACjC,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB;IAChB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,kCAAkC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAC;IAClD,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,mBAAmB;IACnB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,gEAAgE;IAChE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,KAAK,IAAI,CAAC;IACrC,uBAAuB;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACnD;AAQD,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,CAAC,oBAAoB,CAAC,qBAovBnE;AAED,eAAe,cAAc,CAAC"}
|
|
@@ -24,6 +24,7 @@ import StrikethroughIcon from '@patternfly/react-icons/dist/js/icons/strikethrou
|
|
|
24
24
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
25
25
|
import { useTranslation } from 'react-i18next';
|
|
26
26
|
import { ToastNotification } from '../ToastNotification';
|
|
27
|
+
import { isValidUrl, normalizeLinkUrl } from './linkUrl';
|
|
27
28
|
import { useQuillEditor } from './useQuillEditor';
|
|
28
29
|
const defaultCharLimit = 32000;
|
|
29
30
|
const defaultMinCharsForCounter = 31800;
|
|
@@ -173,10 +174,10 @@ export function RichTextEditor(props) {
|
|
|
173
174
|
const onLinkInsert = useCallback(() => {
|
|
174
175
|
const quill = quillRef.current;
|
|
175
176
|
const saved = savedSelectionRef.current;
|
|
176
|
-
const url = linkUrl
|
|
177
|
-
const text = linkText.trim() || url;
|
|
177
|
+
const url = normalizeLinkUrl(linkUrl);
|
|
178
178
|
if (!quill || !saved || !url)
|
|
179
179
|
return;
|
|
180
|
+
const text = linkText.trim() || url;
|
|
180
181
|
if (saved.length > 0) {
|
|
181
182
|
quill.setSelection(saved.index, saved.length, 'silent');
|
|
182
183
|
quill.format('link', url);
|
|
@@ -446,7 +447,7 @@ export function RichTextEditor(props) {
|
|
|
446
447
|
React.createElement(FormGroup, { label: "URL", isRequired: true, fieldId: "link-url-input" },
|
|
447
448
|
React.createElement(TextInput, { id: "link-url-input", type: "url", value: linkUrl, onChange: onLinkUrlChange, placeholder: "https://", onKeyDown: onLinkUrlKeyDown }))),
|
|
448
449
|
React.createElement(ModalFooter, null,
|
|
449
|
-
React.createElement(Button, { variant: ButtonVariant.primary, onClick: onLinkInsert, isDisabled: !linkUrl.trim() }, "Insert"),
|
|
450
|
+
React.createElement(Button, { variant: ButtonVariant.primary, onClick: onLinkInsert, isDisabled: !linkUrl.trim() || !isValidUrl(linkUrl.trim()) }, "Insert"),
|
|
450
451
|
React.createElement(Button, { variant: ButtonVariant.link, onClick: onLinkCancel }, "Cancel"))),
|
|
451
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 }),
|
|
452
453
|
!onImageUpload && (React.createElement(Modal, { variant: ModalVariant.small, isOpen: isImageModalOpen, onClose: onImageCancel, "aria-labelledby": "insert-image-title" },
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a normalized http(s)/mailto URL, or null if the value is not allowed.
|
|
3
|
+
* Protocol-less hosts (e.g. example.com) are treated as https.
|
|
4
|
+
*/
|
|
5
|
+
export declare function normalizeLinkUrl(url: string): string | null;
|
|
6
|
+
export declare function isValidUrl(url: string): boolean;
|
|
7
|
+
//# sourceMappingURL=linkUrl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linkUrl.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/linkUrl.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAa3D;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const ALLOWED_PROTOCOLS = new Set(['http:', 'https:', 'mailto:']);
|
|
2
|
+
/**
|
|
3
|
+
* Returns a normalized http(s)/mailto URL, or null if the value is not allowed.
|
|
4
|
+
* Protocol-less hosts (e.g. example.com) are treated as https.
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeLinkUrl(url) {
|
|
7
|
+
const trimmed = url.trim();
|
|
8
|
+
if (!trimmed)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const hasAllowedPrefix = /^https?:\/\//i.test(trimmed) || /^mailto:/i.test(trimmed);
|
|
12
|
+
const candidate = hasAllowedPrefix ? trimmed : `https://${trimmed}`;
|
|
13
|
+
const parsed = new URL(candidate);
|
|
14
|
+
if (!ALLOWED_PROTOCOLS.has(parsed.protocol))
|
|
15
|
+
return null;
|
|
16
|
+
if (parsed.protocol !== 'mailto:' && !parsed.hostname.includes('.'))
|
|
17
|
+
return null;
|
|
18
|
+
return candidate;
|
|
19
|
+
}
|
|
20
|
+
catch (_a) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function isValidUrl(url) {
|
|
25
|
+
return normalizeLinkUrl(url) !== null;
|
|
26
|
+
}
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
|
|
14
14
|
.richtext-editor-content {
|
|
15
15
|
resize: vertical;
|
|
16
|
-
overflow: auto;
|
|
16
|
+
overflow-y: auto;
|
|
17
|
+
overflow-x: hidden;
|
|
17
18
|
font-size: 15px;
|
|
18
19
|
min-width: 100%;
|
|
19
20
|
outline: none;
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
outline: none;
|
|
34
35
|
border: none;
|
|
35
36
|
min-height: 100px;
|
|
37
|
+
overflow-wrap: break-word;
|
|
38
|
+
word-break: break-word;
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
.richtext-editor-content .ql-editor:focus {
|
|
@@ -41,10 +44,12 @@
|
|
|
41
44
|
box-shadow: none;
|
|
42
45
|
}
|
|
43
46
|
|
|
44
|
-
.richtext-editor-content
|
|
47
|
+
.richtext-editor-content .ql-editor.ql-blank::before {
|
|
45
48
|
content: attr(data-placeholder);
|
|
46
49
|
color: var(--pf-t--global--text--color--placeholder, #6a6e73);
|
|
47
50
|
pointer-events: none;
|
|
51
|
+
position: absolute;
|
|
52
|
+
font-style: normal;
|
|
48
53
|
}
|
|
49
54
|
|
|
50
55
|
.richtext-editor-content ul,
|
|
@@ -56,10 +61,34 @@
|
|
|
56
61
|
list-style-type: disc;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
.richtext-editor-content li[data-list='bullet'].ql-indent-1 {
|
|
65
|
+
list-style-type: circle;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.richtext-editor-content li[data-list='bullet'].ql-indent-2 {
|
|
69
|
+
list-style-type: square;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.richtext-editor-content li[data-list='bullet'].ql-indent-3 {
|
|
73
|
+
list-style-type: disc;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.richtext-editor-content li[data-list='bullet'].ql-indent-4 {
|
|
77
|
+
list-style-type: circle;
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
.richtext-editor-content li[data-list='ordered'] {
|
|
60
81
|
list-style-type: decimal;
|
|
61
82
|
}
|
|
62
83
|
|
|
84
|
+
.richtext-editor-content li[data-list='ordered'].ql-indent-1 {
|
|
85
|
+
list-style-type: lower-alpha;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.richtext-editor-content li[data-list='ordered'].ql-indent-2 {
|
|
89
|
+
list-style-type: lower-roman;
|
|
90
|
+
}
|
|
91
|
+
|
|
63
92
|
.richtext-editor-content .ql-align-center {
|
|
64
93
|
text-align: center;
|
|
65
94
|
}
|
|
@@ -73,23 +102,23 @@
|
|
|
73
102
|
}
|
|
74
103
|
|
|
75
104
|
.richtext-editor-content .ql-indent-1 {
|
|
76
|
-
|
|
105
|
+
margin-left: 3em;
|
|
77
106
|
}
|
|
78
107
|
|
|
79
108
|
.richtext-editor-content .ql-indent-2 {
|
|
80
|
-
|
|
109
|
+
margin-left: 6em;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
.richtext-editor-content .ql-indent-3 {
|
|
84
|
-
|
|
113
|
+
margin-left: 9em;
|
|
85
114
|
}
|
|
86
115
|
|
|
87
116
|
.richtext-editor-content .ql-indent-4 {
|
|
88
|
-
|
|
117
|
+
margin-left: 12em;
|
|
89
118
|
}
|
|
90
119
|
|
|
91
120
|
.richtext-editor-content .ql-indent-5 {
|
|
92
|
-
|
|
121
|
+
margin-left: 15em;
|
|
93
122
|
}
|
|
94
123
|
|
|
95
124
|
.richtext-editor-content a {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useQuillEditor.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/useQuillEditor.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"useQuillEditor.d.ts","sourceRoot":"","sources":["../../../src/RichTextEditor/useQuillEditor.ts"],"names":[],"mappings":"AACA,OAAO,KAAmC,MAAM,OAAO,CAAC;AAKxD,MAAM,WAAW,qBAAqB;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAoFD,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB;;;;;wBA+ElB,MAAM;4BAgBF,OAAO;;EASrD"}
|
|
@@ -1,7 +1,84 @@
|
|
|
1
1
|
import DOMPurify from 'dompurify';
|
|
2
2
|
import Quill from 'quill';
|
|
3
3
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { linkifyFullDocument, registerAutoLinkModule } from './autoLinkModule';
|
|
4
5
|
const ALLOWED_FORMATS = ['header', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'image'];
|
|
6
|
+
/**
|
|
7
|
+
* Quill 2 internally represents ALL lists as <ol> and distinguishes bullet vs.
|
|
8
|
+
* ordered via data-list attributes on <li>. root.innerHTML preserves text-entity
|
|
9
|
+
* encoding (important for literal angle-brackets), but outputs this internal
|
|
10
|
+
* format. This helper converts it to semantic <ul>/<ol> and nests indented
|
|
11
|
+
* items before sanitization.
|
|
12
|
+
*/
|
|
13
|
+
function normalizeQuillListHtml(rawHtml) {
|
|
14
|
+
const tmp = document.createElement('div');
|
|
15
|
+
tmp.innerHTML = rawHtml;
|
|
16
|
+
tmp.querySelectorAll('.ql-ui').forEach((el) => el.remove());
|
|
17
|
+
tmp.querySelectorAll('ol').forEach((ol) => {
|
|
18
|
+
const items = Array.from(ol.querySelectorAll(':scope > li'));
|
|
19
|
+
const allBullet = items.length > 0 && items.every((li) => li instanceof HTMLElement && li.dataset.list === 'bullet');
|
|
20
|
+
if (allBullet) {
|
|
21
|
+
const ul = document.createElement('ul');
|
|
22
|
+
for (const attr of Array.from(ol.attributes)) {
|
|
23
|
+
ul.setAttribute(attr.name, attr.value);
|
|
24
|
+
}
|
|
25
|
+
while (ol.firstChild)
|
|
26
|
+
ul.appendChild(ol.firstChild);
|
|
27
|
+
ol.replaceWith(ul);
|
|
28
|
+
}
|
|
29
|
+
items.forEach((li) => {
|
|
30
|
+
if (li instanceof HTMLElement) {
|
|
31
|
+
delete li.dataset.list;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
tmp.querySelectorAll('ul, ol').forEach((list) => {
|
|
36
|
+
if (list instanceof HTMLElement)
|
|
37
|
+
nestIndentedItems(list);
|
|
38
|
+
});
|
|
39
|
+
return tmp.innerHTML;
|
|
40
|
+
}
|
|
41
|
+
function tryNestIndentedItem(item, list) {
|
|
42
|
+
var _a;
|
|
43
|
+
const indentMatch = /ql-indent-(\d+)/.exec((_a = item.className) !== null && _a !== void 0 ? _a : '');
|
|
44
|
+
if (!indentMatch)
|
|
45
|
+
return false;
|
|
46
|
+
const level = Number.parseInt(indentMatch[1], 10);
|
|
47
|
+
item.classList.remove(`ql-indent-${level}`);
|
|
48
|
+
if (!item.className.trim())
|
|
49
|
+
item.removeAttribute('class');
|
|
50
|
+
let prev = item.previousElementSibling;
|
|
51
|
+
while (prev && prev.tagName !== 'LI')
|
|
52
|
+
prev = prev.previousElementSibling;
|
|
53
|
+
if (!prev)
|
|
54
|
+
return false;
|
|
55
|
+
let nested = prev.querySelector(':scope > ul, :scope > ol');
|
|
56
|
+
if (!(nested instanceof HTMLElement)) {
|
|
57
|
+
nested = document.createElement(list.tagName.toLowerCase());
|
|
58
|
+
prev.appendChild(nested);
|
|
59
|
+
}
|
|
60
|
+
nested.appendChild(item);
|
|
61
|
+
if (level > 1)
|
|
62
|
+
item.classList.add(`ql-indent-${level - 1}`);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
function nestIndentedItems(list) {
|
|
66
|
+
let changed = true;
|
|
67
|
+
while (changed) {
|
|
68
|
+
changed = false;
|
|
69
|
+
const items = Array.from(list.querySelectorAll(':scope > li'));
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
if (tryNestIndentedItem(item, list))
|
|
72
|
+
changed = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Newly created nested lists are not in the original querySelectorAll snapshot.
|
|
76
|
+
// Recurse so indent 2+ items become true nested lists instead of leftover ql-indent-* siblings.
|
|
77
|
+
for (const nested of Array.from(list.querySelectorAll(':scope > li > ul, :scope > li > ol'))) {
|
|
78
|
+
if (nested instanceof HTMLElement)
|
|
79
|
+
nestIndentedItems(nested);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
5
82
|
export function useQuillEditor(options) {
|
|
6
83
|
const editorRef = useRef(null);
|
|
7
84
|
const quillRef = useRef(null);
|
|
@@ -14,19 +91,40 @@ export function useQuillEditor(options) {
|
|
|
14
91
|
if (!editorRef.current || quillRef.current)
|
|
15
92
|
return;
|
|
16
93
|
const quill = new Quill(editorRef.current, {
|
|
17
|
-
theme: false,
|
|
18
94
|
modules: {
|
|
19
95
|
toolbar: false,
|
|
96
|
+
keyboard: {
|
|
97
|
+
bindings: {
|
|
98
|
+
indent: {
|
|
99
|
+
key: 'Tab',
|
|
100
|
+
format: ['list'],
|
|
101
|
+
handler(_range, _context) {
|
|
102
|
+
this.quill.format('indent', '+1', 'user');
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
outdent: {
|
|
107
|
+
key: 'Tab',
|
|
108
|
+
shiftKey: true,
|
|
109
|
+
format: ['list'],
|
|
110
|
+
handler(_range, _context) {
|
|
111
|
+
this.quill.format('indent', '-1', 'user');
|
|
112
|
+
return false;
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
20
117
|
clipboard: { matchVisual: false },
|
|
21
118
|
},
|
|
22
119
|
formats: ALLOWED_FORMATS,
|
|
23
120
|
placeholder: optionsRef.current.placeholder,
|
|
24
121
|
readOnly: optionsRef.current.readOnly,
|
|
25
122
|
});
|
|
123
|
+
registerAutoLinkModule(quill);
|
|
26
124
|
quill.on('text-change', () => {
|
|
27
125
|
var _a, _b, _c, _d;
|
|
28
126
|
isInternalChange.current = true;
|
|
29
|
-
const html = DOMPurify.sanitize(quill.
|
|
127
|
+
const html = DOMPurify.sanitize(normalizeQuillListHtml(quill.root.innerHTML));
|
|
30
128
|
(_b = (_a = optionsRef.current).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, html);
|
|
31
129
|
(_d = (_c = optionsRef.current).onChangePlainText) === null || _d === void 0 ? void 0 : _d.call(_c, quill.getText().trim());
|
|
32
130
|
const sel = quill.getSelection();
|
|
@@ -45,6 +143,7 @@ export function useQuillEditor(options) {
|
|
|
45
143
|
}
|
|
46
144
|
}
|
|
47
145
|
else {
|
|
146
|
+
linkifyFullDocument(quill);
|
|
48
147
|
(_f = (_e = optionsRef.current).onBlur) === null || _f === void 0 ? void 0 : _f.call(_e);
|
|
49
148
|
}
|
|
50
149
|
});
|
|
@@ -58,7 +157,7 @@ export function useQuillEditor(options) {
|
|
|
58
157
|
const quill = quillRef.current;
|
|
59
158
|
if (!quill || isInternalChange.current)
|
|
60
159
|
return;
|
|
61
|
-
const currentHtml = DOMPurify.sanitize(quill.
|
|
160
|
+
const currentHtml = DOMPurify.sanitize(normalizeQuillListHtml(quill.root.innerHTML));
|
|
62
161
|
if (currentHtml === html)
|
|
63
162
|
return;
|
|
64
163
|
const selection = quill.getSelection();
|
|
@@ -61,8 +61,6 @@ export function SingleSelectDropdown(props) {
|
|
|
61
61
|
return (React.createElement(Select, Object.assign({ shouldFocusToggleOnSelect: true, shouldPreventScrollOnItemFocus: true, id: id || '', className: className || '', "data-tracking-id": dataTrackingId, isOpen: isOpen, selected: selected, onSelect: onLocalSelect, onOpenChange: (isOpen) => setIsOpen(isOpen), toggle: toggle, popperProps: { direction: 'down', enableFlip: false }, isScrollable: isScrollable, onBlur: onLocalBlur, "aria-label": ariaLabel, zIndex: zIndex, "aria-labelledby": ariaLabelledBy, maxMenuHeight: "20rem" }, restProps),
|
|
62
62
|
React.createElement(SelectList, null, options.map((option, index) => {
|
|
63
63
|
var _a;
|
|
64
|
-
return (React.createElement(SelectOption, { key: option.value, value: option, description: option.description || '',
|
|
65
|
-
// Fix the comparison to compare the entire objects or at least their values
|
|
66
|
-
isSelected: isEqual(selected === null || selected === void 0 ? void 0 : selected.value, option.value), isDisabled: option.disabled, isFocused: focusedItemIndex === index, title: (option === null || option === void 0 ? void 0 : option.title) || option.label }, (_a = option === null || option === void 0 ? void 0 : option.element) !== null && _a !== void 0 ? _a : option.label));
|
|
64
|
+
return (React.createElement(SelectOption, { key: option.value, value: option, description: option.description || '', isSelected: isEqual(selected === null || selected === void 0 ? void 0 : selected.value, option.value), isDisabled: option.disabled, isFocused: focusedItemIndex === index, title: (option === null || option === void 0 ? void 0 : option.title) || option.label, "aria-label": option.label }, (_a = option === null || option === void 0 ? void 0 : option.element) !== null && _a !== void 0 ? _a : option.label));
|
|
67
65
|
}))));
|
|
68
66
|
}
|
|
@@ -9,8 +9,12 @@ export declare enum ToastType {
|
|
|
9
9
|
SUCCESS = "success",
|
|
10
10
|
WARNING = "warning"
|
|
11
11
|
}
|
|
12
|
+
export type ToastActionLinks = React.ReactNode | ((closeToast?: () => void) => React.ReactNode);
|
|
13
|
+
export type ToastNotificationOptions = ToastOptions & {
|
|
14
|
+
actionLinks?: ToastActionLinks;
|
|
15
|
+
};
|
|
12
16
|
declare class ToastNotification {
|
|
13
|
-
static addDangerMessage(message: React.ReactNode, alertBody?: React.ReactNode, options?:
|
|
17
|
+
static addDangerMessage(message: React.ReactNode, alertBody?: React.ReactNode, options?: ToastNotificationOptions): import("react-toastify").ToastId;
|
|
14
18
|
static addSuccessMessage(message: React.ReactNode, alertBody?: React.ReactNode, options?: ToastOptions): import("react-toastify").ToastId;
|
|
15
19
|
static addWarningMessage(message: any, alertBody?: React.ReactNode | string, options?: ToastOptions): import("react-toastify").ToastId;
|
|
16
20
|
static addInfoMessage(message: any, options?: ToastOptions): import("react-toastify").ToastId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToastNotification.d.ts","sourceRoot":"","sources":["../../../src/ToastNotification/ToastNotification.tsx"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,yBAAyB,CAAC;AAIjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAS,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAA+C,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEzG,oBAAY,SAAS;IACjB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,OAAO,YAAY;CACtB;
|
|
1
|
+
{"version":3,"file":"ToastNotification.d.ts","sourceRoot":"","sources":["../../../src/ToastNotification/ToastNotification.tsx"],"names":[],"mappings":"AAAA,OAAO,2CAA2C,CAAC;AACnD,OAAO,yBAAyB,CAAC;AAIjC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAS,cAAc,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErE,OAAO,EAA+C,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAEzG,oBAAY,SAAS;IACjB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,OAAO,YAAY;CACtB;AAED,MAAM,MAAM,gBAAgB,GAAG,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;AAEhG,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG;IAClD,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAClC,CAAC;AA8BF,cAAM,iBAAiB;WACL,gBAAgB,CAC1B,OAAO,EAAE,KAAK,CAAC,SAAS,EACxB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,EAC3B,OAAO,GAAE,wBAA6B;WAoB5B,iBAAiB,CAC3B,OAAO,EAAE,KAAK,CAAC,SAAS,EACxB,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,EAC3B,OAAO,GAAE,YAAkC;WAajC,iBAAiB,CAAC,OAAO,KAAA,EAAE,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,EAAE,OAAO,GAAE,YAAiB;WAY3F,cAAc,CAAC,OAAO,KAAA,EAAE,OAAO,GAAE,YAAkC;WAQnE,kBAAkB,CAAC,KAAK,KAAA,EAAE,eAAe,CAAC,EAAE,SAAS;WAOrD,QAAQ;WAIR,gBAAgB,CAAC,cAAc,EAAE,MAAM;WAIvC,UAAU,CAAC,OAAO,KAAA;IAIhC;;;OAGG;WACW,kBAAkB,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,KAAK,CAAC,SAAS,EAAE;CAG1F;AAED,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -1,3 +1,14 @@
|
|
|
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
|
+
};
|
|
1
12
|
import 'react-toastify/dist/ReactToastify.min.css';
|
|
2
13
|
import './toastNotification.css';
|
|
3
14
|
import { Alert, AlertActionCloseButton } from '@patternfly/react-core';
|
|
@@ -12,16 +23,18 @@ export var ToastType;
|
|
|
12
23
|
ToastType["SUCCESS"] = "success";
|
|
13
24
|
ToastType["WARNING"] = "warning";
|
|
14
25
|
})(ToastType || (ToastType = {}));
|
|
15
|
-
const AlertComponent = ({ variant, message, alertActions
|
|
16
|
-
return (React.createElement(Alert, { variant: variant, title: message, actionClose: React.createElement(AlertActionCloseButton, { onClose: closeToast }) }, children));
|
|
26
|
+
const AlertComponent = ({ variant, message, alertActions, closeToast, children }) => {
|
|
27
|
+
return (React.createElement(Alert, { variant: variant, title: message, actionClose: React.createElement(AlertActionCloseButton, { onClose: closeToast }), actionLinks: alertActions }, children));
|
|
17
28
|
};
|
|
29
|
+
const resolveActionLinks = (actionLinks, closeToast) => typeof actionLinks === 'function' ? actionLinks(closeToast) : actionLinks;
|
|
18
30
|
const defaultCloseMs = 8000;
|
|
19
31
|
const defaultToastOptions = { autoClose: defaultCloseMs };
|
|
20
32
|
class ToastNotification {
|
|
21
33
|
static addDangerMessage(message, alertBody, options = {}) {
|
|
34
|
+
const { actionLinks } = options, toastOptions = __rest(options, ["actionLinks"]);
|
|
22
35
|
return toast(
|
|
23
36
|
//@ts-ignore
|
|
24
|
-
({ closeToast }) => (React.createElement(AlertComponent, { variant: ToastType.DANGER, message: message, closeToast: closeToast }, alertBody)), Object.assign({},
|
|
37
|
+
({ closeToast }) => (React.createElement(AlertComponent, { variant: ToastType.DANGER, message: message, closeToast: closeToast, alertActions: resolveActionLinks(actionLinks, closeToast) }, alertBody)), Object.assign({}, toastOptions));
|
|
25
38
|
}
|
|
26
39
|
// removed options: ToastOptions = defaultToastOptions to fix auto-dismissing of success alerts.
|
|
27
40
|
static addSuccessMessage(message, alertBody, options = defaultToastOptions) {
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
.Toastify__toast {
|
|
16
16
|
background: none;
|
|
17
|
+
/* react-toastify sets sans-serif; match alert title (h4 → Red Hat Display) */
|
|
18
|
+
font-family: var(--pf-t--global--font--family--heading, inherit);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
.Toastify__toast-body {
|
|
@@ -24,3 +26,8 @@
|
|
|
24
26
|
height: 100%;
|
|
25
27
|
min-width: 500px;
|
|
26
28
|
}
|
|
29
|
+
|
|
30
|
+
.Toastify__toast-body > .pf-v6-c-alert .pf-v6-c-alert__description,
|
|
31
|
+
.Toastify__toast-body > .pf-v6-c-alert .pf-v6-c-alert__action-group {
|
|
32
|
+
font-family: inherit;
|
|
33
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rh-support/components",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.230",
|
|
4
4
|
"description": "Contains all reusabel components for support app",
|
|
5
5
|
"author": "Vikas Rathee <vrathee@redhat.com>",
|
|
6
6
|
"license": "ISC",
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"@patternfly/react-icons": "6.2.1",
|
|
63
63
|
"@patternfly/react-table": "6.2.1",
|
|
64
64
|
"@rh-support/types": "2.0.26",
|
|
65
|
-
"@rh-support/user-permissions": "2.5.
|
|
66
|
-
"@rh-support/utils": "2.5.
|
|
65
|
+
"@rh-support/user-permissions": "2.5.178",
|
|
66
|
+
"@rh-support/utils": "2.5.157",
|
|
67
67
|
"dompurify": "^2.2.6",
|
|
68
68
|
"js-worker-search": "^1.4.1",
|
|
69
69
|
"lazysizes": "^5.3.2",
|
|
@@ -103,5 +103,5 @@
|
|
|
103
103
|
"defaults and supports es6-module",
|
|
104
104
|
"maintained node versions"
|
|
105
105
|
],
|
|
106
|
-
"gitHead": "
|
|
106
|
+
"gitHead": "78790ed8b35b8acbc7a3a4a182d212499a0cd181"
|
|
107
107
|
}
|