Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QUIC-1590-widget-markdown-card #279

Merged
merged 40 commits into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
3a7443a
Initial Markdown Card
johnniedfeldt Jul 13, 2022
0b2f05e
Merge branch 'master' into QUIC-1590-widget-markdown-card
johnniedfeldt Jul 13, 2022
3f29696
Update story controls
johnniedfeldt Jul 13, 2022
fadd8eb
Fix StoryBook MarkdownCard Basic.
johnniedfeldt Jul 13, 2022
50cfd26
fix: resolve ts error
Jul 14, 2022
cc49dc6
Basic Markdown Card Styling
johnniedfeldt Jul 14, 2022
a0c858a
Markdown styling changes. Added different heading text sizes based on…
johnniedfeldt Jul 14, 2022
f151578
Updated <hr> rendering for markdowncard
johnniedfeldt Jul 14, 2022
2bf292c
Heading styling update. Sub lists styled. Checkboxes implemented.
johnniedfeldt Jul 14, 2022
23ff243
Updated structure to make it easier to add Markdown. There is some is…
johnniedfeldt Jul 14, 2022
3a806e8
Minor adjustments
johnniedfeldt Jul 14, 2022
2094098
Update src/components/ContentCards/ContentCards.tsx
johnniedfeldt Jul 19, 2022
8ee6594
Add Heading size prop
johnniedfeldt Jul 19, 2022
7883841
Merge branch 'master' into QUIC-1590-widget-markdown-card
johnniedfeldt Jul 19, 2022
ddc459e
Reduced version of react-markdown for compatibility reasons and to fi…
johnniedfeldt Jul 20, 2022
f2cfb3f
Fix test issue.
johnniedfeldt Jul 20, 2022
d3ea33f
Added remark-gfm back in for Github Markdown support.
johnniedfeldt Jul 20, 2022
3c62a08
Added more test cases.
johnniedfeldt Jul 20, 2022
e064603
PR Comment resolutions.
johnniedfeldt Jul 20, 2022
b9b6a60
Ran prettier and added prettier check to husky.
johnniedfeldt Jul 20, 2022
b1581c5
More PR changes
johnniedfeldt Jul 21, 2022
c249c88
Remove unused import
johnniedfeldt Jul 21, 2022
d7030f3
Adjusted Table and code styling. Change Heading size implementation.
johnniedfeldt Jul 22, 2022
ee35db7
Change default text size
johnniedfeldt Jul 22, 2022
09ec822
Revert husky pre-commit
johnniedfeldt Jul 22, 2022
cfa70dd
Minor font changes
johnniedfeldt Jul 22, 2022
fe58d77
Merge branch 'master' into QUIC-1590-widget-markdown-card
johnniedfeldt Jul 22, 2022
9c7de3c
Update src/components/Heading/Heading.tsx
johnniedfeldt Jul 22, 2022
1a21387
Fixed lint issues. Still have 3 warnings in MarkdownCard.tsx
johnniedfeldt Jul 22, 2022
ffe1b31
Added markdown test. Markdown block does not show for some reason, bu…
johnniedfeldt Jul 25, 2022
dec0179
possible debug updates for package.json
johnniedfeldt Jul 25, 2022
13bc6d6
react-markdown updated to lates. Cypress now works for Markdown card.
johnniedfeldt Jul 27, 2022
079d819
Jest fix
johnniedfeldt Jul 27, 2022
6bc5468
Minor PR fixes
johnniedfeldt Jul 27, 2022
edea7be
PR comment implementations
johnniedfeldt Jul 28, 2022
c7487f3
Remove unused test
johnniedfeldt Jul 28, 2022
d8ca1cf
Fix unused variable warnings
johnniedfeldt Jul 28, 2022
f5e255f
Remove extra unused variable
johnniedfeldt Jul 28, 2022
7b7bc7f
Minor fixes
johnniedfeldt Jul 28, 2022
ccd539d
Merge branch 'master' into QUIC-1590-widget-markdown-card
johnniedfeldt Jul 28, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
760 changes: 632 additions & 128 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@
"preact": "^10.8.1",
"preactement": "^1.8.2",
"react-cool-dimensions": "^2.0.7",
"react-spring": "^9.4.5"
"react-markdown": "^6.0.0",
"react-spring": "^9.4.5",
"remark-gfm": "^1.0.0"
},
"devDependencies": {
"@mdx-js/preact": "^2.1.2",
Expand Down
2 changes: 2 additions & 0 deletions src/components/ContentCards/ContentCards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useTransition, animated, config } from 'react-spring';
import { useSoulMachines } from '../../contexts/SoulMachinesContext';
import { ImageCard } from '../ImageCard';
import { LinkCard } from '../LinkCard';
import { MarkdownCard } from '../MarkdownCard';
import { OptionsCard } from '../OptionsCard';

