Skip to content

Commit

Permalink
feat: add markdown user badge
Browse files Browse the repository at this point in the history
  • Loading branch information
asabotovich committed Jun 24, 2024
1 parent ebf880e commit c62ce93
Show file tree
Hide file tree
Showing 11 changed files with 201 additions and 14 deletions.
128 changes: 124 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@sentry/nextjs": "7.99.0",
"@tanstack/react-query": "4.29.5",
"@tanstack/react-query-devtools": "4.29.6",
"@taskany/bricks": "5.39.0",
"@taskany/bricks": "5.40.1",
"@taskany/colors": "1.13.0",
"@taskany/icons": "2.0.7",
"@tippyjs/react": "4.2.6",
Expand Down
20 changes: 14 additions & 6 deletions src/components/FormControlEditor/FormControlEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,27 @@ export const FormControlEditor = React.forwardRef<HTMLDivElement, React.Componen
});

return {
suggestions: users.map((user) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
suggestions: users.reduce<any[]>((acum, user) => {
if (!user.login) {
return acum;
}

const { login } = user;
const label = getUserName(user);

const startColumn = position.column - query.length;
const endColumn = startColumn + label.length;
const endColumn = startColumn + login.length;

return {
acum.push({
label,
range: {
startColumn,
endColumn,
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
},
insertText: `[${label}](${routes.crewUser(user.id)})`,
insertText: `[${login}](${routes.crewUser(user.login)} "${user.name}")`,
kind: monaco.languages.CompletionItemKind.User,
additionalTextEdits: [
{
Expand All @@ -75,8 +81,10 @@ export const FormControlEditor = React.forwardRef<HTMLDivElement, React.Componen
},
],
filterText: query,
};
}),
});

return acum;
}, []),
};
},
resolveCompletionItem: (item) => {
Expand Down
1 change: 1 addition & 0 deletions src/components/HistoryRecord/HistoryRecord.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

.HistoryUserBadge {
position: relative;
vertical-align: text-bottom;
bottom: -7px;
}

Expand Down
9 changes: 9 additions & 0 deletions src/components/InlineUserBadge/InlineUserBadge.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.InlineUserBadge {
vertical-align: middle;
padding-right: 0;
padding-top: 0;
}

.InlineUserBadge:first-child {
padding-left: 0;
}
26 changes: 26 additions & 0 deletions src/components/InlineUserBadge/InlineUserBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { ComponentProps, FC, useRef } from 'react';
import { Tooltip } from '@taskany/bricks/harmony';
import { nullable } from '@taskany/bricks';

import { UserBadge } from '../UserBadge/UserBadge';

import s from './InlineUserBadge.module.css';

interface InlineUserBadgeProps extends ComponentProps<typeof UserBadge> {
tooltip?: string;
}

export const InlineUserBadge: FC<InlineUserBadgeProps> = ({ tooltip, ...props }) => {
const badgeRef = useRef<HTMLDivElement>(null);

return (
<>
<UserBadge ref={badgeRef} className={s.InlineUserBadge} as="span" {...props} />
{nullable(tooltip, (t) => (
<Tooltip arrow reference={badgeRef} placement="top">
{t}
</Tooltip>
))}
</>
);
};
2 changes: 1 addition & 1 deletion src/components/ProjectTeamPage/ProjectTeamPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export const ProjectTeamPage = ({ user, ssrTime, params: { id } }: ExternalPageP
key={user.id}
className={s.ProjectTeamPageTeamLink}
target="_blank"
href={routes.crewUser(user.id)}
href={routes.crewUserByEmail(user.email)}
>
<TeamMemberListItem
roles={roles}
Expand Down
4 changes: 3 additions & 1 deletion src/components/UserBadge/UserBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ interface UserBadgeProps {
children?: React.ReactNode;
short?: boolean;
size?: React.ComponentProps<typeof User>['size'];
as?: React.ComponentProps<typeof User>['as'];
className?: string;
}

export const UserBadge = forwardRef<HTMLDivElement, UserBadgeProps>(
({ name, image, email, children, short, size = 's', className }, ref) => {
({ name, image, email, children, short, size = 's', as, className }, ref) => {
return (
<User
ref={ref}
Expand All @@ -27,6 +28,7 @@ export const UserBadge = forwardRef<HTMLDivElement, UserBadgeProps>(
iconRight={children}
action="dynamic"
size={size}
as={as}
/>
);
},
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export const routes = {
whatsnew: (release: string, locale: TLocale) => `/whatsnew/${release}/${locale}`,

crewTeam: (id: string) => `${process.env.NEXT_PUBLIC_CREW_URL}teams/${id}`,
crewUser: (id: string) => `${process.env.NEXT_PUBLIC_CREW_URL}users/${id}`,
crewUserByEmail: (email: string) => `${process.env.NEXT_PUBLIC_CREW_URL}users/email/${email}`,
crewUser: (login: string) => `${process.env.NEXT_PUBLIC_CREW_URL}${login}`,
};

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
15 changes: 15 additions & 0 deletions src/hooks/useMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import {
LiHTMLAttributes,
OlHTMLAttributes,
} from 'react';
import NextLink from 'next/link';
import remarkEmoji from 'remark-emoji';
import { useRemarkSync, UseRemarkSyncOptions } from 'react-remark';
import { Image, Link, Text } from '@taskany/bricks/harmony';

import { ModalEvent, dispatchModalEvent } from '../utils/dispatchModal';
import { parseCrewLink } from '../utils/crew';
import { InlineUserBadge } from '../components/InlineUserBadge/InlineUserBadge';

const mdAndPlainUrls =
/((\[.*\])?(\(|<))?((https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})(?:(\s?("|').*("|'))?))((\)|>)?)/gi;
Expand Down Expand Up @@ -89,6 +92,18 @@ const ssrRenderOptions: UseRemarkSyncOptions = {
rehypeReactOptions: {
components: {
...markdownComponents,
a: ({ href, title, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) => {
const login = href ? parseCrewLink(href) : '';

if (href && login) {
return (
<NextLink href={href} passHref target="_blank">
<InlineUserBadge tooltip={title} name={login} email={login} size="xs" />
</NextLink>
);
}
return <Link {...props} title={title} href={href} target="_blank" view="inline" />;
},
img: (props: React.ComponentProps<typeof Image>) => (
<Image
{...props}
Expand Down
5 changes: 5 additions & 0 deletions src/utils/crew.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const crewLinkRegExp = new RegExp(`^${process.env.NEXT_PUBLIC_CREW_URL}(ru/|en/)?(?<login>[^/]+)/?$`);

export const parseCrewLink = (link: string): string => {
return link.match(crewLinkRegExp)?.groups?.login ?? '';
};

0 comments on commit c62ce93

Please sign in to comment.