Skip to content

Commit

Permalink
feat(message-parser): add timestamps pattern (#31810)
Browse files Browse the repository at this point in the history
Co-authored-by: Diego Sampaio <[email protected]>
  • Loading branch information
ggazzo and sampaiodiego authored Mar 8, 2024
1 parent b876e4e commit 5ad65ff
Show file tree
Hide file tree
Showing 22 changed files with 424 additions and 41 deletions.
24 changes: 24 additions & 0 deletions .changeset/stupid-trains-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@rocket.chat/message-parser": patch
---

feat(message-parser): add timestamps pattern

### Usage

Pattern: <t:{timestamp}:?{format}>

- {timestamp} is a Unix timestamp
- {format} is an optional parameter that can be used to customize the date and time format.

#### Formats

| Format | Description | Example |
| ------ | ------------------------- | --------------------------------------- |
| `t` | Short time | 12:00 AM |
| `T` | Long time | 12:00:00 AM |
| `d` | Short date | 12/31/2020 |
| `D` | Long date | Thursday, December 31, 2020 |
| `f` | Full date and time | Thursday, December 31, 2020 12:00 AM |
| `F` | Full date and time (long) | Thursday, December 31, 2020 12:00:00 AM |
| `R` | Relative time | 1 year ago |
7 changes: 7 additions & 0 deletions apps/meteor/client/components/GazzodownText.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { IRoom } from '@rocket.chat/core-typings';
import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
import type { ChannelMention, UserMention } from '@rocket.chat/gazzodown';
import { MarkupInteractionContext } from '@rocket.chat/gazzodown';
import { escapeRegExp } from '@rocket.chat/string-helpers';
import { useFeaturePreview } from '@rocket.chat/ui-client';
import { useLayout, useRouter, useSetting, useUserPreference, useUserId } from '@rocket.chat/ui-contexts';
import type { UIEvent } from 'react';
import React, { useCallback, memo, useMemo } from 'react';
Expand All @@ -25,6 +27,9 @@ type GazzodownTextProps = {
};

const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTextProps) => {
const enableTimestamp = useFeaturePreview('enable-timestamp-message-parser');
const [userLanguage] = useLocalStorage('userLanguage', 'en');

const highlights = useMessageListHighlights();
const { triggerProps, openUserCard } = useUserCard();

Expand Down Expand Up @@ -125,6 +130,8 @@ const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTe
ownUserId,
showMentionSymbol,
triggerProps,
enableTimestamp,
language: userLanguage,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const styles = StyleSheet.create({
});

type MessageBlock =
| MessageParser.Timestamp
| MessageParser.Emoji
| MessageParser.ChannelMention
| MessageParser.UserMention
Expand Down
8 changes: 8 additions & 0 deletions packages/gazzodown/.storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ module.exports = {
typescript: {
reactDocgen: 'react-docgen-typescript-plugin',
},
async webpackFinal(config) {
config.module.rules.push({
test: /(date-fns).*\.(ts|js|mjs)x?$/,
include: /node_modules/,
loader: 'babel-loader',
});
return config;
},
};
1 change: 1 addition & 0 deletions packages/gazzodown/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"react": "*"
},
"dependencies": {
"date-fns": "^3.3.1",
"highlight.js": "^11.5.1",
"react-error-boundary": "^3.1.4"
},
Expand Down
102 changes: 67 additions & 35 deletions packages/gazzodown/src/Markup.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,53 +7,56 @@ import outdent from 'outdent';
import { ReactElement, Suspense } from 'react';

import Markup from './Markup';
import { MarkupInteractionContext } from './MarkupInteractionContext';

