Skip to content

Commit f560d5c

Browse files
WIP: Display starred messages in dedicated component (which will display at the top of Composer articles)
Co-Authored-By: Tom Richards <19289579+twrichards@users.noreply.github.com>
1 parent 28165b1 commit f560d5c

File tree

8 files changed

+101
-29
lines changed

8 files changed

+101
-29
lines changed

bootstrapping-lambda/local/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ <h4>
3838
Remove pinboard pre-selection
3939
</button>
4040

41-
<h5>Starred messages</h5>
41+
<h5>Starred messages (requires pinboard pre-selection)</h5>
4242
<pinboard-starred-messages></pinboard-starred-messages>
4343

4444
<h4>via hidden element (for invalid/untracked composer ID)</h4>

client/src/formattedDateTime.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { formatDateTime } from "./util";
44
export const TickContext = React.createContext<number>(Date.now());
55

66
interface FormattedDateTimeProps {
7-
timestamp: number;
7+
timestamp: number | string;
88
isPartOfSentence?: true;
99
withAgo?: true;
1010
}

client/src/itemDisplay.tsx

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ export const ItemDisplay = ({
7878
[item.id, item.message]
7979
);
8080

81-
const dateInMillisecs = new Date(item.timestamp).valueOf();
82-
8381
const isDifferentUserFromPreviousItem =
8482
maybePreviousItem?.userEmail !== item.userEmail;
8583

@@ -150,15 +148,17 @@ export const ItemDisplay = ({
150148
</React.Fragment>
151149
)}
152150
</div>
153-
<div
154-
css={css`
155-
position: absolute;
156-
margin-top: 5px;
157-
margin-left: 2px;
158-
`}
159-
>
160-
<StarredControl itemId={item.id} isStarred={item.isStarred} />
161-
</div>
151+
{!isDeleted && (
152+
<div
153+
css={css`
154+
position: absolute;
155+
margin-top: 5px;
156+
margin-left: 2px;
157+
`}
158+
>
159+
<StarredControl itemId={item.id} isStarred={item.isStarred} />
160+
</div>
161+
)}
162162
<div
163163
css={css`
164164
margin-left: ${space[9] - 4}px;
@@ -172,7 +172,7 @@ export const ItemDisplay = ({
172172
margin-bottom: 2px;
173173
`}
174174
>
175-
<FormattedDateTime timestamp={dateInMillisecs} />
175+
<FormattedDateTime timestamp={item.timestamp} />
176176
{isEdited && <span>&nbsp;-&nbsp;Edited</span>}
177177
{maybeRelatedItem && item.type !== "claim" && (
178178
<span>&nbsp;-&nbsp;Reply</span>

client/src/nestedItemDisplay.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export const NestedItemDisplay = ({
103103
margin-left: ${payloadAndType ? 0 : 10}px;
104104
`}
105105
>
106-
<FormattedDateTime timestamp={new Date(item.timestamp).valueOf()} />
106+
<FormattedDateTime timestamp={item.timestamp} />
107107
{item.editHistory && item.editHistory.length > 0 && (
108108
<span> - Edited</span>
109109
)}

client/src/pinboard.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,11 @@ export const Pinboard: React.FC<PinboardProps> = ({
400400
{isPinboardData(preselectedPinboard) &&
401401
preselectedPinboard.id === pinboardId &&
402402
maybeStarredMessagesArea && (
403-
<StarredMessagesPortal node={maybeStarredMessagesArea} />
403+
<StarredMessagesPortal
404+
node={maybeStarredMessagesArea}
405+
items={items}
406+
userLookup={userLookup}
407+
/>
404408
)}
405409
</React.Fragment>
406410
);
Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,76 @@
11
import React from "react";
22
import ReactDOM from "react-dom";
3+
import { Item } from "shared/graphql/graphql";
4+
import root from "react-shadow/emotion";
5+
import { UserLookup } from "../types/UserLookup";
6+
import { FormattedDateTime } from "../formattedDateTime";
7+
import { css } from "@emotion/react";
8+
import { agateSans } from "../../fontNormaliser";
9+
import { neutral, space } from "@guardian/source-foundations";
310

411
export const STARRED_MESSAGES_HTML_TAG = "pinboard-starred-messages";
512

6-
// interface StarredMessagesProps {}
13+
const StarredItemDisplay = ({
14+
item,
15+
userLookup,
16+
}: {
17+
item: Item;
18+
userLookup: UserLookup;
19+
}) => {
20+
const user = userLookup?.[item.userEmail];
21+
const userDisplayName = user
22+
? `${user.firstName} ${user.lastName}`
23+
: item.userEmail;
24+
return (
25+
<div
26+
css={css`
27+
${agateSans.xsmall()};
28+
display: flex;
29+
align-items: flex-end;
30+
gap: ${space[1]}px;
31+
color: ${neutral[20]};
32+
`}
33+
>
34+
<span
35+
css={css`
36+
color: ${neutral[0]};
37+
${agateSans.medium()};
38+
`}
39+
>
40+
{item.message}
41+
</span>
42+
<span title={item.userEmail}>{userDisplayName}</span>
43+
<span>
44+
<FormattedDateTime timestamp={item.timestamp} withAgo />
45+
</span>
46+
</div>
47+
);
48+
};
749

8-
const StarredMessages = (/* props: StarredMessagesProps */) => (
9-
<div>
10-
<p>Starred Messages</p>
11-
</div>
12-
);
50+
interface StarredMessagesProps {
51+
items: Item[];
52+
userLookup: UserLookup;
53+
}
54+
55+
const StarredMessages = ({ items, userLookup }: StarredMessagesProps) => {
56+
const starredMessages = items.filter(
57+
(item) => item.isStarred && !item.deletedAt
58+
);
59+
return (
60+
<root.div>
61+
{starredMessages.map((item) => (
62+
<StarredItemDisplay key={item.id} item={item} userLookup={userLookup} />
63+
))}
64+
</root.div>
65+
);
66+
};
1367

14-
interface StarredMessagesPortalProps {
68+
interface StarredMessagesPortalProps extends StarredMessagesProps {
1569
node: Element;
1670
}
1771

18-
export const StarredMessagesPortal = ({ node }: StarredMessagesPortalProps) =>
19-
ReactDOM.createPortal(<StarredMessages />, node);
72+
export const StarredMessagesPortal = ({
73+
node,
74+
...props
75+
}: StarredMessagesPortalProps) =>
76+
ReactDOM.createPortal(<StarredMessages {...props} />, node);

client/src/util.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ export const getTooltipText = (
1313
) => `WT: ${workingTitle}` + (headline ? `\nHL: ${headline}` : "");
1414

1515
export const formatDateTime = (
16-
timestamp: number,
17-
isPartOfSentence?: true,
18-
withAgo?: true
16+
timestampStringOrEpochMillis: number | string,
17+
isPartOfSentence?: boolean,
18+
withAgo?: boolean
1919
): string => {
2020
const now = Date.now();
21+
const timestamp =
22+
typeof timestampStringOrEpochMillis === "string"
23+
? new Date(timestampStringOrEpochMillis).valueOf()
24+
: timestampStringOrEpochMillis;
2125
if (isThisYear(timestamp)) {
2226
if (isToday(timestamp)) {
2327
if (differenceInMinutes(now, timestamp) < 1) {
@@ -32,7 +36,7 @@ export const formatDateTime = (
3236
return (
3337
formatDistanceStrict(timestamp, now, {
3438
roundingMethod: "floor",
35-
}).slice(0, -4) + (withAgo ? " ago" : "")
39+
}).slice(0, -4) + (withAgo ? "s ago" : "")
3640
);
3741
}
3842
return format(timestamp, "HH:mm");

client/test/formattedDateTime.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ test("display is correct if timestamp is between 2 min and 1 hr ago", () => {
2828
expect(formatDateTime(twoMinsAgo)).toBe("2 min");
2929
const lessThanHr = subMinutes(Date.now(), 59).valueOf();
3030
expect(formatDateTime(lessThanHr)).toBe("59 min");
31+
const pluralMinsWithAgo = subMinutes(Date.now(), 3).valueOf();
32+
expect(formatDateTime(pluralMinsWithAgo, false, true)).toBe("3 mins ago");
33+
});
34+
35+
test("display is correct if passed a string", () => {
36+
const twoMinsAgoString = subMinutes(Date.now(), 2).toISOString();
37+
expect(formatDateTime(twoMinsAgoString)).toBe("2 min");
3138
});
3239

3340
test("display is correct if timestamp is 1 hr ago exactly", () => {

0 commit comments

Comments
 (0)