@selfcommunity/react-ui 0.10.2-alpha.9 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/lib/cjs/components/CommentObject/CommentObject.js +1 -1
  2. package/lib/cjs/components/EditEventButton/EditEventButton.js +2 -2
  3. package/lib/cjs/components/Editor/plugins/AutoLinkPlugin.js +7 -1
  4. package/lib/cjs/components/Event/Event.js +4 -4
  5. package/lib/cjs/components/EventInviteButton/EventInviteButton.js +5 -5
  6. package/lib/cjs/components/FeedObject/FeedObject.js +2 -2
  7. package/lib/cjs/components/NavigationMenuIconButton/DefaultDrawerContent.js +24 -2
  8. package/lib/cjs/components/NavigationToolbar/NavigationToolbar.js +1 -1
  9. package/lib/cjs/components/Notification/Comment/Comment.js +2 -2
  10. package/lib/cjs/components/Notification/Notification.js +1 -1
  11. package/lib/cjs/components/PrivateMessageThread/PrivateMessageThread.js +3 -6
  12. package/lib/cjs/utils/contribution.d.ts +7 -0
  13. package/lib/cjs/utils/contribution.js +13 -1
  14. package/lib/esm/components/CommentObject/CommentObject.js +2 -2
  15. package/lib/esm/components/EditEventButton/EditEventButton.js +2 -2
  16. package/lib/esm/components/Editor/plugins/AutoLinkPlugin.js +7 -1
  17. package/lib/esm/components/Event/Event.js +4 -4
  18. package/lib/esm/components/EventInviteButton/EventInviteButton.js +5 -5
  19. package/lib/esm/components/FeedObject/FeedObject.js +2 -2
  20. package/lib/esm/components/NavigationMenuIconButton/DefaultDrawerContent.js +27 -5
  21. package/lib/esm/components/NavigationToolbar/NavigationToolbar.js +1 -1
  22. package/lib/esm/components/Notification/Comment/Comment.js +2 -2
  23. package/lib/esm/components/Notification/Notification.js +1 -1
  24. package/lib/esm/components/PrivateMessageThread/PrivateMessageThread.js +3 -6
  25. package/lib/esm/utils/contribution.d.ts +7 -0
  26. package/lib/esm/utils/contribution.js +11 -0
  27. package/lib/umd/react-ui.js +2 -2
  28. package/package.json +7 -7
@@ -1,17 +1,21 @@
1
1
  import { __rest } from "tslib";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Box, Button, ListItem, Typography, Zoom } from '@mui/material';
4
- import { Link, SCRoutes, useSCFetchCategories, useSCRouting } from '@selfcommunity/react-core';
5
- import { useEffect, useState } from 'react';
3
+ import { Box, Divider, Icon, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Typography, Zoom } from '@mui/material';
4
+ import { Link, SCPreferences, SCRoutes, useSCFetchCategories, useSCPreferences, useSCRouting, useSCUser } from '@selfcommunity/react-core';
5
+ import { useEffect, useMemo, useState } from 'react';
6
6
  import Category from '../Category';
7
7
  import { FormattedMessage } from 'react-intl';
8
8
  import { sortByAttr } from '@selfcommunity/utils';
9
+ import { SCFeatureName } from '@selfcommunity/types';
9
10
  import { styled } from '@mui/material/styles';
10
11
  import classNames from 'classnames';
11
12
  import { useThemeProps } from '@mui/system';
12
13
  const PREFIX = 'SCDefaultDrawerContent';
13
14
  const classes = {
14
- root: `${PREFIX}-root`
15
+ root: `${PREFIX}-root`,
16
+ navigation: `${PREFIX}-navigation`,
17
+ noResults: `${PREFIX}-no-results`,
18
+ title: `${PREFIX}-title`
15
19
  };
