@ndlib/component-library 0.0.100 → 0.0.102
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/components/elements/Icon/Icon.test.js +8 -0
- package/dist/components/elements/Icon/index.js +1 -1
- package/dist/components/elements/Markdown/Markdown.stories.d.ts +0 -1
- package/dist/components/elements/Markdown/Markdown.stories.js +20 -20
- package/dist/components/elements/Markdown/Markdown.test.js +17 -10
- package/dist/components/elements/Markdown/index.d.ts +0 -6
- package/dist/components/elements/Markdown/index.js +34 -12
- package/package.json +4 -3
|
@@ -3,6 +3,7 @@ import { render } from '../../../utils/test';
|
|
|
3
3
|
import { Icon } from '.';
|
|
4
4
|
import { SPACING } from '../../../theme';
|
|
5
5
|
import { FONT_SIZE, fontSizeMap } from '../../../theme/typography';
|
|
6
|
+
import { vitest } from 'vitest';
|
|
6
7
|
const MockIcon = () => {
|
|
7
8
|
return _jsx("div", { children: "Mock Icon" });
|
|
8
9
|
};
|
|
@@ -23,4 +24,11 @@ describe('Icon', () => {
|
|
|
23
24
|
padding: SPACING[1],
|
|
24
25
|
});
|
|
25
26
|
});
|
|
27
|
+
it('icon can be clicked', () => {
|
|
28
|
+
const onClick = vitest.fn();
|
|
29
|
+
const { getByLabelText } = render(_jsx(Icon, { icon: MockIcon, sx: { p: 1 }, onClick: onClick, "aria-label": "My Icon" }));
|
|
30
|
+
const icon = getByLabelText('My Icon');
|
|
31
|
+
icon.click();
|
|
32
|
+
expect(onClick).toHaveBeenCalled();
|
|
33
|
+
});
|
|
26
34
|
});
|
|
@@ -20,7 +20,7 @@ export const Icon = (_a) => {
|
|
|
20
20
|
if (onClick && !rest['aria-label']) {
|
|
21
21
|
flagInDevelopment('Icon component with onClick should have an aria-label and tabIndex={0}');
|
|
22
22
|
}
|
|
23
|
-
return (_jsx("div", Object.assign({ tabIndex: onClick ? 0 : undefined, role: onClick ? 'button' : 'none', sx: Object.assign(Object.assign({}, sx), { fontSize: size, display: 'flex', alignItems: 'center', justifyContent: 'center', ':hover': onClick
|
|
23
|
+
return (_jsx("div", Object.assign({ tabIndex: onClick ? 0 : undefined, role: onClick ? 'button' : 'none', onClick: onClick, sx: Object.assign(Object.assign({}, sx), { fontSize: size, display: 'flex', alignItems: 'center', justifyContent: 'center', ':hover': onClick
|
|
24
24
|
? {
|
|
25
25
|
cursor: 'pointer',
|
|
26
26
|
transform: 'scale(1.05)',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Markdown } from '.';
|
|
3
3
|
import { FONT_WEIGHT } from '../../../theme/typography';
|
|
4
4
|
import { COLOR } from '../../../theme/colors';
|
|
5
5
|
const meta = {
|
|
@@ -57,6 +57,16 @@ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
|
|
57
57
|
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
58
58
|
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
59
59
|
nulla pariatur.
|
|
60
|
+
|
|
61
|
+
https://this-should-be-auto-linked.nd.edu
|
|
62
|
+
|
|
63
|
+
While Gura says that the exhibit was inspired by the themes of the Medieval conference and will appeal to its attendees, the displays are curated to interest Notre Dame students and faculty across various disciplines, as well as the general public.
|
|
64
|
+
|
|
65
|
+
“I'm trying to pick items to tell a story that viewers can appreciate on different levels at the same time,” he said.
|
|
66
|
+
|
|
67
|
+
To tell that story, Gura decided to give each of the seven cases housing this exhibit a theme.
|
|
68
|
+
|
|
69
|
+
> “I think it's an exciting way to create a journey through time and space using the objects themselves as the primary storyteller,” he said. “They will drive the narrative from case to case. You can be exposed to different things that the average person never really thought about.”
|
|
60
70
|
`;
|
|
61
71
|
export const Default = {
|
|
62
72
|
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
@@ -65,14 +75,15 @@ export const Default = {
|
|
|
65
75
|
},
|
|
66
76
|
};
|
|
67
77
|
const htmlContent = `
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
## HTML Content
|
|
79
|
+
<p style='color: red;'>hello world </p>
|
|
80
|
+
> test
|
|
81
|
+
<img
|
|
82
|
+
style="width: 300px"
|
|
83
|
+
src="https://s3.amazonaws.com/resources.library.nd.edu/images/website/search.banner.fall.jpg"
|
|
84
|
+
></img>
|
|
85
|
+
<a href="https://google.com" class="embedly-card" data-card-width="100px" data-card-controls="0">Embedded content: https://google.com</a>
|
|
86
|
+
<iframe src="https://www.facebook.com/plugins/video.php?href=https%3A%2F%2Fwww.facebook.com%2FNDLibraries%2Fvideos%2F1673794309311939%2F&show_text=0&width=560" width="560" height="315" style="border:none;overflow:hidden" scrolling="no" frameborder="0" ></iframe>
|
|
76
87
|
`;
|
|
77
88
|
export const WithHtml = {
|
|
78
89
|
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
@@ -100,17 +111,6 @@ export const CustomizeStyles = {
|
|
|
100
111
|
content: startingContent,
|
|
101
112
|
},
|
|
102
113
|
};
|
|
103
|
-
export const CustomHtmlSanitize = {
|
|
104
|
-
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
105
|
-
args: {
|
|
106
|
-
enableHtml: true,
|
|
107
|
-
sanitizeHtmlOptions: {
|
|
108
|
-
allowedAttributes: Object.assign(Object.assign({}, DEFAULT_ALLOWED_ATTRIBUTES), { p: ['style'], img: ['src', 'style'] }),
|
|
109
|
-
allowedTags: [...DEFAULT_ALLOWED_TAGS, 'img'],
|
|
110
|
-
},
|
|
111
|
-
content: htmlContent,
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
114
|
export const NoHtml = {
|
|
115
115
|
render: (args) => _jsx(Markdown, Object.assign({}, args)),
|
|
116
116
|
args: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { render } from '../../../utils/test';
|
|
3
|
-
import {
|
|
3
|
+
import { Markdown } from '.';
|
|
4
4
|
const testMarkdown = `
|
|
5
5
|
# Inline styles
|
|
6
6
|
This paragraph has __bold content__ and **more bold content** and _italic content_ and *more italic content*
|
|
@@ -35,6 +35,16 @@ Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris
|
|
|
35
35
|
nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in
|
|
36
36
|
reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
37
37
|
nulla pariatur.
|
|
38
|
+
|
|
39
|
+
https://this-should-be-auto-linked.nd.edu
|
|
40
|
+
|
|
41
|
+
While Gura says that the exhibit was inspired by the themes of the Medieval conference and will appeal to its attendees, the displays are curated to interest Notre Dame students and faculty across various disciplines, as well as the general public.
|
|
42
|
+
|
|
43
|
+
“I'm trying to pick items to tell a story that viewers can appreciate on different levels at the same time,” he said.
|
|
44
|
+
|
|
45
|
+
To tell that story, Gura decided to give each of the seven cases housing this exhibit a theme.
|
|
46
|
+
|
|
47
|
+
> “I think it's an exciting way to create a journey through time and space using the objects themselves as the primary storyteller,” he said. “They will drive the narrative from case to case. You can be exposed to different things that the average person never really thought about.”
|
|
38
48
|
`;
|
|
39
49
|
const htmlMarkdown = `
|
|
40
50
|
## HTML Content
|
|
@@ -74,15 +84,18 @@ describe('Markdown', () => {
|
|
|
74
84
|
name: 'Heading 5',
|
|
75
85
|
level: 6,
|
|
76
86
|
})).toBeInTheDocument();
|
|
87
|
+
expect(getByRole('link', {
|
|
88
|
+
name: 'https://this-should-be-auto-linked.nd.edu',
|
|
89
|
+
})).toBeInTheDocument();
|
|
77
90
|
expect(getAllByRole('list')).toHaveLength(3);
|
|
78
91
|
});
|
|
79
92
|
it('renders allowed html when included', () => {
|
|
80
|
-
const { container, getByText,
|
|
93
|
+
const { container, getByText, getAllByRole } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true }));
|
|
81
94
|
expect(getByText('hello world')).toBeDefined();
|
|
82
|
-
expect(
|
|
95
|
+
expect(getAllByRole('link')).toBeDefined();
|
|
83
96
|
expect(() => getByText(' ')).toThrow();
|
|
84
97
|
expect(container.getElementsByTagName('script')).toHaveLength(0);
|
|
85
|
-
expect(container.getElementsByTagName('img')).toHaveLength(
|
|
98
|
+
expect(container.getElementsByTagName('img')).toHaveLength(1);
|
|
86
99
|
expect(container.getElementsByTagName('iframe')).toHaveLength(1);
|
|
87
100
|
});
|
|
88
101
|
it('does not render html when disabled', () => {
|
|
@@ -91,10 +104,4 @@ describe('Markdown', () => {
|
|
|
91
104
|
expect(container.getElementsByTagName('script')).toHaveLength(0);
|
|
92
105
|
expect(container.getElementsByTagName('iframe')).toHaveLength(0);
|
|
93
106
|
});
|
|
94
|
-
it('supports customizing html sanitization', () => {
|
|
95
|
-
const { container } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true, sanitizeHtmlOptions: {
|
|
96
|
-
allowedTags: DEFAULT_ALLOWED_TAGS.concat(['img']),
|
|
97
|
-
} }));
|
|
98
|
-
expect(container.getElementsByTagName('img')).toHaveLength(1);
|
|
99
|
-
});
|
|
100
107
|
});
|
|
@@ -6,17 +6,11 @@ export declare const DEFAULT_ALLOWED_ATTRIBUTES: {
|
|
|
6
6
|
iframe: string[];
|
|
7
7
|
a: sanitizeHtml.AllowedAttribute[];
|
|
8
8
|
};
|
|
9
|
-
export declare const DEFAULT_ALLOWED_IFRAME_DOMAINS: string[];
|
|
10
9
|
type MarkdownProps = StyledElementProps<HTMLDivElement, {
|
|
11
10
|
content: string;
|
|
12
11
|
enableHtml?: boolean;
|
|
13
12
|
imageStyles?: React.CSSProperties;
|
|
14
13
|
headingLevelOffset?: number;
|
|
15
|
-
sanitizeHtmlOptions?: {
|
|
16
|
-
allowedTags?: string[];
|
|
17
|
-
allowedIframeDomains?: string[];
|
|
18
|
-
allowedAttributes?: sanitizeHtml.IOptions['allowedAttributes'];
|
|
19
|
-
};
|
|
20
14
|
customStyles?: Record<string, StylesProp>;
|
|
21
15
|
}>;
|
|
22
16
|
export declare const Markdown: React.FC<MarkdownProps>;
|
|
@@ -18,37 +18,59 @@ import { HEADING_SIZE, Heading } from '../text/Heading';
|
|
|
18
18
|
import { Bold, Italic } from '../text/Inline';
|
|
19
19
|
import { firstChildAltSelector } from '../../../utils/misc';
|
|
20
20
|
import sanitizeHtml from 'sanitize-html';
|
|
21
|
+
import remarkGfm from 'remark-gfm';
|
|
21
22
|
import { BlockQuote } from '../BlockQuote';
|
|
22
23
|
export const DEFAULT_ALLOWED_TAGS = sanitizeHtml.defaults.allowedTags.concat([
|
|
23
24
|
'iframe',
|
|
25
|
+
'img',
|
|
24
26
|
]);
|
|
25
27
|
export const DEFAULT_ALLOWED_ATTRIBUTES = Object.assign(Object.assign({}, sanitizeHtml.defaults.allowedAttributes), { iframe: ['*'], a: sanitizeHtml.defaults.allowedAttributes.a.concat([
|
|
26
28
|
'class',
|
|
27
29
|
'data-card-width',
|
|
28
30
|
'data-card-controls',
|
|
29
31
|
]) });
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
const parseBlockquotes = (content) => {
|
|
33
|
+
const contentArray = content.split('\n');
|
|
34
|
+
let newContent = '';
|
|
35
|
+
let isAddingBlockquote = false;
|
|
36
|
+
for (const line of contentArray) {
|
|
37
|
+
if (line.startsWith('> ') && !isAddingBlockquote) {
|
|
38
|
+
const newLine = line.replace('> ', '');
|
|
39
|
+
newContent += `<blockquote>${newLine}`;
|
|
40
|
+
isAddingBlockquote = true;
|
|
41
|
+
}
|
|
42
|
+
else if (line.startsWith('> ') && isAddingBlockquote) {
|
|
43
|
+
newContent += line.replace('> ', '');
|
|
44
|
+
}
|
|
45
|
+
else if (isAddingBlockquote) {
|
|
46
|
+
newContent += `</blockquote>\n\n${line}`;
|
|
47
|
+
isAddingBlockquote = false;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
newContent += line;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (isAddingBlockquote) {
|
|
54
|
+
newContent += '</blockquote>';
|
|
55
|
+
}
|
|
56
|
+
return newContent;
|
|
57
|
+
};
|
|
35
58
|
const dynamicTopMarginStyles = {
|
|
36
59
|
[firstChildAltSelector]: {
|
|
37
60
|
mt: 0,
|
|
38
61
|
},
|
|
39
62
|
};
|
|
40
63
|
export const Markdown = (_a) => {
|
|
41
|
-
var { content, enableHtml,
|
|
64
|
+
var { content, enableHtml, imageStyles, customStyles = {}, headingLevelOffset = 1 } = _a, rest = __rest(_a, ["content", "enableHtml", "imageStyles", "customStyles", "headingLevelOffset"]);
|
|
42
65
|
let sanitizedContent = content;
|
|
43
66
|
if (enableHtml) {
|
|
44
|
-
sanitizedContent =
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
allowedAttributes: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedAttributes) || DEFAULT_ALLOWED_ATTRIBUTES,
|
|
67
|
+
sanitizedContent = parseBlockquotes(content);
|
|
68
|
+
sanitizedContent = sanitizeHtml(sanitizedContent, {
|
|
69
|
+
allowedTags: DEFAULT_ALLOWED_TAGS,
|
|
70
|
+
allowedAttributes: DEFAULT_ALLOWED_ATTRIBUTES,
|
|
49
71
|
});
|
|
50
72
|
}
|
|
51
|
-
return (_jsx("div", Object.assign({}, rest, { children: _jsx(ReactMarkdown, Object.assign({ rehypePlugins: enableHtml ? [rehypeRaw] : [], components: {
|
|
73
|
+
return (_jsx("div", Object.assign({}, rest, { children: _jsx(ReactMarkdown, Object.assign({ rehypePlugins: enableHtml ? [rehypeRaw, remarkGfm] : [remarkGfm], components: {
|
|
52
74
|
h1: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.XL }, props, { level: props.level + headingLevelOffset, standalone: true, sx: Object.assign(Object.assign({}, dynamicTopMarginStyles), customStyles.h1) }))),
|
|
53
75
|
h2: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.LG }, props, { level: props.level + headingLevelOffset, standalone: true, sx: Object.assign(Object.assign({}, dynamicTopMarginStyles), customStyles.h2) }))),
|
|
54
76
|
h3: (props) => (_jsx(Heading, Object.assign({ size: HEADING_SIZE.MD }, props, { level: props.level + headingLevelOffset, standalone: true, sx: Object.assign(Object.assign({}, dynamicTopMarginStyles), customStyles.h3) }))),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ndlib/component-library",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.102",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"files": [
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"storybook": "^7.0.17",
|
|
78
78
|
"theme-ui": "^0.16.1",
|
|
79
79
|
"typescript": "^5.0.2",
|
|
80
|
-
"vite": "^4.
|
|
80
|
+
"vite": "^4.5.2",
|
|
81
81
|
"vitest": "^0.31.4"
|
|
82
82
|
},
|
|
83
83
|
"prettier": {
|
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
"react-markdown": "^8.0.7",
|
|
93
93
|
"react-modal": "^3.16.1",
|
|
94
94
|
"rehype-raw": "^6.1.1",
|
|
95
|
-
"
|
|
95
|
+
"remark-gfm": "^4.0.0",
|
|
96
|
+
"sanitize-html": "^2.12.1",
|
|
96
97
|
"schema-dts": "^1.1.2"
|
|
97
98
|
}
|
|
98
99
|
}
|