@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.
@@ -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)',
@@ -7,5 +7,4 @@ export declare const Default: Story;
7
7
  export declare const WithHtml: Story;
8
8
  export declare const CustomizeImages: Story;
9
9
  export declare const CustomizeStyles: Story;
10
- export declare const CustomHtmlSanitize: Story;
11
10
  export declare const NoHtml: Story;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { DEFAULT_ALLOWED_ATTRIBUTES, DEFAULT_ALLOWED_TAGS, Markdown } from '.';
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
- ## HTML Content
69
- <p style='color: red;'>hello world </p>
70
- <img
71
- style="width: 300px"
72
- src="https://s3.amazonaws.com/resources.library.nd.edu/images/website/search.banner.fall.jpg"
73
- ></img>
74
- <a href="https://google.com" class="embedly-card" data-card-width="100px" data-card-controls="0">Embedded content: https://google.com</a>
75
- <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>
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 { DEFAULT_ALLOWED_TAGS, Markdown } from '.';
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, getByRole } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true }));
93
+ const { container, getByText, getAllByRole } = render(_jsx(Markdown, { content: htmlMarkdown, enableHtml: true }));
81
94
  expect(getByText('hello world')).toBeDefined();
82
- expect(getByRole('link')).toBeDefined();
95
+ expect(getAllByRole('link')).toBeDefined();
83
96
  expect(() => getByText('&nbsp;')).toThrow();
84
97
  expect(container.getElementsByTagName('script')).toHaveLength(0);
85
- expect(container.getElementsByTagName('img')).toHaveLength(0);
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
- export const DEFAULT_ALLOWED_IFRAME_DOMAINS = [
31
- 'facebook.com',
32
- 'youtube.com',
33
- 'nd.edu',
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, sanitizeHtmlOptions, imageStyles, customStyles = {}, headingLevelOffset = 1 } = _a, rest = __rest(_a, ["content", "enableHtml", "sanitizeHtmlOptions", "imageStyles", "customStyles", "headingLevelOffset"]);
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 = sanitizeHtml(content, {
45
- allowedTags: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedTags) || DEFAULT_ALLOWED_TAGS,
46
- allowedIframeDomains: (sanitizeHtmlOptions === null || sanitizeHtmlOptions === void 0 ? void 0 : sanitizeHtmlOptions.allowedIframeDomains) ||
47
- DEFAULT_ALLOWED_IFRAME_DOMAINS,
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.100",
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.3.9",
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
- "sanitize-html": "^2.11.0",
95
+ "remark-gfm": "^4.0.0",
96
+ "sanitize-html": "^2.12.1",
96
97
  "schema-dts": "^1.1.2"
97
98
  }
98
99
  }