16
20
  const Root = styled(Box, {
17
21
  name: PREFIX,
@@ -29,8 +33,26 @@ export default function DefaultDrawerContent(inProps) {
29
33
  const [categoriesOrdered, setCategoriesOrdered] = useState([]);
30
34
  // ROUTING
31
35
  const scRoutingContext = useSCRouting();
36
+ // CONTEXT
37
+ const scUserContext = useSCUser();
38
+ // PREFERENCES
39
+ const { preferences, features } = useSCPreferences();
32
40
  //STATE
33
41
  const [isHovered, setIsHovered] = useState({});
42
+ // MEMO
43
+ const groupsEnabled = useMemo(() => preferences &&
44
+ features &&
45
+ features.includes(SCFeatureName.TAGGING) &&
46
+ features.includes(SCFeatureName.GROUPING) &&
47
+ SCPreferences.CONFIGURATIONS_GROUPS_ENABLED in preferences &&
48
+ preferences[SCPreferences.CONFIGURATIONS_GROUPS_ENABLED].value, [preferences, features]);
49
+ const eventsEnabled = useMemo(() => preferences &&
50
+ features &&
51
+ features.includes(SCFeatureName.TAGGING) &&
52
+ SCPreferences.CONFIGURATIONS_EVENTS_ENABLED in preferences &&
53
+ preferences[SCPreferences.CONFIGURATIONS_EVENTS_ENABLED].value, [preferences, features]);
54
+ const exploreStreamEnabled = preferences[SCPreferences.CONFIGURATIONS_EXPLORE_STREAM_ENABLED].value;
55
+ const contentAvailable = preferences[SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY].value;
34
56
  // HANDLERS
35
57
  const handleMouseEnter = (index) => {
36
58
  setIsHovered((prevState) => {
@@ -53,5 +75,5 @@ export default function DefaultDrawerContent(inProps) {
53
75
  onTouchMove: mouseLeave
54
76
  });
55
77
  //order
56
- return (_jsxs(Root, Object.assign({ className: classNames(className, classes.root) }, rest, { children: [_jsxs(Typography, Object.assign({ variant: "subtitle1" }, { children: [_jsx("span", { children: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.category.title", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.category.title" }) }), ' ', _jsx(Button, Object.assign({ variant: "text", component: Link, color: "secondary", to: scRoutingContext.url(SCRoutes.CATEGORIES_LIST_ROUTE_NAME, {}) }, { children: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.category.seeAll", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.category.seeAll" }) }))] })), categoriesOrdered.map((c, index) => (_jsx(Zoom, Object.assign({ in: true, style: { transform: isHovered[c.id] && 'scale(1.05)' } }, { children: _jsx(ListItem, { children: _jsx(Category, Object.assign({ ButtonBaseProps: { component: Link, to: scRoutingContext.url(SCRoutes.CATEGORY_ROUTE_NAME, c) }, elevation: 0, category: c, actions: null }, CategoryItemProps, getMouseEvents(() => handleMouseEnter(c.id), () => handleMouseLeave(c.id)))) }, c.id) }), index)))] })));
78
+ return (_jsxs(Root, Object.assign({ className: classNames(className, classes.root) }, rest, { children: [_jsxs(List, Object.assign({ className: classes.navigation }, { children: [_jsx(ListItem, Object.assign({ disablePadding: true }, { children: _jsxs(ListItemButton, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.HOME_ROUTE_NAME, {}) }, { children: [_jsx(ListItemIcon, { children: _jsx(Icon, { children: "home" }) }), _jsx(ListItemText, { primary: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.home", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.home" }) })] })) })), groupsEnabled && scUserContext.user && (_jsx(ListItem, Object.assign({ disablePadding: true }, { children: _jsxs(ListItemButton, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.GROUPS_ROUTE_NAME, {}) }, { children: [_jsx(ListItemIcon, { children: _jsx(Icon, { children: "groups" }) }), _jsx(ListItemText, { primary: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.groups", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.groups" }) })] })) }))), eventsEnabled && (scUserContext.user || contentAvailable) && (_jsx(ListItem, Object.assign({ disablePadding: true }, { children: _jsxs(ListItemButton, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.EVENTS_ROUTE_NAME, {}) }, { children: [_jsx(ListItemIcon, { children: _jsx(Icon, { children: "CalendarIcon" }) }), _jsx(ListItemText, { primary: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.events", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.events" }) })] })) }))), exploreStreamEnabled && (contentAvailable || scUserContext.user) && (_jsx(ListItem, Object.assign({ disablePadding: true }, { children: _jsxs(ListItemButton, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.EXPLORE_ROUTE_NAME, {}) }, { children: [_jsx(ListItemIcon, { children: _jsx(Icon, { children: "explore" }) }), _jsx(ListItemText, { primary: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.explore", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.navigation.explore" }) })] })) })))] })), _jsx(Divider, {}), _jsx(Typography, Object.assign({ variant: "subtitle1", className: classes.title }, { children: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.category.title", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.category.title" }) })), !categoriesOrdered.length && (_jsx(Typography, Object.assign({ variant: "body1", className: classes.noResults }, { children: _jsx(FormattedMessage, { id: "ui.navigationMenuIconButton.defaultDrawerContent.category.noResults", defaultMessage: "ui.navigationMenuIconButton.defaultDrawerContent.category.noResults" }) }))), categoriesOrdered.map((c, index) => (_jsx(Zoom, Object.assign({ in: true, style: { transform: isHovered[c.id] && 'scale(1.05)' } }, { children: _jsx(ListItem, { children: _jsx(Category, Object.assign({ ButtonBaseProps: { component: Link, to: scRoutingContext.url(SCRoutes.CATEGORY_ROUTE_NAME, c) }, elevation: 0, category: c, actions: null }, CategoryItemProps, getMouseEvents(() => handleMouseEnter(c.id), () => handleMouseLeave(c.id)))) }, c.id) }), index)))] })));
57
79
  }
