diff options
Diffstat (limited to 'webui/src/components')
-rw-r--r-- | webui/src/components/Author.tsx | 33 | ||||
-rw-r--r-- | webui/src/components/Content/ImageTag.tsx | 23 | ||||
-rw-r--r-- | webui/src/components/Content/PreTag.tsx | 17 | ||||
-rw-r--r-- | webui/src/components/Content/index.tsx | 26 | ||||
-rw-r--r-- | webui/src/components/Date.tsx | 18 | ||||
-rw-r--r-- | webui/src/components/Label.tsx | 57 | ||||
-rw-r--r-- | webui/src/components/fragments.graphql | 19 |
7 files changed, 193 insertions, 0 deletions
diff --git a/webui/src/components/Author.tsx b/webui/src/components/Author.tsx new file mode 100644 index 00000000..9ac1da52 --- /dev/null +++ b/webui/src/components/Author.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import MAvatar from '@material-ui/core/Avatar'; +import Tooltip from '@material-ui/core/Tooltip/Tooltip'; + +import { AuthoredFragment } from './fragments.generated'; + +type Props = AuthoredFragment & { + className?: string; + bold?: boolean; +}; + +const Author = ({ author, ...props }: Props) => { + if (!author.email) { + return <span {...props}>{author.displayName}</span>; + } + + return ( + <Tooltip title={author.email}> + <span {...props}>{author.displayName}</span> + </Tooltip> + ); +}; + +export const Avatar = ({ author, ...props }: Props) => { + if (author.avatarUrl) { + return <MAvatar src={author.avatarUrl} {...props} />; + } + + return <MAvatar {...props}>{author.displayName[0]}</MAvatar>; +}; + +export default Author; diff --git a/webui/src/components/Content/ImageTag.tsx b/webui/src/components/Content/ImageTag.tsx new file mode 100644 index 00000000..70ee1bc0 --- /dev/null +++ b/webui/src/components/Content/ImageTag.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/styles'; + +const useStyles = makeStyles({ + tag: { + maxWidth: '100%', + }, +}); + +const ImageTag = ({ + alt, + ...props +}: React.ImgHTMLAttributes<HTMLImageElement>) => { + const classes = useStyles(); + return ( + <a href={props.src} target="_blank" rel="noopener noreferrer nofollow"> + <img className={classes.tag} alt={alt} {...props} /> + </a> + ); +}; + +export default ImageTag; diff --git a/webui/src/components/Content/PreTag.tsx b/webui/src/components/Content/PreTag.tsx new file mode 100644 index 00000000..5256ab12 --- /dev/null +++ b/webui/src/components/Content/PreTag.tsx @@ -0,0 +1,17 @@ +import React from 'react'; + +import { makeStyles } from '@material-ui/styles'; + +const useStyles = makeStyles({ + tag: { + maxWidth: '100%', + overflowX: 'auto', + }, +}); + +const PreTag = (props: React.HTMLProps<HTMLPreElement>) => { + const classes = useStyles(); + return <pre className={classes.tag} {...props}></pre>; +}; + +export default PreTag; diff --git a/webui/src/components/Content/index.tsx b/webui/src/components/Content/index.tsx new file mode 100644 index 00000000..56e52e1e --- /dev/null +++ b/webui/src/components/Content/index.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import html from 'remark-html'; +import parse from 'remark-parse'; +import remark2react from 'remark-react'; +import unified from 'unified'; + +import ImageTag from './ImageTag'; +import PreTag from './PreTag'; + +type Props = { markdown: string }; +const Content: React.FC<Props> = ({ markdown }: Props) => { + const processor = unified() + .use(parse) + .use(html) + .use(remark2react, { + remarkReactComponents: { + img: ImageTag, + pre: PreTag, + }, + }); + + const contents: React.ReactNode = processor.processSync(markdown).contents; + return <>{contents}</>; +}; + +export default Content; diff --git a/webui/src/components/Date.tsx b/webui/src/components/Date.tsx new file mode 100644 index 00000000..f9e0a0b2 --- /dev/null +++ b/webui/src/components/Date.tsx @@ -0,0 +1,18 @@ +import moment from 'moment'; +import React from 'react'; +import Moment from 'react-moment'; + +import Tooltip from '@material-ui/core/Tooltip/Tooltip'; + +const HOUR = 1000 * 3600; +const DAY = 24 * HOUR; +const WEEK = 7 * DAY; + +type Props = { date: string }; +const Date = ({ date }: Props) => ( + <Tooltip title={moment(date).format('LLLL')}> + <Moment date={date} format="on ll" fromNowDuring={WEEK} /> + </Tooltip> +); + +export default Date; diff --git a/webui/src/components/Label.tsx b/webui/src/components/Label.tsx new file mode 100644 index 00000000..1fb8caea --- /dev/null +++ b/webui/src/components/Label.tsx @@ -0,0 +1,57 @@ +import React from 'react'; + +import { common } from '@material-ui/core/colors'; +import { makeStyles } from '@material-ui/core/styles'; +import { + getContrastRatio, + darken, +} from '@material-ui/core/styles/colorManipulator'; + +import { Color } from 'src/gqlTypes'; + +import { LabelFragment } from './fragments.generated'; + +// Minimum contrast between the background and the text color +const contrastThreshold = 2.5; + +// Guess the text color based on the background color +const getTextColor = (background: string) => + getContrastRatio(background, common.white) >= contrastThreshold + ? common.white // White on dark backgrounds + : common.black; // And black on light ones + +const _rgb = (color: Color) => + 'rgb(' + color.R + ',' + color.G + ',' + color.B + ')'; + +// Create a style object from the label RGB colors +const createStyle = (color: Color) => ({ + backgroundColor: _rgb(color), + color: getTextColor(_rgb(color)), + borderBottomColor: darken(_rgb(color), 0.2), +}); + +const useStyles = makeStyles(theme => ({ + label: { + ...theme.typography.body1, + padding: '1px 6px 0.5px', + fontSize: '0.9em', + fontWeight: 500, + margin: '0.05em 1px calc(-1.5px + 0.05em)', + borderRadius: '3px', + display: 'inline-block', + borderBottom: 'solid 1.5px', + verticalAlign: 'bottom', + }, +})); + +type Props = { label: LabelFragment }; +function Label({ label }: Props) { + const classes = useStyles(); + return ( + <span className={classes.label} style={createStyle(label.color)}> + {label.name} + </span> + ); +} + +export default Label; diff --git a/webui/src/components/fragments.graphql b/webui/src/components/fragments.graphql new file mode 100644 index 00000000..03a235f9 --- /dev/null +++ b/webui/src/components/fragments.graphql @@ -0,0 +1,19 @@ +# Label.tsx +fragment Label on Label { + name + color { + R + G + B + } +} + +# Author.tsx +fragment authored on Authored { + author { + name + email + displayName + avatarUrl + } +} |