feat: render timestamp markdown#6266
Conversation
Co-authored-by: Diego Mello <[email protected]>
WalkthroughThis PR introduces a new Timestamp component for rendering timestamps in markdown with support for seven format options (t, T, d, D, f, F, R). The component is integrated into Inline and Paragraph markdown containers with appropriate styling adjustments. It uses moment.js for date formatting and emits events on user interactions. Changes
Sequence DiagramsequenceDiagram
participant Markdown as Markdown Parser
participant Inline as Inline.tsx
participant Paragraph as Paragraph.tsx
participant Timestamp as Timestamp Component
participant Theme as Theme/Styling
participant EventEmitter as EventEmitter
Markdown->>Inline: Render TIMESTAMP block
Inline->>Timestamp: value={block.value}
activate Timestamp
Timestamp->>Timestamp: Compute formatted date (memoized)
Timestamp->>Theme: Get theme colors
Theme-->>Timestamp: bgColor, fontColor
Timestamp->>Timestamp: Render Text with formatting
deactivate Timestamp
Timestamp-->>Inline: Rendered timestamp UI
Note over Timestamp: On user press
Timestamp->>EventEmitter: emit(LISTENER, {message})
EventEmitter->>EventEmitter: Trigger feedback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
app/containers/markdown/components/Inline.tsx (1)
25-79: Add key props to all components returned from the map.The map iteration starting at line 25 returns components from the switch statement without key props, causing React to be unable to efficiently track changes. Biome correctly flagged this for the new TIMESTAMP case (line 75), but the issue affects all switch cases.
Each component returned from the switch should include a
keyprop. Apply this pattern to all cases:<Text style={styles.inline}> {value.map((block, index) => { // ... forceTrim logic ... switch (block.type) { case 'IMAGE': - return <Image value={block.value} />; + return <Image key={index} value={block.value} />; case 'PLAIN_TEXT': - return <Plain value={block.value} />; + return <Plain key={index} value={block.value} />; case 'BOLD': - return <Bold value={block.value} />; + return <Bold key={index} value={block.value} />; case 'STRIKE': - return <Strike value={block.value} />; + return <Strike key={index} value={block.value} />; case 'ITALIC': - return <Italic value={block.value} />; + return <Italic key={index} value={block.value} />; case 'LINK': - return <Link value={block.value} />; + return <Link key={index} value={block.value} />; case 'MENTION_USER': return ( - <AtMention + <AtMention key={index} mention={block.value.value} useRealName={useRealName} username={username} navToRoomInfo={navToRoomInfo} mentions={mentions} /> ); case 'EMOJI': - return <Emoji block={block} />; + return <Emoji key={index} block={block} />; case 'MENTION_CHANNEL': - return <Hashtag hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />; + return <Hashtag key={index} hashtag={block.value.value} navToRoomInfo={navToRoomInfo} channels={channels} />; case 'INLINE_CODE': - return <InlineCode value={block.value} />; + return <InlineCode key={index} value={block.value} />; case 'INLINE_KATEX': - return <Text>{block.value}</Text>; + return <Text key={index}>{block.value}</Text>; case 'TIMESTAMP': - return <Timestamp value={block.value} />; + return <Timestamp key={index} value={block.value} />; default: return null; } })} </Text>Note: While using
indexas a key is acceptable here since the order of inline markdown elements is stable and won't be reordered, if these elements could be reordered or filtered, consider using a more stable identifier.
♻️ Duplicate comments (4)
app/containers/markdown/components/Paragraph.tsx (2)
36-42: Clarify the use case and add stories for this implementation.This early return handles timestamps at the paragraph start, but the use case and test coverage remain unclear.
As noted in the previous review: What's the specific use case that requires this early return? Please add Storybook stories to demonstrate the TIMESTAMP rendering behavior.
45-45: Optimize the type check and clarify the margin logic.The inline map operation is inefficient and the marginTop logic needs clarification:
value?.map(x=> x.type).includes('TIMESTAMP')creates an unnecessary intermediate array. Usesomeinstead:value?.some(x => x.type === 'TIMESTAMP')- Missing space in arrow function:
x=>should bex =>- The logic is confusing: lines 36-42 early return when
value[0]is TIMESTAMP, so this marginTop only applies when TIMESTAMP appears at a non-zero index. Please clarify why mixed content with TIMESTAMP requires different spacing.Apply this diff to optimize the type check:
- <Text style={[styles.text, { color: themes[theme].fontDefault, marginTop: value?.map(x=> x.type).includes('TIMESTAMP') ? 2 : 0 }]}> + <Text style={[styles.text, { color: themes[theme].fontDefault, marginTop: value?.some(x => x.type === 'TIMESTAMP') ? 2 : 0 }]}>Note: The previous reviewer asked "Why do you have to add this margin top?" - please provide justification for this spacing adjustment.
app/containers/markdown/components/Timestamp.tsx (2)
56-60: Add font styling to match the web version.The component is missing font styling (family, weight) that should align with the web implementation for consistency across platforms.
As noted in the previous review: Please verify the web version's font styling and apply the same here. Consider extracting these styles to a StyleSheet for better performance instead of using inline styles.
#!/bin/bash # Description: Find timestamp styling in web codebase to ensure mobile matches # Search for timestamp-related styles in the web codebase rg -n -C3 "timestamp.*style|Timestamp.*css" --type-add 'web:*.{tsx,ts,css,scss}' -t web
51-54: Extract the duplicate timestamp calculation.The timestamp calculation
parseInt(value.timestamp) * 1000is duplicated on lines 18 and 52, violating DRY principles.As noted in the previous review, extract this to a memoized constant:
const formatDate = React.useMemo(()=>{ const timestamp = parseInt(value.timestamp) * 1000; // ... formatting logic }, [value]); + + const timestampMs = React.useMemo(() => parseInt(value.timestamp) * 1000, [value.timestamp]); const handlePress = () => { - const message = moment(parseInt(value.timestamp) * 1000).format('dddd, MMM DD, YYYY hh:mm A'); + const message = moment(timestampMs).format('LLL'); EventEmitter.emit(LISTENER, { message }); }Note: Also updated the format string to
'LLL'to use locale-aware formatting (see previous comment).
🧹 Nitpick comments (1)
app/containers/markdown/components/Timestamp.tsx (1)
20-46: Consider using a switch statement for better readability.The sequence of seven if statements would be more readable and maintainable as a switch statement.
Apply this diff:
const formatDate = React.useMemo(()=>{ const timestamp = parseInt(value.timestamp) * 1000; - if(value.format === "t"){ - return moment(timestamp).format('LT'); - } - - if(value.format === "T"){ - return moment(timestamp).format('LTS'); - } - - if(value.format === "d"){ - return moment(timestamp).format('L'); - } - - if(value.format === "D"){ - return moment(timestamp).format('LL'); - } - - if(value.format === "f"){ - return moment(timestamp).format('LLL'); - } - - if(value.format === "F"){ - return moment(timestamp).format('LLLL'); - } - - if(value.format === "R"){ - return moment(timestamp).fromNow(); - } - - return "Invalid Date"; + switch(value.format) { + case "t": + return moment(timestamp).format('LT'); + case "T": + return moment(timestamp).format('LTS'); + case "d": + return moment(timestamp).format('L'); + case "D": + return moment(timestamp).format('LL'); + case "f": + return moment(timestamp).format('LLL'); + case "F": + return moment(timestamp).format('LLLL'); + case "R": + return moment(timestamp).fromNow(); + default: + return "Invalid Date"; + } }, [value]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (3)
app/containers/markdown/components/Inline.tsx(2 hunks)app/containers/markdown/components/Paragraph.tsx(2 hunks)app/containers/markdown/components/Timestamp.tsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/containers/markdown/components/Timestamp.tsx (2)
app/theme.tsx (1)
useTheme(29-29)app/containers/Toast.tsx (1)
LISTENER(21-21)
🪛 Biome (2.1.2)
app/containers/markdown/components/Inline.tsx
[error] 75-75: Missing key property for this element in iterable.
The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.
(lint/correctness/useJsxKeyInIterable)
🪛 ESLint
app/containers/markdown/components/Timestamp.tsx
[error] 5-5: 'styles' is defined but never used.
(@typescript-eslint/no-unused-vars)
[error] 7-7: Useless path segments for "../../../containers/Toast", should be "../../Toast"
(import/no-useless-path-segments)
🔇 Additional comments (1)
app/containers/markdown/components/Inline.tsx (1)
13-13: LGTM!The Timestamp import is correctly placed and follows the existing import pattern.
|
Requesting a review :) |
Proposed changes
This PR adds a function to render the timestamp message in the mobile app, aligning it with the web version. Previously, the timestamp was missing on mobile causing it to render blank message. This update ensures that users see the timestamp message correctly across both web and mobile.
Issue(s)
https://rocketchat.atlassian.net/browse/CORE-1637
How to test or reproduce
Screenshots
Types of changes
Checklist
Further Comment
I am using the date and time format given in https://github.com/RocketChat/google-summer-of-code/blob/main/google-summer-of-code-2025.md#-message-timestamp-date-picker-components

Summary by CodeRabbit