@@ -144,7 +144,7 @@ export default function NavigationToolbar(inProps) {
144
144
  value.startsWith(scRoutingContext.url(SCRoutes.GROUPS_ROUTE_NAME, {}))
145
145
  }), "aria-label": "Groups", to: scRoutingContext.url(SCRoutes.GROUPS_SUBSCRIBED_ROUTE_NAME, {}), component: Link }, { children: _jsx(Icon, { children: "groups" }) }))), eventsEnabled && (scUserContext.user || preferences[SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY]) && (_jsx(IconButton, Object.assign({ className: classNames(classes.events, {
146
146
  [classes.active]: value.startsWith(scRoutingContext.url(SCRoutes.EVENTS_ROUTE_NAME, {}))
147
- }), "aria-label": "Groups", to: scRoutingContext.url(SCRoutes.EVENTS_ROUTE_NAME, {}), component: Link }, { children: _jsx(Icon, { children: "CalendarIcon" }) })))] })));
147
+ }), "aria-label": "Events", to: scRoutingContext.url(SCRoutes.EVENTS_ROUTE_NAME, {}), component: Link }, { children: _jsx(Icon, { children: "CalendarIcon" }) })))] })));
148
148
  return (_jsxs(Root, Object.assign({ className: classNames(className, classes.root) }, rest, { children: [_jsx(NavigationMenuIconButtonComponent, Object.assign({}, NavigationMenuIconButtonComponentProps)), _jsx(Link, Object.assign({ to: scRoutingContext.url(SCRoutes.HOME_ROUTE_NAME, {}), className: classes.logo }, { children: _jsx("img", { src: preferences[SCPreferences.LOGO_NAVBAR_LOGO], alt: "logo" }) })), !scUserContext.user && !preferences[SCPreferences.ADDONS_CLOSED_COMMUNITY] && (_jsx(Button, Object.assign({ color: "inherit", component: Link, to: scRoutingContext.url(SCRoutes.SIGNUP_ROUTE_NAME, {}), className: classes.register }, { children: _jsx(FormattedMessage, { id: "ui.appBar.navigation.register", defaultMessage: "ui.appBar.navigation.register" }) }))), preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_ENABLED] && (_jsx(_Fragment, { children: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_TEXT] ? (_jsx(Tooltip, Object.assign({ title: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_TEXT] }, { children: _jsx(Link, Object.assign({ target: "blank", to: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_URL], className: classes.customItem }, { children: _jsx("img", { src: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_IMAGE], alt: "custom_item" }) })) }))) : (_jsx(Link, Object.assign({ target: "blank", to: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_URL], className: classes.customItem }, { children: _jsx("img", { src: preferences[SCPreferences.CONFIGURATIONS_CUSTOM_NAVBAR_ITEM_IMAGE], alt: "custom_item" }) }))) })), _children, (preferences[SCPreferences.CONFIGURATIONS_CONTENT_AVAILABILITY] || scUserContext.user) && !disableSearch ? (_jsx(SearchAutocomplete, Object.assign({ className: classes.search, blurOnSelect: true }, SearchAutocompleteProps))) : (_jsx(Box, { className: classes.search })), startActions, scUserContext.user ? (_jsxs(_Fragment, { children: [showComposer && _jsx(ComposerIconButton, Object.assign({ className: classes.composer }, ComposerIconButtonProps)), _jsx(Tooltip, Object.assign({ title: scUserContext.user.username }, { children: _jsx(IconButton, Object.assign({ component: Link, to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, scUserContext.user), "aria-label": "Profile", className: classes.profile }, { children: _jsx(Avatar, { alt: scUserContext.user.username, src: scUserContext.user.avatar }) })) })), _jsxs(_Fragment, { children: [_jsx(IconButton, Object.assign({ className: classNames(classes.notification, {
149
149
  [classes.active]: value.startsWith(scRoutingContext.url(SCRoutes.USER_NOTIFICATIONS_ROUTE_NAME, {}))
150
150
  }), "aria-label": "Notification", onClick: handleOpenNotificationMenu }, { children: _jsx(Badge, Object.assign({ badgeContent: scUserContext.user.unseen_notification_banners_counter + scUserContext.user.unseen_interactions_counter, color: "secondary" }, { children: _jsx(Icon, { children: "notifications_active" }) })) })), _jsx(NotificationMenu, Object.assign({ className: classes.notificationsMenu, id: "notification-menu", anchorEl: anchorNotification, open: Boolean(anchorNotification), onClose: handleCloseNotificationMenu, onClick: handleCloseNotificationMenu, transformOrigin: { horizontal: 'right', vertical: 'top' }, anchorOrigin: { horizontal: 'right', vertical: 'bottom' } }, NotificationMenuProps))] }), privateMessagingEnabled && (_jsx(IconButton, Object.assign({ className: classNames(classes.messages, {
@@ -65,10 +65,10 @@ export default function CommentNotification(props) {
65
65
  }), { onClick: notificationObject.comment.author.deleted ? () => setOpenAlert(true) : null }, { children: _jsx(UserAvatar, Object.assign({ hide: !notificationObject.comment.author.community_badge, smaller: true }, { children: _jsx(Avatar, { alt: notificationObject.comment.author.username, variant: "circular", src: notificationObject.comment.author.avatar, classes: { root: classes.avatar } }) })) })), primary: _jsxs(_Fragment, { children: [_jsx(Link, Object.assign({}, (!notificationObject.comment.author.deleted && {
66
66
  to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, notificationObject.comment.author)
67
67
  }), { onClick: notificationObject.comment.author.deleted ? () => setOpenAlert(true) : null, className: classes.username }, { children: notificationObject.comment.author.username })), ' ', notificationObject.type === SCNotificationTypologyType.NESTED_COMMENT
68
- ? intl.formatMessage(messages.comment, {
68
+ ? intl.formatMessage(messages.nestedComment, {
69
69
  b: (...chunks) => _jsx("strong", { children: chunks })
70
70
  })
71
- : intl.formatMessage(messages.nestedComment, {
71
+ : intl.formatMessage(messages.comment, {
72
72
  b: (...chunks) => _jsx("strong", { children: chunks })
73
73
  })] }), secondary: _jsxs(React.Fragment, { children: [_jsx(Link, Object.assign({ to: scRoutingContext.url(SCRoutes.COMMENT_ROUTE_NAME, getRouteData(notificationObject.comment)) }, { children: _jsx(Typography, Object.assign({ variant: "body2", className: classes.contributionText }, { children: getContributionSnippet(notificationObject.comment) })) })), (template === SCNotificationObjectTemplateType.DETAIL || template === SCNotificationObjectTemplateType.SNIPPET) && (_jsxs(Stack, Object.assign({ direction: "row", justifyContent: "flex-start", alignItems: "center", spacing: 1 }, { children: [_jsx(DateTimeAgo, { date: notificationObject.active_at, className: classes.activeAt }), _jsx(Bullet, { className: classes.bullet }), _jsx(VoteButton, { className: classes.voteButton, variant: "text", size: "small", contributionId: notificationObject.comment.id, contributionType: SCContributionType.COMMENT, contribution: notificationObject.comment, onVote: handleVote })] })))] }), footer: template === SCNotificationObjectTemplateType.TOAST && (_jsxs(Stack, Object.assign({ direction: "row", justifyContent: "space-between", alignItems: "center", spacing: 2 }, { children: [_jsx(DateTimeAgo, { date: notificationObject.active_at }), _jsx(Typography, Object.assign({ color: "primary", component: 'div' }, { children: _jsx(Link, Object.assign({ to: scRoutingContext.url(SCRoutes.COMMENT_ROUTE_NAME, getRouteData(notificationObject.comment)) }, { children: _jsx(FormattedMessage, { id: "ui.userToastNotifications.viewContribution", defaultMessage: 'ui.userToastNotifications.viewContribution' }) })) }))] }))) }, rest)), openAlert && _jsx(UserDeletedSnackBar, { open: openAlert, handleClose: () => setOpenAlert(false) })] }));
74
74
  }
@@ -224,7 +224,7 @@ export default function UserNotification(inProps) {
224
224
  notificationObject.aggregated[0].type === SCNotificationTypologyType.VOTE_UP ||
225
225
  notificationObject.aggregated[0].type === SCNotificationTypologyType.CONTRIBUTION)) {
226
226
  const contribution = getContribution(notificationObject);
227
- return (_jsx(CardHeader, { className: classes.header, titleTypographyProps: { className: classes.title, variant: 'subtitle1' }, title: _jsx(Link, Object.assign({ to: scRoutingContext.url(getContributionRouteName(contribution), getRouteData(notificationObject[contribution.type])) }, { children: getContributionSnippet(contribution) })), action: contribution && (_jsx(Tooltip, Object.assign({ title: contribution.suspended ? (_jsx(FormattedMessage, { id: 'ui.notification.notificationSuspended', defaultMessage: 'ui.notification.notificationSuspended' })) : (_jsx(FormattedMessage, { id: 'ui.notification.notificationSuspend', defaultMessage: 'ui.notification.notificationSuspend' })) }, { children: _jsx(LoadingButton, Object.assign({ variant: "text", size: "small", loading: loadingSuspendNotification, color: 'inherit', classes: { root: classes.stopButton }, onClick: () => handleStopContentNotification(contribution) }, { children: contribution.suspended ? _jsx(Icon, Object.assign({ color: 'primary' }, { children: "notifications_off" })) : _jsx(Icon, Object.assign({ color: 'inherit' }, { children: "notifications" })) })) }))) }));
227
+ return (_jsx(CardHeader, { className: classes.header, titleTypographyProps: { className: classes.title, variant: 'subtitle1' }, title: _jsx(Link, Object.assign({ to: scRoutingContext.url(getContributionRouteName(contribution), getRouteData(notificationObject[contribution.type])) }, { children: getContributionSnippet(contribution) })), action: contribution && (_jsx(Tooltip, Object.assign({ title: contribution.suspended ? (_jsx(FormattedMessage, { id: 'ui.notification.notificationSuspended', defaultMessage: 'ui.notification.notificationSuspended' })) : (_jsx(FormattedMessage, { id: 'ui.notification.notificationSuspend', defaultMessage: 'ui.notification.notificationSuspend' })) }, { children: _jsx(LoadingButton, Object.assign({ variant: "text", size: "small", loading: loadingSuspendNotification, color: 'inherit', classes: { root: classes.stopButton }, onClick: () => handleStopContentNotification(contribution) }, { children: contribution.suspended ? _jsx(Icon, Object.assign({ color: 'primary' }, { children: "notifications_off" })) : _jsx(Icon, Object.assign({ color: 'inherit' }, { children: "notifications_active" })) })) }))) }));
228
228
  }
