@patternfly/quickstarts 6.3.0-prerelease.1 → 6.3.0-prerelease.10
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/dist/ConsoleInternal/components/markdown-view.d.ts +2 -2
- package/dist/ConsoleInternal/components/utils/camel-case-wrap.d.ts +1 -1
- package/dist/ConsoleInternal/components/utils/status-box.d.ts +3 -3
- package/dist/ConsoleShared/src/components/markdown-extensions/MarkdownCopyClipboard.d.ts +3 -3
- package/dist/ConsoleShared/src/components/markdown-extensions/accordion-extension.d.ts +1 -1
- package/dist/ConsoleShared/src/components/markdown-extensions/accordion-render-extension.d.ts +2 -2
- package/dist/ConsoleShared/src/components/markdown-extensions/admonition-extension.d.ts +1 -1
- package/dist/ConsoleShared/src/components/markdown-highlight-extension/MarkdownHighlightExtension.d.ts +2 -2
- package/dist/ConsoleShared/src/components/modal/Modal.d.ts +3 -3
- package/dist/ConsoleShared/src/components/popper/Portal.d.ts +3 -3
- package/dist/ConsoleShared/src/components/popper/SimplePopper.d.ts +3 -3
- package/dist/ConsoleShared/src/components/spotlight/InteractiveSpotlight.d.ts +3 -2
- package/dist/ConsoleShared/src/components/spotlight/Spotlight.d.ts +2 -2
- package/dist/ConsoleShared/src/components/spotlight/StaticSpotlight.d.ts +3 -2
- package/dist/ConsoleShared/src/components/status/GenericStatus.d.ts +4 -4
- package/dist/ConsoleShared/src/components/status/NotStartedIcon.d.ts +1 -2
- package/dist/ConsoleShared/src/components/status/PopoverStatus.d.ts +4 -4
- package/dist/ConsoleShared/src/components/status/Status.d.ts +3 -3
- package/dist/ConsoleShared/src/components/status/StatusIconAndText.d.ts +3 -3
- package/dist/ConsoleShared/src/components/status/icons.d.ts +4 -4
- package/dist/ConsoleShared/src/components/status/statuses.d.ts +4 -4
- package/dist/ConsoleShared/src/components/utils/FallbackImg.d.ts +3 -3
- package/dist/ConsoleShared/src/utils/useCombineRefs.d.ts +2 -2
- package/dist/HelpTopicDrawer.d.ts +3 -3
- package/dist/HelpTopicPanelContent.d.ts +2 -2
- package/dist/QuickStartCatalogPage.d.ts +3 -3
- package/dist/QuickStartCloseModal.d.ts +2 -2
- package/dist/QuickStartContainer.d.ts +2 -2
- package/dist/QuickStartController.d.ts +2 -2
- package/dist/QuickStartDrawer.d.ts +2 -2
- package/dist/QuickStartDrawerContent.d.ts +2 -2
- package/dist/QuickStartMarkdownView.d.ts +2 -2
- package/dist/QuickStartPanelContent.d.ts +2 -2
- package/dist/catalog/Catalog/QuickStartCatalogHeader.d.ts +2 -2
- package/dist/catalog/Catalog/QuickStartCatalogSection.d.ts +2 -2
- package/dist/catalog/Catalog/QuickStartCatalogToolbar.d.ts +2 -2
- package/dist/catalog/QuickStartCatalog.d.ts +2 -2
- package/dist/catalog/QuickStartTile.d.ts +2 -2
- package/dist/catalog/QuickStartTileDescription.d.ts +2 -2
- package/dist/catalog/QuickStartTileFooter.d.ts +2 -2
- package/dist/catalog/QuickStartTileFooterExternal.d.ts +2 -2
- package/dist/catalog/QuickStartTileHeader.d.ts +2 -2
- package/dist/catalog/Toolbar/QuickStartCatalogFilter.d.ts +2 -2
- package/dist/catalog/Toolbar/QuickStartCatalogFilterItems.d.ts +7 -7
- package/dist/controller/QuickStartConclusion.d.ts +2 -2
- package/dist/controller/QuickStartContent.d.ts +2 -2
- package/dist/controller/QuickStartFooter.d.ts +2 -2
- package/dist/controller/QuickStartIntroduction.d.ts +2 -2
- package/dist/controller/QuickStartTaskHeader.d.ts +2 -2
- package/dist/controller/QuickStartTaskHeaderList.d.ts +2 -2
- package/dist/controller/QuickStartTaskReview.d.ts +2 -2
- package/dist/controller/QuickStartTasks.d.ts +2 -2
- package/dist/index.es.js +489 -561
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +500 -574
- package/dist/index.js.map +1 -1
- package/dist/quickstarts-base.css +61 -0
- package/dist/quickstarts-full.es.js +1963 -693
- package/dist/quickstarts-full.es.js.map +1 -1
- package/dist/quickstarts.css +61 -0
- package/dist/quickstarts.min.css +1 -1
- package/dist/utils/help-topic-context.d.ts +2 -2
- package/dist/utils/quick-start-context.d.ts +3 -3
- package/package.json +5 -6
- package/src/ConsoleInternal/components/markdown-view.tsx +112 -22
- package/src/ConsoleInternal/components/utils/camel-case-wrap.tsx +3 -3
- package/src/ConsoleInternal/components/utils/status-box.tsx +4 -4
- package/src/ConsoleShared/src/components/markdown-extensions/MarkdownCopyClipboard.tsx +8 -15
- package/src/ConsoleShared/src/components/markdown-extensions/__tests__/MarkdownCopyClipboard.spec.tsx +0 -1
- package/src/ConsoleShared/src/components/markdown-extensions/__tests__/accordion-extension.spec.tsx +105 -0
- package/src/ConsoleShared/src/components/markdown-extensions/__tests__/admonition-extension.spec.tsx +121 -0
- package/src/ConsoleShared/src/components/markdown-extensions/accordion-extension.tsx +22 -15
- package/src/ConsoleShared/src/components/markdown-extensions/accordion-render-extension.tsx +23 -9
- package/src/ConsoleShared/src/components/markdown-extensions/admonition-extension.tsx +19 -8
- package/src/ConsoleShared/src/components/markdown-extensions/code-extension.tsx +2 -2
- package/src/ConsoleShared/src/components/markdown-extensions/inline-clipboard-extension.tsx +3 -3
- package/src/ConsoleShared/src/components/markdown-extensions/multiline-clipboard-extension.tsx +3 -3
- package/src/ConsoleShared/src/components/markdown-highlight-extension/MarkdownHighlightExtension.tsx +5 -5
- package/src/ConsoleShared/src/components/modal/Modal.tsx +3 -3
- package/src/ConsoleShared/src/components/popper/Portal.tsx +5 -5
- package/src/ConsoleShared/src/components/popper/SimplePopper.tsx +15 -15
- package/src/ConsoleShared/src/components/spotlight/InteractiveSpotlight.tsx +6 -5
- package/src/ConsoleShared/src/components/spotlight/Spotlight.tsx +3 -3
- package/src/ConsoleShared/src/components/spotlight/StaticSpotlight.tsx +4 -3
- package/src/ConsoleShared/src/components/spotlight/spotlight.scss +63 -0
- package/src/ConsoleShared/src/components/status/GenericStatus.tsx +5 -5
- package/src/ConsoleShared/src/components/status/NotStartedIcon.tsx +0 -1
- package/src/ConsoleShared/src/components/status/PopoverStatus.tsx +4 -4
- package/src/ConsoleShared/src/components/status/Status.tsx +3 -11
- package/src/ConsoleShared/src/components/status/StatusIconAndText.tsx +6 -6
- package/src/ConsoleShared/src/components/status/icons.tsx +4 -8
- package/src/ConsoleShared/src/components/status/statuses.tsx +4 -5
- package/src/ConsoleShared/src/components/utils/FallbackImg.tsx +4 -4
- package/src/ConsoleShared/src/hooks/scroll.ts +4 -4
- package/src/ConsoleShared/src/hooks/useBoundingClientRect.ts +3 -3
- package/src/ConsoleShared/src/hooks/useForceRender.ts +2 -2
- package/src/ConsoleShared/src/hooks/useResizeObserver.ts +3 -6
- package/src/ConsoleShared/src/hooks/useScrollShadows.ts +4 -4
- package/src/ConsoleShared/src/utils/useCombineRefs.ts +4 -4
- package/src/HelpTopicDrawer.tsx +6 -6
- package/src/HelpTopicPanelContent.tsx +4 -4
- package/src/QuickStartCatalogPage.tsx +9 -9
- package/src/QuickStartCloseModal.tsx +3 -7
- package/src/QuickStartContainer.tsx +4 -4
- package/src/QuickStartController.tsx +11 -11
- package/src/QuickStartDrawer.tsx +6 -6
- package/src/QuickStartDrawerContent.tsx +6 -4
- package/src/QuickStartMarkdownView.tsx +3 -3
- package/src/QuickStartPanelContent.tsx +8 -8
- package/src/catalog/Catalog/QuickStartCatalogHeader.tsx +2 -2
- package/src/catalog/Catalog/QuickStartCatalogSection.tsx +2 -2
- package/src/catalog/Catalog/QuickStartCatalogToolbar.tsx +2 -2
- package/src/catalog/QuickStartCatalog.tsx +3 -3
- package/src/catalog/QuickStartTile.tsx +4 -4
- package/src/catalog/QuickStartTileDescription.tsx +4 -4
- package/src/catalog/QuickStartTileFooter.tsx +6 -6
- package/src/catalog/QuickStartTileFooterExternal.tsx +2 -5
- package/src/catalog/QuickStartTileHeader.tsx +2 -6
- package/src/catalog/Toolbar/QuickStartCatalogFilter.tsx +2 -2
- package/src/catalog/Toolbar/QuickStartCatalogFilterItems.tsx +17 -20
- package/src/catalog/__tests__/QuickStartCatalog.spec.tsx +0 -1
- package/src/catalog/__tests__/QuickStartTile.spec.tsx +0 -1
- package/src/catalog/__tests__/QuickStartTileDescription.spec.tsx +1 -2
- package/src/controller/QuickStartConclusion.tsx +3 -3
- package/src/controller/QuickStartContent.tsx +2 -2
- package/src/controller/QuickStartFooter.tsx +10 -11
- package/src/controller/QuickStartIntroduction.tsx +5 -5
- package/src/controller/QuickStartTaskHeader.tsx +5 -5
- package/src/controller/QuickStartTaskHeaderList.tsx +2 -2
- package/src/controller/QuickStartTaskReview.tsx +4 -4
- package/src/controller/QuickStartTasks.tsx +5 -5
- package/src/controller/__tests__/QuickStartConclusion.spec.tsx +3 -3
- package/src/controller/__tests__/QuickStartContent.spec.tsx +2 -2
- package/src/controller/__tests__/QuickStartTaskHeader.spec.tsx +2 -2
- package/src/controller/__tests__/QuickStartTaskReview.spec.tsx +2 -2
- package/src/utils/help-topic-context.tsx +7 -10
- package/src/utils/quick-start-context.tsx +11 -11
package/src/ConsoleShared/src/components/markdown-extensions/__tests__/admonition-extension.spec.tsx
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react';
|
|
2
|
+
import useAdmonitionShowdownExtension from '../admonition-extension';
|
|
3
|
+
import { marked } from 'marked';
|
|
4
|
+
|
|
5
|
+
// Mock marked
|
|
6
|
+
jest.mock('marked', () => ({
|
|
7
|
+
marked: {
|
|
8
|
+
parseInline: jest.fn((text) => `<strong>${text}</strong>`),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
// Mock DOMPurify
|
|
13
|
+
jest.mock('dompurify', () => ({
|
|
14
|
+
sanitize: jest.fn((html) => html),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
describe('useAdmonitionShowdownExtension', () => {
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
jest.clearAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return a showdown extension with correct properties', () => {
|
|
23
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
24
|
+
const extension = result.current;
|
|
25
|
+
|
|
26
|
+
expect(extension.type).toBe('lang');
|
|
27
|
+
expect(extension.regex).toEqual(/\[(.+)]{{(admonition) ([\w-]+)}}/g);
|
|
28
|
+
expect(typeof extension.replace).toBe('function');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should match different admonition types', () => {
|
|
32
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
33
|
+
const { regex } = result.current;
|
|
34
|
+
|
|
35
|
+
const admonitionTypes = ['note', 'tip', 'important', 'warning', 'caution', 'custom-type'];
|
|
36
|
+
|
|
37
|
+
admonitionTypes.forEach((type) => {
|
|
38
|
+
const testText = `[Content for ${type}]{{admonition ${type}}}`;
|
|
39
|
+
// Reset regex state for global flag
|
|
40
|
+
regex.lastIndex = 0;
|
|
41
|
+
const matches = regex.exec(testText);
|
|
42
|
+
|
|
43
|
+
expect(matches).not.toBeNull();
|
|
44
|
+
if (matches) {
|
|
45
|
+
expect(matches[1]).toBe(`Content for ${type}`);
|
|
46
|
+
expect(matches[2]).toBe('admonition');
|
|
47
|
+
expect(matches[3]).toBe(type);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should not match malformed admonition syntax', () => {
|
|
53
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
54
|
+
const { regex } = result.current;
|
|
55
|
+
|
|
56
|
+
const malformedCases = [
|
|
57
|
+
'Content]{{admonition note}}',
|
|
58
|
+
'[Content{{admonition note}}',
|
|
59
|
+
'[Content]{{admonition}}',
|
|
60
|
+
'[Content]{{notadmonition note}}',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
malformedCases.forEach((testCase) => {
|
|
64
|
+
expect(testCase.match(regex)).toBeNull();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should generate correct alert HTML structure', () => {
|
|
69
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
70
|
+
const { replace } = result.current;
|
|
71
|
+
|
|
72
|
+
const html = replace('[Test message]{{admonition note}}', 'Test message', 'admonition', 'note');
|
|
73
|
+
|
|
74
|
+
expect(html).toContain('pf-v6-c-alert');
|
|
75
|
+
expect(html).toContain('pf-m-info'); // note maps to info variant
|
|
76
|
+
expect(html).toContain('pf-m-inline');
|
|
77
|
+
expect(html).toContain('pfext-markdown-admonition');
|
|
78
|
+
expect(html).toContain('NOTE'); // uppercase title
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle different admonition types with correct variants', () => {
|
|
82
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
83
|
+
const { replace } = result.current;
|
|
84
|
+
|
|
85
|
+
const testCases = [
|
|
86
|
+
{ type: 'note', expectedClass: 'pf-m-info' },
|
|
87
|
+
{ type: 'warning', expectedClass: 'pf-m-warning' },
|
|
88
|
+
{ type: 'important', expectedClass: 'pf-m-danger' },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
testCases.forEach(({ type, expectedClass }) => {
|
|
92
|
+
const html = replace(`[Content]{{admonition ${type}}}`, 'Content', 'admonition', type);
|
|
93
|
+
|
|
94
|
+
expect(html).toContain(expectedClass);
|
|
95
|
+
expect(html).toContain(type.toUpperCase());
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should process content through marked', () => {
|
|
100
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
101
|
+
const { replace } = result.current;
|
|
102
|
+
|
|
103
|
+
replace('[**Bold text**]{{admonition note}}', '**Bold text**', 'admonition', 'note');
|
|
104
|
+
|
|
105
|
+
expect(marked.parseInline).toHaveBeenCalledWith('**Bold text**');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return original text for invalid cases', () => {
|
|
109
|
+
const { result } = renderHook(() => useAdmonitionShowdownExtension());
|
|
110
|
+
const { replace } = result.current;
|
|
111
|
+
|
|
112
|
+
// Missing content
|
|
113
|
+
const originalText = '[Content]{{admonition note}}';
|
|
114
|
+
const result1 = replace(originalText, '', 'admonition', 'note');
|
|
115
|
+
expect(result1).toBe(originalText);
|
|
116
|
+
|
|
117
|
+
// Wrong command
|
|
118
|
+
const result2 = replace(originalText, 'Content', 'not-admonition', 'note');
|
|
119
|
+
expect(result2).toBe(originalText);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Accordion,
|
|
4
4
|
AccordionItem,
|
|
@@ -8,34 +8,41 @@ import {
|
|
|
8
8
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
9
9
|
import { removeTemplateWhitespace } from './utils';
|
|
10
10
|
import { ACCORDION_MARKDOWN_BUTTON_ID, ACCORDION_MARKDOWN_CONTENT_ID } from './const';
|
|
11
|
+
import { marked } from 'marked';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
14
|
+
const DOMPurify = require('dompurify');
|
|
11
15
|
|
|
12
16
|
const useAccordionShowdownExtension = () =>
|
|
13
|
-
|
|
17
|
+
useMemo(
|
|
14
18
|
() => ({
|
|
15
19
|
type: 'lang',
|
|
16
|
-
regex: /\[(.+)]{{(accordion) (
|
|
20
|
+
regex: /\[(.+)]{{(accordion) ("(.*?)")}}/g,
|
|
17
21
|
replace: (
|
|
18
22
|
_text: string,
|
|
19
23
|
accordionContent: string,
|
|
20
24
|
_command: string,
|
|
25
|
+
_quotedHeading: string,
|
|
21
26
|
accordionHeading: string,
|
|
22
27
|
): string => {
|
|
23
28
|
const accordionId = String(accordionHeading).replace(/\s/g, '-');
|
|
24
29
|
|
|
30
|
+
// Process accordion content with markdown
|
|
31
|
+
const processedContent = marked.parseInline(accordionContent);
|
|
32
|
+
const sanitizedContent = DOMPurify.sanitize(processedContent);
|
|
33
|
+
|
|
25
34
|
return removeTemplateWhitespace(
|
|
26
35
|
renderToStaticMarkup(
|
|
27
|
-
|
|
28
|
-
<
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</Accordion>
|
|
38
|
-
</>,
|
|
36
|
+
<Accordion>
|
|
37
|
+
<AccordionItem>
|
|
38
|
+
<AccordionToggle id={`${ACCORDION_MARKDOWN_BUTTON_ID}-${accordionId}`}>
|
|
39
|
+
{accordionHeading}
|
|
40
|
+
</AccordionToggle>
|
|
41
|
+
<AccordionContent id={`${ACCORDION_MARKDOWN_CONTENT_ID}-${accordionId}`} hidden>
|
|
42
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
|
|
43
|
+
</AccordionContent>
|
|
44
|
+
</AccordionItem>
|
|
45
|
+
</Accordion>,
|
|
39
46
|
),
|
|
40
47
|
);
|
|
41
48
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FC, useState } from 'react';
|
|
2
2
|
import { useEventListener } from '../../hooks';
|
|
3
3
|
import { ACCORDION_MARKDOWN_BUTTON_ID, ACCORDION_MARKDOWN_CONTENT_ID } from './const';
|
|
4
4
|
|
|
@@ -7,21 +7,35 @@ interface AccordionShowdownComponentProps {
|
|
|
7
7
|
contentElement: HTMLElement;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const AccordionShowdownHandler:
|
|
10
|
+
const AccordionShowdownHandler: FC<AccordionShowdownComponentProps> = ({
|
|
11
11
|
buttonElement,
|
|
12
12
|
contentElement,
|
|
13
13
|
}) => {
|
|
14
|
-
const [expanded, setExpanded] =
|
|
14
|
+
const [expanded, setExpanded] = useState<boolean>(false);
|
|
15
15
|
|
|
16
16
|
const handleClick = () => {
|
|
17
|
+
const newExpanded = !expanded;
|
|
17
18
|
const expandedModifier = 'pf-m-expanded';
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
// Find the accordion item element (parent of the button)
|
|
21
|
+
const accordionItem = buttonElement.closest('.pf-v6-c-accordion__item');
|
|
22
|
+
|
|
23
|
+
// Update button - both visual state and aria-expanded
|
|
24
|
+
buttonElement.className = `pf-v6-c-accordion__toggle ${newExpanded ? expandedModifier : ''}`;
|
|
25
|
+
buttonElement.setAttribute('aria-expanded', newExpanded.toString());
|
|
26
|
+
|
|
27
|
+
// Update content - both visual state and hidden attribute
|
|
28
|
+
contentElement.hidden = !newExpanded;
|
|
29
|
+
contentElement.className = `pf-v6-c-accordion__expandable-content ${
|
|
30
|
+
newExpanded ? expandedModifier : ''
|
|
23
31
|
}`;
|
|
24
|
-
|
|
32
|
+
|
|
33
|
+
// Update accordion item
|
|
34
|
+
if (accordionItem) {
|
|
35
|
+
accordionItem.className = `pf-v6-c-accordion__item ${newExpanded ? expandedModifier : ''}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
setExpanded(newExpanded);
|
|
25
39
|
};
|
|
26
40
|
|
|
27
41
|
useEventListener(buttonElement, 'click', handleClick);
|
|
@@ -33,7 +47,7 @@ interface accordionRenderExtensionProps {
|
|
|
33
47
|
docContext: Document;
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
const AccordionRenderExtension:
|
|
50
|
+
const AccordionRenderExtension: FC<accordionRenderExtensionProps> = ({ docContext }) => {
|
|
37
51
|
const buttonElements = docContext.querySelectorAll(`[id ^= ${ACCORDION_MARKDOWN_BUTTON_ID}]`);
|
|
38
52
|
const contentElements = docContext.querySelectorAll(`[id ^= ${ACCORDION_MARKDOWN_CONTENT_ID}]`);
|
|
39
53
|
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
import { removeTemplateWhitespace } from './utils';
|
|
3
3
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
4
|
import { Alert } from '@patternfly/react-core';
|
|
5
5
|
import LightbulbIcon from '@patternfly/react-icons/dist/js/icons/lightbulb-icon';
|
|
6
6
|
import FireIcon from '@patternfly/react-icons/dist/js/icons/fire-icon';
|
|
7
|
-
import
|
|
7
|
+
import { marked } from 'marked';
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
10
|
+
const DOMPurify = require('dompurify');
|
|
8
11
|
|
|
9
12
|
enum AdmonitionType {
|
|
10
13
|
TIP = 'TIP',
|
|
@@ -24,7 +27,7 @@ const admonitionToAlertVariantMap = {
|
|
|
24
27
|
|
|
25
28
|
const useAdmonitionShowdownExtension = () =>
|
|
26
29
|
// const { getResource } = React.useContext<QuickStartContextValues>(QuickStartContext);
|
|
27
|
-
|
|
30
|
+
useMemo(
|
|
28
31
|
() => ({
|
|
29
32
|
type: 'lang',
|
|
30
33
|
regex: /\[(.+)]{{(admonition) ([\w-]+)}}/g,
|
|
@@ -33,15 +36,23 @@ const useAdmonitionShowdownExtension = () =>
|
|
|
33
36
|
content: string,
|
|
34
37
|
admonitionLabel: string,
|
|
35
38
|
admonitionType: string,
|
|
36
|
-
groupId: string,
|
|
37
39
|
): string => {
|
|
38
|
-
if (!content || !admonitionLabel || !admonitionType
|
|
40
|
+
if (!content || !admonitionLabel || !admonitionType) {
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
if (admonitionLabel !== 'admonition') {
|
|
39
44
|
return text;
|
|
40
45
|
}
|
|
41
46
|
admonitionType = admonitionType.toUpperCase();
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
const
|
|
48
|
+
// Process markdown content directly using marked
|
|
49
|
+
const processedContent = marked.parseInline(content);
|
|
50
|
+
const sanitizedContent = DOMPurify.sanitize(processedContent);
|
|
51
|
+
|
|
52
|
+
// Handle unknown admonition types by defaulting to NOTE
|
|
53
|
+
const admonitionConfig =
|
|
54
|
+
admonitionToAlertVariantMap[admonitionType] || admonitionToAlertVariantMap.NOTE;
|
|
55
|
+
const { variant, customIcon } = admonitionConfig;
|
|
45
56
|
const pfAlert = (
|
|
46
57
|
<Alert
|
|
47
58
|
variant={variant}
|
|
@@ -50,7 +61,7 @@ const useAdmonitionShowdownExtension = () =>
|
|
|
50
61
|
title={admonitionType}
|
|
51
62
|
className="pfext-markdown-admonition"
|
|
52
63
|
>
|
|
53
|
-
{
|
|
64
|
+
<div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />
|
|
54
65
|
</Alert>
|
|
55
66
|
);
|
|
56
67
|
return removeTemplateWhitespace(renderToStaticMarkup(pfAlert));
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
2
|
import { removeTemplateWhitespace } from './utils';
|
|
3
3
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
4
4
|
import { CodeBlock } from '@patternfly/react-core';
|
|
5
5
|
|
|
6
6
|
const useCodeShowdownExtension = () =>
|
|
7
|
-
|
|
7
|
+
useMemo(
|
|
8
8
|
() => ({
|
|
9
9
|
type: 'output',
|
|
10
10
|
regex: /<pre><code>(.*?)\n?<\/code><\/pre>/g,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useContext, useMemo } from 'react';
|
|
2
2
|
import { QuickStartContext, QuickStartContextValues } from '@quickstarts/utils/quick-start-context';
|
|
3
3
|
import { MARKDOWN_COPY_BUTTON_ID, MARKDOWN_SNIPPET_ID } from './const';
|
|
4
4
|
import { removeTemplateWhitespace } from './utils';
|
|
@@ -6,8 +6,8 @@ import { renderToStaticMarkup } from 'react-dom/server';
|
|
|
6
6
|
import CopyIcon from '@patternfly/react-icons/dist/js/icons/copy-icon';
|
|
7
7
|
|
|
8
8
|
const useInlineCopyClipboardShowdownExtension = () => {
|
|
9
|
-
const { getResource } =
|
|
10
|
-
return
|
|
9
|
+
const { getResource } = useContext<QuickStartContextValues>(QuickStartContext);
|
|
10
|
+
return useMemo(
|
|
11
11
|
() => ({
|
|
12
12
|
type: 'lang',
|
|
13
13
|
regex: /`([^`](.*?)[^`])`{{copy}}/g,
|
package/src/ConsoleShared/src/components/markdown-extensions/multiline-clipboard-extension.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/* eslint-disable max-len */
|
|
2
|
-
import
|
|
2
|
+
import { useContext, useMemo } from 'react';
|
|
3
3
|
import { QuickStartContext, QuickStartContextValues } from '@quickstarts/utils/quick-start-context';
|
|
4
4
|
import { MARKDOWN_COPY_BUTTON_ID, MARKDOWN_SNIPPET_ID } from './const';
|
|
5
5
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
6
6
|
import CopyIcon from '@patternfly/react-icons/dist/js/icons/copy-icon';
|
|
7
7
|
|
|
8
8
|
const useMultilineCopyClipboardShowdownExtension = () => {
|
|
9
|
-
const { getResource } =
|
|
10
|
-
return
|
|
9
|
+
const { getResource } = useContext<QuickStartContextValues>(QuickStartContext);
|
|
10
|
+
return useMemo(
|
|
11
11
|
() => ({
|
|
12
12
|
type: 'lang',
|
|
13
13
|
regex: /```[\n]\s*((((?!```).)*?\n)+)\s*```{{copy}}/g,
|
package/src/ConsoleShared/src/components/markdown-highlight-extension/MarkdownHighlightExtension.tsx
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FC, useEffect, useState } from 'react';
|
|
2
2
|
import { Spotlight } from '../spotlight';
|
|
3
3
|
|
|
4
4
|
interface MarkdownHighlightExtensionProps {
|
|
5
5
|
docContext: Document;
|
|
6
6
|
rootSelector: string;
|
|
7
7
|
}
|
|
8
|
-
const MarkdownHighlightExtension:
|
|
8
|
+
const MarkdownHighlightExtension: FC<MarkdownHighlightExtensionProps> = ({
|
|
9
9
|
docContext,
|
|
10
10
|
rootSelector,
|
|
11
11
|
}) => {
|
|
12
|
-
const [selector, setSelector] =
|
|
13
|
-
|
|
12
|
+
const [selector, setSelector] = useState<string>(null);
|
|
13
|
+
useEffect(() => {
|
|
14
14
|
const elements = docContext.querySelectorAll(`${rootSelector} [data-highlight]`);
|
|
15
15
|
let timeoutId: NodeJS.Timeout;
|
|
16
16
|
function startHighlight(e) {
|
|
@@ -29,7 +29,7 @@ const MarkdownHighlightExtension: React.FC<MarkdownHighlightExtensionProps> = ({
|
|
|
29
29
|
elements && elements.forEach((elm) => elm.removeEventListener('click', startHighlight));
|
|
30
30
|
};
|
|
31
31
|
}, [docContext, rootSelector]);
|
|
32
|
-
|
|
32
|
+
useEffect(() => {
|
|
33
33
|
const elements = docContext.querySelectorAll(`${rootSelector} [class^=data-highlight__]`);
|
|
34
34
|
let timeoutId: NodeJS.Timeout;
|
|
35
35
|
function startHighlight(e) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FC, Ref } from 'react';
|
|
2
2
|
import { Modal as PfModal, ModalProps as PfModalProps } from '@patternfly/react-core/deprecated';
|
|
3
3
|
import { css } from '@patternfly/react-styles';
|
|
4
4
|
|
|
5
5
|
type ModalProps = {
|
|
6
6
|
isFullScreen?: boolean;
|
|
7
|
-
ref?:
|
|
7
|
+
ref?: Ref<PfModal>;
|
|
8
8
|
} & PfModalProps;
|
|
9
9
|
|
|
10
|
-
const Modal:
|
|
10
|
+
const Modal: FC<ModalProps> = ({ isFullScreen = false, className, ...props }) => (
|
|
11
11
|
<PfModal
|
|
12
12
|
{...props}
|
|
13
13
|
className={css('pfext-modal', className)}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useIsomorphicLayoutEffect } from '@patternfly/react-core';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import { useState, ReactNode } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
4
|
|
|
5
5
|
type GetContainer = Element | null | undefined | (() => Element);
|
|
6
6
|
|
|
7
7
|
interface PortalProps {
|
|
8
|
-
children:
|
|
8
|
+
children: ReactNode;
|
|
9
9
|
container?: GetContainer;
|
|
10
10
|
}
|
|
11
11
|
|
|
@@ -13,13 +13,13 @@ const getContainer = (container: GetContainer): Element | null | undefined =>
|
|
|
13
13
|
typeof container === 'function' ? container() : container;
|
|
14
14
|
|
|
15
15
|
const Portal = ({ children, container }: PortalProps) => {
|
|
16
|
-
const [containerNode, setContainerNode] =
|
|
16
|
+
const [containerNode, setContainerNode] = useState<Element>();
|
|
17
17
|
|
|
18
18
|
useIsomorphicLayoutEffect(() => {
|
|
19
19
|
setContainerNode(getContainer(container) || document.body);
|
|
20
20
|
}, [container]);
|
|
21
21
|
|
|
22
|
-
return containerNode ?
|
|
22
|
+
return containerNode ? createPortal(children, containerNode) : null;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export default Portal;
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ReactNode, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import Portal from './Portal';
|
|
3
3
|
|
|
4
4
|
interface SimplePopperProps {
|
|
5
|
-
children:
|
|
5
|
+
children: ReactNode;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
9
9
|
const openProp = true;
|
|
10
|
-
const nodeRef =
|
|
11
|
-
const popperRef =
|
|
12
|
-
const [isOpen, setOpenState] =
|
|
10
|
+
const nodeRef = useRef<Element>(null);
|
|
11
|
+
const popperRef = useRef(null);
|
|
12
|
+
const [isOpen, setOpenState] = useState(openProp);
|
|
13
13
|
|
|
14
|
-
const setOpen =
|
|
14
|
+
const setOpen = useCallback((newOpen: boolean) => {
|
|
15
15
|
setOpenState(newOpen);
|
|
16
16
|
}, []);
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
useEffect(() => {
|
|
19
19
|
setOpen(openProp);
|
|
20
20
|
}, [openProp, setOpen]);
|
|
21
21
|
|
|
22
|
-
const onKeyDown =
|
|
22
|
+
const onKeyDown = useCallback(
|
|
23
23
|
(e: KeyboardEvent) => {
|
|
24
24
|
if (e.keyCode === 27) {
|
|
25
25
|
setOpen(false);
|
|
@@ -28,7 +28,7 @@ const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
|
28
28
|
[setOpen],
|
|
29
29
|
);
|
|
30
30
|
|
|
31
|
-
const onClickOutside =
|
|
31
|
+
const onClickOutside = useCallback(
|
|
32
32
|
(e: MouseEvent) => {
|
|
33
33
|
if (!nodeRef.current || (e.target instanceof Node && !nodeRef.current.contains(e.target))) {
|
|
34
34
|
setOpen(false);
|
|
@@ -37,7 +37,7 @@ const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
|
37
37
|
[setOpen],
|
|
38
38
|
);
|
|
39
39
|
|
|
40
|
-
const destroy =
|
|
40
|
+
const destroy = useCallback(() => {
|
|
41
41
|
if (popperRef.current) {
|
|
42
42
|
popperRef.current.destroy();
|
|
43
43
|
document.removeEventListener('keydown', onKeyDown, true);
|
|
@@ -46,7 +46,7 @@ const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
|
46
46
|
}
|
|
47
47
|
}, [onClickOutside, onKeyDown]);
|
|
48
48
|
|
|
49
|
-
const initialize =
|
|
49
|
+
const initialize = useCallback(() => {
|
|
50
50
|
if (!nodeRef.current || !isOpen) {
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
@@ -54,7 +54,7 @@ const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
|
54
54
|
destroy();
|
|
55
55
|
}, [isOpen, destroy]);
|
|
56
56
|
|
|
57
|
-
const nodeRefCallback =
|
|
57
|
+
const nodeRefCallback = useCallback(
|
|
58
58
|
(node) => {
|
|
59
59
|
nodeRef.current = node;
|
|
60
60
|
initialize();
|
|
@@ -62,18 +62,18 @@ const SimplePopper = ({ children }: SimplePopperProps) => {
|
|
|
62
62
|
[initialize],
|
|
63
63
|
);
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
useEffect(() => {
|
|
66
66
|
initialize();
|
|
67
67
|
}, [initialize]);
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
useEffect(
|
|
70
70
|
() => () => {
|
|
71
71
|
destroy();
|
|
72
72
|
},
|
|
73
73
|
[destroy],
|
|
74
74
|
);
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
useEffect(() => {
|
|
77
77
|
if (!isOpen) {
|
|
78
78
|
destroy();
|
|
79
79
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import './spotlight.scss';
|
|
2
|
+
import { CSSProperties, FC, useEffect, useState } from 'react';
|
|
2
3
|
import { Portal, SimplePopper } from '../popper';
|
|
3
4
|
|
|
4
5
|
interface InteractiveSpotlightProps {
|
|
@@ -15,9 +16,9 @@ const isInViewport = (elementToCheck: Element) => {
|
|
|
15
16
|
);
|
|
16
17
|
};
|
|
17
18
|
|
|
18
|
-
const InteractiveSpotlight:
|
|
19
|
+
const InteractiveSpotlight: FC<InteractiveSpotlightProps> = ({ element }) => {
|
|
19
20
|
const { top, bottom, left, right, height, width } = element.getBoundingClientRect();
|
|
20
|
-
const style:
|
|
21
|
+
const style: CSSProperties = {
|
|
21
22
|
height,
|
|
22
23
|
width,
|
|
23
24
|
top,
|
|
@@ -25,9 +26,9 @@ const InteractiveSpotlight: React.FC<InteractiveSpotlightProps> = ({ element })
|
|
|
25
26
|
bottom,
|
|
26
27
|
right,
|
|
27
28
|
};
|
|
28
|
-
const [clicked, setClicked] =
|
|
29
|
+
const [clicked, setClicked] = useState(false);
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
useEffect(() => {
|
|
31
32
|
if (!clicked) {
|
|
32
33
|
if (!isInViewport(element)) {
|
|
33
34
|
element.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'nearest' });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { FC, useMemo } from 'react';
|
|
2
2
|
import InteractiveSpotlight from './InteractiveSpotlight';
|
|
3
3
|
import StaticSpotlight from './StaticSpotlight';
|
|
4
4
|
|
|
@@ -7,9 +7,9 @@ interface SpotlightProps {
|
|
|
7
7
|
interactive?: boolean;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
const Spotlight:
|
|
10
|
+
const Spotlight: FC<SpotlightProps> = ({ selector, interactive }) => {
|
|
11
11
|
// if target element is a hidden one return null
|
|
12
|
-
const element =
|
|
12
|
+
const element = useMemo(() => {
|
|
13
13
|
const highlightElement = document.querySelector(selector);
|
|
14
14
|
let hiddenElement = highlightElement;
|
|
15
15
|
while (hiddenElement) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import './spotlight.scss';
|
|
2
|
+
import { CSSProperties, FC } from 'react';
|
|
2
3
|
import { useBoundingClientRect } from '../../hooks';
|
|
3
4
|
import Portal from '../popper/Portal';
|
|
4
5
|
|
|
@@ -6,9 +7,9 @@ interface StaticSpotlightProps {
|
|
|
6
7
|
element: Element;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const StaticSpotlight:
|
|
10
|
+
const StaticSpotlight: FC<StaticSpotlightProps> = ({ element }) => {
|
|
10
11
|
const clientRect = useBoundingClientRect(element as HTMLElement);
|
|
11
|
-
const style:
|
|
12
|
+
const style: CSSProperties = clientRect
|
|
12
13
|
? {
|
|
13
14
|
top: clientRect.top,
|
|
14
15
|
left: clientRect.left,
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@keyframes pfext-spotlight-expand {
|
|
2
|
+
0% {
|
|
3
|
+
outline-offset: -4px;
|
|
4
|
+
outline-width: 4px;
|
|
5
|
+
opacity: 1;
|
|
6
|
+
}
|
|
7
|
+
100% {
|
|
8
|
+
outline-offset: 21px;
|
|
9
|
+
outline-width: 12px;
|
|
10
|
+
opacity: 0;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@keyframes pfext-spotlight-fade-in {
|
|
15
|
+
0% {
|
|
16
|
+
opacity: 0;
|
|
17
|
+
}
|
|
18
|
+
100% {
|
|
19
|
+
opacity: 1;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
@keyframes pfext-spotlight-fade-out {
|
|
24
|
+
0% {
|
|
25
|
+
opacity: 1;
|
|
26
|
+
}
|
|
27
|
+
100% {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.pfext-spotlight {
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
position: absolute;
|
|
35
|
+
&__with-backdrop {
|
|
36
|
+
mix-blend-mode: hard-light;
|
|
37
|
+
}
|
|
38
|
+
&__element-highlight-noanimate {
|
|
39
|
+
border: var(--pf-t--global--border--width--strong) solid var(--pf-t--global--border--color--brand--default);
|
|
40
|
+
background-color: var(--pf-t--color--gray--40);
|
|
41
|
+
z-index: 9999;
|
|
42
|
+
}
|
|
43
|
+
&__element-highlight-animate {
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
position: absolute;
|
|
46
|
+
box-shadow: inset 0px 0px 0px 4px var(--pf-t--global--color--brand--default);
|
|
47
|
+
opacity: 0;
|
|
48
|
+
animation: 0.4s pfext-spotlight-fade-in 0s ease-in-out, 5s pfext-spotlight-fade-out 12.8s ease-in-out;
|
|
49
|
+
animation-fill-mode: forwards;
|
|
50
|
+
&::after {
|
|
51
|
+
content: '';
|
|
52
|
+
position: absolute;
|
|
53
|
+
left: 0;
|
|
54
|
+
right: 0;
|
|
55
|
+
top: 0;
|
|
56
|
+
bottom: 0;
|
|
57
|
+
animation: 1.2s pfext-spotlight-expand 1.6s ease-out;
|
|
58
|
+
animation-fill-mode: forwards;
|
|
59
|
+
outline: 4px solid var(--pf-t--global--color--brand--default);
|
|
60
|
+
outline-offset: -4px;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|