export type CardComponent = {
Expand All @@ -25,6 +26,7 @@ export function ContentCards() {
image: ImageCard,
externalLink: (props) => <LinkCard {...props} isExternal={true} />,
internalLink: (props) => <LinkCard {...props} isExternal={false} />,
markdown: MarkdownCard
};
johnniedfeldt marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
Expand Down
12 changes: 10 additions & 2 deletions src/components/Heading/Heading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ import { createElement } from 'preact';
export type HeadingProps = {
children: string;
type: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
textClass?: string;
size?: string;
};

export function Heading({ type, children }: HeadingProps) {
export function Heading({
type,
children,
size = 'sm-text-lg',
textClass: textClass = 'sm-text-neutral-700',
}: HeadingProps) {
// TODO jcn add text sizing styling for different headings
johnniedfeldt marked this conversation as resolved.
Show resolved Hide resolved
return createElement(
type,
{
className: 'sm-text-2xl sm-font-rubik sm-font-medium sm-m-0 sm-text-neutral-700',
className: `${size} sm-font-rubik sm-font-medium sm-m-0 ${textClass}`,
},
children,
);
Expand Down
4 changes: 3 additions & 1 deletion src/components/LinkCard/LinkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ export function LinkCard({ content, isExternal, style }: LinkCardProps) {
className="sm-flex sm-flex-col sm-gap-y-3 sm-items-start sm-h-full sm-max-h-contentCard sm-overflow-y-auto"
>
{data.imageUrl && <img src={data.imageUrl} alt={data.title} />}
<Heading type="h2">{data.title}</Heading>
<Heading type="h2" size="sm-text-2xl">
{data.title}
</Heading>
{data.description && <Text>{data.description}</Text>}
<div className="sm-bg-white sm-sticky sm-bottom-0 sm-w-full sm-pt-5 sm-border-solid sm-border-0 sm-border-t-2 sm-border-gray-50">
<a className="sm-text-white sm-no-underline" href={data.url} {...conditionalAttributes}>
Expand Down
57 changes: 57 additions & 0 deletions src/components/MarkdownCard/MarkdownCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { MarkdownCard, MarkdownCardProps } from '.';

const content = {
id: 'id',
type: 'markdown',
data: {
text: `# H1
johnniedfeldt marked this conversation as resolved.
Show resolved Hide resolved
## H2
### H3
#### H4
##### H5
###### H6

1. Most interesting
1. Sublist 1
2. Sublist 2
2. Less interesting
1. Sublist 1
2. Sublist 2
3. Least Interesting

**bold**
- item 1
- item 2

[External Link](https://www.google.com)

[Internal Link](http://localhost:3000)

johnniedfeldt marked this conversation as resolved.
Show resolved Hide resolved
*emphasis*

---
Horizontal Rule

---

#### Image Example

![Image Alt Text](https://assets.gocomics.com/uploads/collection_images/collection_image_large_1628638_dilbert-inventions-content-admin-2048x1280_201809101600.jpg)
`,
},
};

export default {
title: `Components / MarkdownCard`,
component: MarkdownCard,
argTypes: {
content: { control: 'object', defaultValue: content },
},
parameters: {
docs: {
page: <MarkdownCard content={content} />,
},
},
};

export const Basic = ({ content }: MarkdownCardProps) => <MarkdownCard content={content} />;
111 changes: 111 additions & 0 deletions src/components/MarkdownCard/MarkdownCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { MarkdownCard } from '.';
import { getByRole, render } from '@testing-library/preact';
Fixed Show fixed Hide fixed
import { ContentCard } from '@soulmachines/smwebsdk';

describe('<MarkdownCard />', () => {
function markdownData(markdown: string): ContentCard {
Fixed Show fixed Hide fixed
return {
id: 'mockId',
type: 'markdown',
data: {
text: markdown,
},
};
}

const mockCard: ContentCard = {
id: 'mockId',
type: 'markdown',
data: {
text: '## An Interesting List\r\n 1. Most interesting\r\n 2. Less interesting\r\n 3. Least Interesting\r\n\r\n**bold**\r\n- item 1\r\n- item 2\r\n\r\n[test link](https://google.com)',
},
};

johnniedfeldt marked this conversation as resolved.
Show resolved Hide resolved
it('renders data-sm-content with the card id', () => {
const { container } = render(<MarkdownCard content={mockCard} />);
expect(container.querySelector('[data-sm-content="mockId"]')).toBeInTheDocument();
});
allanpope marked this conversation as resolved.
Show resolved Hide resolved

it('renders headings 1 to 6 individually', () => {
for (let hlevel = 1; hlevel < 7; hlevel++) {
console.log(hlevel);
const { container } = render(
<MarkdownCard content={markdownData('#'.repeat(hlevel) + ' Heading')} />,
);

expect(container.querySelector('h' + hlevel)).toBeInTheDocument();
}
});

it('renders unordered list', () => {
const markdown = `- item 1
- item 2
- item 3
`;
const { container } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(container.querySelector('ul')).toBeInTheDocument();
expect(container.querySelector('li')).toBeInTheDocument();
expect(container.querySelectorAll('li').length).toBe(3);
});

it('renders ordered list', () => {
const markdown = `1. item 1
2. item 2
3. item 3
`;
const { container } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(container.querySelector('ol')).toBeInTheDocument();
expect(container.querySelector('li')).toBeInTheDocument();
expect(container.querySelectorAll('li').length).toBe(3);
});

it('renders bold', () => {
const markdown = `**bold text**
`;
const { container } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(container.querySelector('strong')).toBeInTheDocument();
expect(container.querySelectorAll('strong').length).toBe(1);
});

it('renders emphasis', () => {
const markdown = `*emphasized text*
`;
const { container } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(container.querySelector('em')).toBeInTheDocument();
expect(container.querySelectorAll('em').length).toBe(1);
});

it('renders external link', () => {
Object.defineProperty(window, 'location', { value: { hostname: 'mycurrentwebpage.com' } });

const markdown = `[external link](https://www.google.com)
`;
const { getByRole } = render(<MarkdownCard content={markdownData(markdown)} />);
const element = getByRole('link', { name: 'external link' });
Fixed Show fixed Hide fixed
expect(getByRole('link', { name: 'external link' })).toHaveAttribute(
'href',
'https://www.google.com',
);
expect(getByRole('link', { name: 'external link' })).toHaveAttribute('target', '_blank');
});

it('renders internal link', () => {
Object.defineProperty(window, 'location', { value: { hostname: 'mycurrentwebpage.com' } });

const markdown = `[internal link](http://mycurrentwebpage.com/moreinfo);
`;
const { getByRole } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(getByRole('link', { name: 'internal link' })).toHaveAttribute(
'href',
'http://mycurrentwebpage.com/moreinfo',
);
expect(getByRole('link', { name: 'internal link' })).not.toHaveAttribute('target');
});

it('renders nothing if nothing received', () => {
const markdown = '';

const { container } = render(<MarkdownCard content={markdownData(markdown)} />);
expect(container).toBeEmptyDOMElement();
});
});
151 changes: 151 additions & 0 deletions src/components/MarkdownCard/MarkdownCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { ContentCard } from '@soulmachines/smwebsdk';
import { Card } from '../Card';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { Heading, HeadingProps } from '../Heading';
import { Text, TextProps } from '../Text';

export type MarkdownCardProps = {
content: ContentCard;
// Styles are passed through from react spring
style?: Record<string, 'string | CSSProperties | undefined'>;
};

export type LiProps = {
children: string;
ordered: boolean;
index: number;
checked: boolean | null;
className: string | null;
};

export type AProps = {
href: string;
children: string;
title: string;
target?: string;
};

export type OlProps = {
children: string;
};

export type MarkdownData = {
text: string;
};

export function MarkdownCard({ content, style }: MarkdownCardProps) {
const data = content.data as unknown as MarkdownData;
if (!data.text) {
return null;
}
const markdown = data.text;

return (
<Card style={style}>
<div
data-sm-content={content.id}
className="sm-sans sm-flex sm-flex-col sm-gap-y-3 sm-items-start sm-h-full sm-max-h-contentCard sm-overflow-y-auto sm-text-neutral-700 sm-font-sans sm-font-normal"
>
{/*
Fixes a typescript issue "JSX element type 'ReactMarkdown' does not have any construct or call signatures".
@ts-ignore */}
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
h1: ({
type = 'h1',
children,
size = 'sm-text-2xl',
textClass: text_class = 'sm-text-blue-800',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
h2: ({
type = 'h2',
children,
size = 'sm-text-2xl',
textClass: text_class = 'sm-text-blue-700',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
h3: ({
type = 'h3',
children,
size = 'sm-text-xl',
textClass: text_class = 'sm-text-blue-600',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
h4: ({
type = 'h4',
children,
size = 'sm-text-xl',
textClass: text_class = 'sm-text-blue-500',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
h5: ({
type = 'h5',
children,
size = 'sm-text-lg',
textClass: text_class = 'sm-text-blue-400',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
h6: ({
type = 'h6',
children,
size = 'sm-text-lg',
textClass: text_class = 'sm-text-blue-300',
}: HeadingProps) => (
<Heading type={type} children={children} size={size} textClass={text_class} />
),
li: ({ children, ordered, index, checked, className }: LiProps) => {
if (ordered) {
return (
<li>
{index + 1}. {children}
</li>
);
} else if (className === 'task-list-item') {
allanpope marked this conversation as resolved.
Show resolved Hide resolved
return <li>{children}</li>;
}
return <li>- {children}</li>;
},
ol: ({ children }: OlProps) => {
return <ol className="sm-ml-4">{children}</ol>;
},
ul: ({ children }: OlProps) => {
return <ul className="sm-ml-4">{children}</ul>;
},
a: ({ href, children, title, target }: AProps) => {
if (!target) {
const currentDomain = window.location.hostname;
const destinationDomain = new URL(href).hostname;
if (!(currentDomain === destinationDomain)) target = '_blank';
allanpope marked this conversation as resolved.
Show resolved Hide resolved
}

return (
<a
className="sm-text-blue-400 active:sm-text-red-500 hover:sm-underline focus:underline visited:sm-text-pink-500"
href={href}
title={title}
target={target}
>
{children}
</a>
);
},
p: ({ children }: TextProps) => {
return <Text children={children} size="sm" />;
},
hr: () => <hr className="sm-w-11/12 sm-bg-gray-400" />,
}}
>
{markdown}
</ReactMarkdown>
</div>
</Card>
);
}
Loading