229
229
  return null;
230
230
  }
@@ -175,12 +175,9 @@ export default function PrivateMessageThread(inProps) {
175
175
  * Memoized message recipients ids
176
176
  */
177
177
  const ids = useMemo(() => {
178
- if (recipients && openNewMessage) {
179
- return recipients.map((u) => {
180
- return parseInt(u.id, 10);
181
- });
182
- }
183
- return [parseInt(recipients === null || recipients === void 0 ? void 0 : recipients.id, 10) || recipients];
178
+ if (!recipients)
179
+ return [];
180
+ return Array.isArray(recipients) ? recipients.map((u) => parseInt(u.id, 10)) : [parseInt(recipients.id || recipients, 10)];
184
181
  }, [recipients, openNewMessage]);
185
182
  function fetchResults() {
186
183
  setLoading(true);
@@ -21,6 +21,13 @@ export declare function getContributionSnippet(obj: any): any;
21
21
  * @param handleUrl Func that handle urls
22
22
  */
23
23
  export declare function getContributionHtml(html: any, handleUrl: any): any;
24
+ /**
25
+ * Get the contribution text for comment object
26
+ * Hydrate text with mention, etc.
27
+ * @param html Html of the contribution
28
+ * @param handleUrl Func that handle urls
29
+ */
30
+ export declare function getCommentContributionHtml(html: any, handleUrl: any): any;
24
31
  /**
25
32
  * Get route name for a contribution
26
33
  * @param obj
@@ -56,6 +56,17 @@ export function getContributionHtml(html, handleUrl) {
56
56
  return `<a href='${handleUrl(SCRoutes.USER_PROFILE_ROUTE_NAME, { id, username })}'>@${username}</a>`;
57
57
  });
58
58
  }
59
+ /**
60
+ * Get the contribution text for comment object
61
+ * Hydrate text with mention, etc.
62
+ * @param html Html of the contribution
63
+ * @param handleUrl Func that handle urls
64
+ */
65
+ export function getCommentContributionHtml(html, handleUrl) {
66
+ return html.replace(/<mention.*? id="([0-9]+)"{1}.*?>@([a-z\d_-]+)<\/mention>/gi, (match, id, username) => {
67
+ return `<a href='${handleUrl(SCRoutes.USER_PROFILE_ROUTE_NAME, { id, username })}'>@${username}</a>`;
68
+ });
69
+ }
59
70
  /**
60
71
  * Get route name for a contribution
61
72
  * @param obj