export default {
title: 'Markup',
component: Markup,
decorators: [
(Story): ReactElement => (
<Suspense fallback={null}>
<MessageContainer>
<MessageBody>
<Box
className={css`
> blockquote {
padding-inline: 8px;
border-radius: 2px;
border-width: 2px;
border-style: solid;
background-color: var(--rcx-color-neutral-100, ${colors.n100});
border-color: var(--rcx-color-neutral-200, ${colors.n200});
border-inline-start-color: var(--rcx-color-neutral-600, ${colors.n600});
&:hover,
&:focus {
background-color: var(--rcx-color-neutral-200, ${colors.n200});
border-color: var(--rcx-color-neutral-300, ${colors.n300});
<MarkupInteractionContext.Provider value={{ enableTimestamp: true }}>
<MessageContainer>
<MessageBody>
<Box
className={css`
> blockquote {
padding-inline: 8px;
border-radius: 2px;
border-width: 2px;
border-style: solid;
background-color: var(--rcx-color-neutral-100, ${colors.n100});
border-color: var(--rcx-color-neutral-200, ${colors.n200});
border-inline-start-color: var(--rcx-color-neutral-600, ${colors.n600});
}
}
> ul.task-list {
> li::before {
display: none;
&:hover,
&:focus {
background-color: var(--rcx-color-neutral-200, ${colors.n200});
border-color: var(--rcx-color-neutral-300, ${colors.n300});
border-inline-start-color: var(--rcx-color-neutral-600, ${colors.n600});
}
}
> li > .rcx-check-box > .rcx-check-box__input:focus + .rcx-check-box__fake {
z-index: 1;
}
> ul.task-list {
> li::before {
display: none;
}
> li > .rcx-check-box > .rcx-check-box__input:focus + .rcx-check-box__fake {
z-index: 1;
}
list-style: none;
margin-inline-start: 0;
padding-inline-start: 0;
}
`}
>
<Story />
</Box>
</MessageBody>
</MessageContainer>
list-style: none;
margin-inline-start: 0;
padding-inline-start: 0;
}
`}
>
<Story />
</Box>
</MessageBody>
</MessageContainer>
</MarkupInteractionContext.Provider>
{/* workaround? */}
<Box />
</Suspense>
Expand All @@ -75,6 +78,35 @@ Empty.args = {
tokens: [],
};

export const Timestamp = Template.bind({});

Timestamp.args = {
tokens: parse(`Short time: <t:1708551317:t>
Long time: <t:1708551317:T>
Short date: <t:1708551317:d>
Long date: <t:1708551317:D>
Full date: <t:1708551317:f>
Full date (long): <t:1708551317:F>
Relative time from past: <t:${((): number => {
const date = new Date();
date.setHours(date.getHours() - 1);
return date.getTime();
})()}:R>
Relative to Future: <t:${((): number => {
const date = new Date();
date.setHours(date.getHours() + 1);
return date.getTime();
})()}:R>
Relative Seconds: <t:${((): number => {
const date = new Date();
date.setSeconds(date.getSeconds() - 1);
return date.getTime();
})()}:R>
`),
};

export const BigEmoji = Template.bind({});
BigEmoji.args = {
tokens: [
Expand Down
2 changes: 2 additions & 0 deletions packages/gazzodown/src/MarkupInteractionContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type MarkupInteractionContextValue = {
ownUserId?: string | null;
showMentionSymbol?: boolean;
triggerProps?: AriaButtonProps<'button'>;
enableTimestamp?: boolean;
language?: string;
};

export const MarkupInteractionContext = createContext<MarkupInteractionContextValue>({});
1 change: 1 addition & 0 deletions packages/gazzodown/src/elements/ImageElement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ReactElement, useMemo } from 'react';

const flattenMarkup = (
markup:
| MessageParser.Timestamp
| MessageParser.Markup
| MessageParser.InlineCode
| MessageParser.Link
Expand Down
13 changes: 11 additions & 2 deletions packages/gazzodown/src/elements/InlineElements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import ItalicSpan from './ItalicSpan';
import LinkSpan from './LinkSpan';
import PlainSpan from './PlainSpan';
import StrikeSpan from './StrikeSpan';
import Timestamp from './Timestamp';

const CodeElement = lazy(() => import('../code/CodeElement'));
const KatexElement = lazy(() => import('../katex/KatexElement'));

type InlineElementsProps = {
children: MessageParser.Inlines[];
children: (MessageParser.Inlines | { fallback: MessageParser.Plain; type: undefined })[];
};

const InlineElements = ({ children }: InlineElementsProps): ReactElement => (
Expand Down Expand Up @@ -70,8 +71,16 @@ const InlineElements = ({ children }: InlineElementsProps): ReactElement => (
</KatexErrorBoundary>
);

default:
case 'TIMESTAMP': {
return <Timestamp key={index} children={child} />;
}

default: {
if ('fallback' in child) {
return <InlineElements key={index} children={[child.fallback]} />;
}
return null;
}
}
})}
</>
Expand Down
1 change: 1 addition & 0 deletions packages/gazzodown/src/elements/StrikeSpan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import PlainSpan from './PlainSpan';
const CodeElement = lazy(() => import('../code/CodeElement'));

type MessageBlock =
| MessageParser.Timestamp
| MessageParser.Emoji
| MessageParser.ChannelMention
| MessageParser.UserMention
Expand Down
21 changes: 21 additions & 0 deletions packages/gazzodown/src/elements/Timestamp/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Component } from 'react';

export class ErrorBoundary extends Component<{ fallback: React.ReactNode }, { hasError: boolean }> {
constructor(props: { fallback: React.ReactNode }) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError() {

Check warning on line 9 in packages/gazzodown/src/elements/Timestamp/ErrorBoundary.tsx

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Missing return type on function
return { hasError: true };
}

render() {

Check warning on line 13 in packages/gazzodown/src/elements/Timestamp/ErrorBoundary.tsx

View workflow job for this annotation

GitHub Actions / 🔎 Code Check / Code Lint

Missing return type on function
if (this.state.hasError) {
// You can render any custom fallback UI
return this.props.fallback;
}

return this.props.children;
}
}
Loading

0 comments on commit 5ad65ff

Please sign in to comment.