summaryrefslogtreecommitdiffstatshomepage
path: root/webui/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'webui/src/components')
-rw-r--r--webui/src/components/Author.tsx2
-rw-r--r--webui/src/components/BackToListButton.tsx2
-rw-r--r--webui/src/components/BugTitleForm/BugTitleForm.tsx4
-rw-r--r--webui/src/components/CommentInput/CommentInput.tsx2
-rw-r--r--webui/src/components/Content/AnchorTag.tsx2
-rw-r--r--webui/src/components/Content/index.tsx98
-rw-r--r--webui/src/components/Date.tsx3
-rw-r--r--webui/src/components/Header/Header.tsx61
-rw-r--r--webui/src/components/Identity/CurrentIdentity.tsx2
-rw-r--r--webui/src/components/Identity/CurrentRepository.tsx19
-rw-r--r--webui/src/components/Label.tsx4
-rw-r--r--webui/src/components/Moment.tsx28
-rw-r--r--webui/src/components/Themer.tsx2
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: '',
});