diff options
Diffstat (limited to 'webui/src/components')
-rw-r--r-- | webui/src/components/Author.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/BackToListButton.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/BugTitleForm/BugTitleForm.tsx | 4 | ||||
-rw-r--r-- | webui/src/components/CommentInput/CommentInput.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/Content/AnchorTag.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/Content/index.tsx | 98 | ||||
-rw-r--r-- | webui/src/components/Date.tsx | 3 | ||||
-rw-r--r-- | webui/src/components/Header/Header.tsx | 61 | ||||
-rw-r--r-- | webui/src/components/Identity/CurrentIdentity.tsx | 2 | ||||
-rw-r--r-- | webui/src/components/Identity/CurrentRepository.tsx | 19 | ||||
-rw-r--r-- | webui/src/components/Label.tsx | 4 | ||||
-rw-r--r-- | webui/src/components/Moment.tsx | 28 | ||||
-rw-r--r-- | webui/src/components/Themer.tsx | 2 |
13 files changed, 140 insertions, 89 deletions
diff --git a/webui/src/components/Author.tsx b/webui/src/components/Author.tsx index 92e14d40f..51ed336ab 100644 --- a/webui/src/components/Author.tsx +++ b/webui/src/components/Author.tsx @@ -1,7 +1,7 @@ import MAvatar from '@mui/material/Avatar'; import Link from '@mui/material/Link'; import Tooltip from '@mui/material/Tooltip/Tooltip'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink } from 'react-router'; import { AuthoredFragment } from '../graphql/fragments.generated'; diff --git a/webui/src/components/BackToListButton.tsx b/webui/src/components/BackToListButton.tsx index a4e4ea9c8..234d14588 100644 --- a/webui/src/components/BackToListButton.tsx +++ b/webui/src/components/BackToListButton.tsx @@ -1,7 +1,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import Button from '@mui/material/Button'; import makeStyles from '@mui/styles/makeStyles'; -import { Link } from 'react-router-dom'; +import { Link } from 'react-router'; const useStyles = makeStyles((theme) => ({ backButton: { diff --git a/webui/src/components/BugTitleForm/BugTitleForm.tsx b/webui/src/components/BugTitleForm/BugTitleForm.tsx index 78b9e901b..82185d4fd 100644 --- a/webui/src/components/BugTitleForm/BugTitleForm.tsx +++ b/webui/src/components/BugTitleForm/BugTitleForm.tsx @@ -1,7 +1,7 @@ import { Button, Typography } from '@mui/material'; import makeStyles from '@mui/styles/makeStyles'; import { useRef, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from 'react-router'; import { TimelineDocument } from '../../pages/bug/TimelineQuery.generated'; import IfLoggedIn from '../IfLoggedIn/IfLoggedIn'; @@ -71,7 +71,7 @@ function BugTitleForm({ bug }: Props) { const [setTitle, { loading, error }] = useSetTitleMutation(); const [issueTitle, setIssueTitle] = useState(bug.title); const classes = useStyles(); - const issueTitleInput = useRef<HTMLInputElement>(); + const issueTitleInput = useRef<HTMLInputElement>(null); function isFormValid() { if (issueTitleInput.current) { diff --git a/webui/src/components/CommentInput/CommentInput.tsx b/webui/src/components/CommentInput/CommentInput.tsx index 0fde1d95b..4d4c3b633 100644 --- a/webui/src/components/CommentInput/CommentInput.tsx +++ b/webui/src/components/CommentInput/CommentInput.tsx @@ -102,7 +102,7 @@ function CommentInput({ inputProps, inputText, loading, onChange }: Props) { multiline value={input} variant="filled" - rows="4" // TODO: rowsMin support + minRows={4} onChange={(e: any) => setInput(e.target.value)} disabled={loading} /> diff --git a/webui/src/components/Content/AnchorTag.tsx b/webui/src/components/Content/AnchorTag.tsx index e9edef954..da861bc2d 100644 --- a/webui/src/components/Content/AnchorTag.tsx +++ b/webui/src/components/Content/AnchorTag.tsx @@ -1,6 +1,6 @@ import makeStyles from '@mui/styles/makeStyles'; import * as React from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from 'react-router'; const useStyles = makeStyles((theme) => ({ tag: { diff --git a/webui/src/components/Content/index.tsx b/webui/src/components/Content/index.tsx index 9bf9ff7a6..3cfa9eb89 100644 --- a/webui/src/components/Content/index.tsx +++ b/webui/src/components/Content/index.tsx @@ -1,11 +1,24 @@ -import { createElement, Fragment, useEffect, useState } from 'react'; +import type { Root as HastRoot } from 'hast'; +import type { Root as MdRoot } from 'mdast'; +import { useEffect, useState } from 'react'; import * as React from 'react'; +import * as production from 'react/jsx-runtime'; +import rehypeHighlight, { + Options as RehypeHighlightOpts, +} from 'rehype-highlight'; import rehypeReact from 'rehype-react'; -import gemoji from 'remark-gemoji'; -import html from 'remark-html'; -import parse from 'remark-parse'; +import rehypeSanitize from 'rehype-sanitize'; +import remarkBreaks from 'remark-breaks'; +import remarkGemoji from 'remark-gemoji'; +import remarkGfm from 'remark-gfm'; +import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; +import type { Options as RemarkRehypeOptions } from 'remark-rehype'; import { unified } from 'unified'; +import type { Plugin, Processor } from 'unified'; +import { Node as UnistNode } from 'unified/lib'; + +import { ThemeContext } from '../Themer'; import AnchorTag from './AnchorTag'; import BlockQuoteTag from './BlockQuoteTag'; @@ -13,32 +26,71 @@ import ImageTag from './ImageTag'; import PreTag from './PreTag'; type Props = { markdown: string }; + +// @lygaret 2025/05/16 +// type inference for some of this doesn't work, but the pipeline is fine +// this might get better when we upgrade typescript + +type RemarkPlugin = Plugin<[], MdRoot, HastRoot>; +type RemarkRehypePlugin = Plugin<RemarkRehypeOptions[], MdRoot, HastRoot>; +type RehypePlugin<Options extends unknown[] = []> = Plugin< + Options, + HastRoot, + HastRoot +>; + +const markdownPipeline: Processor< + UnistNode, + undefined, + undefined, + HastRoot, + React.JSX.Element +> = unified() + .use(remarkParse) + .use(remarkGemoji as unknown as RemarkPlugin) + .use(remarkBreaks as unknown as RemarkPlugin) + .use(remarkGfm) + .use(remarkRehype as unknown as RemarkRehypePlugin, { + allowDangerousHtml: true, + }) + .use(rehypeSanitize as unknown as RehypePlugin) + .use(rehypeHighlight as unknown as RehypePlugin<RehypeHighlightOpts[]>, { + detect: true, + subset: ['text'], + }) + .use(rehypeReact, { + ...production, + components: { + a: AnchorTag, + blockquote: BlockQuoteTag, + img: ImageTag, + pre: PreTag, + }, + }); + const Content: React.FC<Props> = ({ markdown }: Props) => { - const [Content, setContent] = useState(<></>); + const theme = React.useContext(ThemeContext); + const [content, setContent] = useState(<></>); useEffect(() => { - unified() - .use(parse) - .use(gemoji) - .use(html) - .use(remarkRehype) - .use(rehypeReact, { - createElement, - Fragment, - components: { - img: ImageTag, - pre: PreTag, - a: AnchorTag, - blockquote: BlockQuoteTag, - }, - }) + markdownPipeline .process(markdown) - .then((file) => { - setContent(file.result); + .then((file) => setContent(file.result)) + .catch((err: any) => { + setContent( + <> + <span className="error">{err}</span> + <pre>{markdown}</pre> + </> + ); }); }, [markdown]); - return Content; + return ( + <div className={'highlight-theme'} data-theme={theme.mode}> + {content} + </div> + ); }; export default Content; diff --git a/webui/src/components/Date.tsx b/webui/src/components/Date.tsx index 1454fac77..19efc081e 100644 --- a/webui/src/components/Date.tsx +++ b/webui/src/components/Date.tsx @@ -1,6 +1,7 @@ import Tooltip from '@mui/material/Tooltip/Tooltip'; import moment from 'moment'; -import Moment from 'react-moment'; + +import Moment from './Moment'; const HOUR = 1000 * 3600; const DAY = 24 * HOUR; diff --git a/webui/src/components/Header/Header.tsx b/webui/src/components/Header/Header.tsx index 5c03a5ecf..69b3fdfb2 100644 --- a/webui/src/components/Header/Header.tsx +++ b/webui/src/components/Header/Header.tsx @@ -1,13 +1,11 @@ import AppBar from '@mui/material/AppBar'; -import Tab, { TabProps } from '@mui/material/Tab'; -import Tabs from '@mui/material/Tabs'; import Toolbar from '@mui/material/Toolbar'; -import Tooltip from '@mui/material/Tooltip/Tooltip'; import { alpha } from '@mui/material/styles'; import makeStyles from '@mui/styles/makeStyles'; -import { Link, useLocation } from 'react-router-dom'; +import { Link } from 'react-router'; import CurrentIdentity from '../Identity/CurrentIdentity'; +import CurrentRepository from '../Identity/CurrentRepository'; import { LightSwitch } from '../Themer'; const useStyles = makeStyles((theme) => ({ @@ -29,7 +27,7 @@ const useStyles = makeStyles((theme) => ({ alignItems: 'center', }, lightSwitch: { - marginRight: '20px', + marginRight: theme.spacing(2), color: alpha(theme.palette.primary.contrastText, 0.5), }, logo: { @@ -38,43 +36,8 @@ const useStyles = makeStyles((theme) => ({ }, })); -function a11yProps(index: any) { - return { - id: `nav-tab-${index}`, - 'aria-controls': `nav-tabpanel-${index}`, - }; -} - -const DisabledTabWithTooltip = (props: TabProps) => { - /*The span elements around disabled tabs are needed, as the tooltip - * won't be triggered by disabled elements. - * See: https://material-ui.com/components/tooltips/#disabled-elements - * This must be done in a wrapper component, otherwise the TabS component - * cannot pass it styles down to the Tab component. Resulting in (console) - * warnings. This wrapper accepts the passed down TabProps and pass it around - * the span element to the Tab component. - */ - const msg = `This feature doesn't exist yet. Come help us build it.`; - return ( - <Tooltip title={msg}> - <span> - <Tab disabled {...props} /> - </span> - </Tooltip> - ); -}; - function Header() { const classes = useStyles(); - const location = useLocation(); - - // Prevents error of invalid tab selection in <Tabs> - // Will return a valid tab path or false if path is unknown. - function highlightTab() { - const validTabs = ['/', '/code', '/pulls', '/settings']; - const tab = validTabs.find((tabPath) => tabPath === location.pathname); - return tab === undefined ? false : tab; - } return ( <> @@ -82,28 +45,14 @@ function Header() { <Toolbar> <Link to="/" className={classes.appTitle}> <img src="/logo.svg" className={classes.logo} alt="git-bug logo" /> - git-bug + <CurrentRepository default="git-bug" /> </Link> <div className={classes.filler} /> <LightSwitch className={classes.lightSwitch} /> <CurrentIdentity /> </Toolbar> </AppBar> - <div className={classes.offset} /> - <Tabs centered value={highlightTab()} aria-label="nav tabs"> - <DisabledTabWithTooltip label="Code" value="/code" {...a11yProps(1)} /> - <Tab label="Bugs" value="/" component={Link} to="/" {...a11yProps(2)} /> - <DisabledTabWithTooltip - label="Pull Requests" - value="/pulls" - {...a11yProps(3)} - /> - <DisabledTabWithTooltip - label="Settings" - value="/settings" - {...a11yProps(4)} - /> - </Tabs> + <div className={classes.offset}></div> </> ); } diff --git a/webui/src/components/Identity/CurrentIdentity.tsx b/webui/src/components/Identity/CurrentIdentity.tsx index e6a396a8e..fb06898ec 100644 --- a/webui/src/components/Identity/CurrentIdentity.tsx +++ b/webui/src/components/Identity/CurrentIdentity.tsx @@ -12,7 +12,7 @@ import { import Avatar from '@mui/material/Avatar'; import makeStyles from '@mui/styles/makeStyles'; import { useState, useRef } from 'react'; -import { Link as RouterLink } from 'react-router-dom'; +import { Link as RouterLink } from 'react-router'; import { useCurrentIdentityQuery } from './CurrentIdentity.generated'; diff --git a/webui/src/components/Identity/CurrentRepository.tsx b/webui/src/components/Identity/CurrentRepository.tsx new file mode 100644 index 000000000..77aa6839b --- /dev/null +++ b/webui/src/components/Identity/CurrentRepository.tsx @@ -0,0 +1,19 @@ +import { useCurrentIdentityQuery } from './CurrentIdentity.generated'; + +// same as in multi_repo_cache.go +const defaultRepoName = '__default'; + +const CurrentRepository = (props: { default: string }) => { + const { loading, error, data } = useCurrentIdentityQuery(); + + if (error || loading || !data?.repository?.name) return null; + + let name = data.repository.name; + if (name === defaultRepoName) { + name = props.default; + } + + return <>{name}</>; +}; + +export default CurrentRepository; diff --git a/webui/src/components/Label.tsx b/webui/src/components/Label.tsx index 709aceff9..a63398487 100644 --- a/webui/src/components/Label.tsx +++ b/webui/src/components/Label.tsx @@ -26,14 +26,16 @@ const createStyle = (color: Color, maxWidth?: string) => ({ type Props = { label: LabelFragment; + inline?: boolean; maxWidth?: string; className?: string; }; -function Label({ label, maxWidth, className }: Props) { +function Label({ label, inline, maxWidth, className }: Props) { return ( <Chip size={'small'} label={label.name} + component={inline ? 'span' : 'div'} className={className} style={createStyle(label.color, maxWidth)} /> diff --git a/webui/src/components/Moment.tsx b/webui/src/components/Moment.tsx new file mode 100644 index 000000000..4bffbd318 --- /dev/null +++ b/webui/src/components/Moment.tsx @@ -0,0 +1,28 @@ +import moment from 'moment'; + +type Props = { + date: moment.MomentInput; + format: string; + fromNowDuring?: number; +}; + +const Moment = ({ date, format, fromNowDuring }: Props) => { + let dateString: string | undefined; + const dateMoment = moment(date); + + if (fromNowDuring) { + const diff = moment().diff(dateMoment, 'ms'); + if (diff < fromNowDuring) { + dateString = dateMoment.fromNow(); + } + } + + // we either are out of range or didn't get asked for fromNow + if (dateString === undefined) { + dateString = dateMoment.format(format); + } + + return <span>{dateString}</span>; +}; + +export default Moment; diff --git a/webui/src/components/Themer.tsx b/webui/src/components/Themer.tsx index 9934888d1..f03881b4d 100644 --- a/webui/src/components/Themer.tsx +++ b/webui/src/components/Themer.tsx @@ -11,7 +11,7 @@ declare module '@mui/styles/defaultTheme' { interface DefaultTheme extends Theme {} } -const ThemeContext = createContext({ +export const ThemeContext = createContext({ toggleMode: () => {}, mode: '', }); |