Layouting
This commit is contained in:
parent
9d2cee8dbb
commit
5c0dcd460e
4 changed files with 149 additions and 12 deletions
13
src/components/Author.tsx
Normal file
13
src/components/Author.tsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { FunctionComponent } from "react";
|
||||||
|
|
||||||
|
type AuthorComponentProps = {
|
||||||
|
authorId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const AuthorComponent: FunctionComponent<AuthorComponentProps> = ({
|
||||||
|
authorId,
|
||||||
|
}) => {
|
||||||
|
return <span className="message-author">{authorId}</span>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AuthorComponent;
|
|
@ -1,16 +1,114 @@
|
||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent, useEffect, useState } from "react";
|
||||||
import { TextMessage } from "src/lib/ServerMessage";
|
import { TextMessage } from "src/lib/ServerMessage";
|
||||||
|
|
||||||
type Props = {
|
import AuthorComponent from "./Author";
|
||||||
|
|
||||||
|
type MessageComponentProps = {
|
||||||
message: TextMessage;
|
message: TextMessage;
|
||||||
};
|
};
|
||||||
|
enum MONTH_NAMES {
|
||||||
|
"January",
|
||||||
|
"February",
|
||||||
|
"March",
|
||||||
|
"April",
|
||||||
|
"May",
|
||||||
|
"June",
|
||||||
|
"July",
|
||||||
|
"August",
|
||||||
|
"September",
|
||||||
|
"October",
|
||||||
|
"November",
|
||||||
|
"December",
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormattedDate(
|
||||||
|
date: Date,
|
||||||
|
prefomattedDate?: string,
|
||||||
|
hideYear = false
|
||||||
|
) {
|
||||||
|
const day = date.getDate();
|
||||||
|
const month = MONTH_NAMES[date.getMonth()];
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const hours = date.getHours();
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, "0");
|
||||||
|
|
||||||
|
if (prefomattedDate) {
|
||||||
|
// Today at 10:20
|
||||||
|
// Yesterday at 10:20
|
||||||
|
return `${prefomattedDate} at ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideYear) {
|
||||||
|
// 10. January at 10:20
|
||||||
|
return `${day}. ${month} at ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. January 2017. at 10:20
|
||||||
|
return `${day}. ${month} ${year}. at ${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main function
|
||||||
|
function timeAgo(dateParam: Date | number) {
|
||||||
|
const date = typeof dateParam === "object" ? dateParam : new Date(dateParam);
|
||||||
|
const DAY_IN_MS = 86400000; // 24 * 60 * 60 * 1000
|
||||||
|
const today = new Date();
|
||||||
|
const yesterday = new Date(today.getTime() - DAY_IN_MS);
|
||||||
|
const seconds = Math.round((today.getTime() - date.getTime()) / 1000);
|
||||||
|
const minutes = Math.round(seconds / 60);
|
||||||
|
const isToday = today.toDateString() === date.toDateString();
|
||||||
|
const isYesterday = yesterday.toDateString() === date.toDateString();
|
||||||
|
const isThisYear = today.getFullYear() === date.getFullYear();
|
||||||
|
|
||||||
|
if (seconds < 5) {
|
||||||
|
return "now";
|
||||||
|
} else if (seconds < 60) {
|
||||||
|
return `${seconds} seconds ago`;
|
||||||
|
} else if (seconds < 90) {
|
||||||
|
return "about a minute ago";
|
||||||
|
} else if (minutes < 60) {
|
||||||
|
return `${minutes} minutes ago`;
|
||||||
|
} else if (isToday) {
|
||||||
|
return getFormattedDate(date, "Today"); // Today at 10:20
|
||||||
|
} else if (isYesterday) {
|
||||||
|
return getFormattedDate(date, "Yesterday"); // Yesterday at 10:20
|
||||||
|
} else if (isThisYear) {
|
||||||
|
return getFormattedDate(date, undefined, true); // 10. January at 10:20
|
||||||
|
} else {
|
||||||
|
return getFormattedDate(date); // 10. January 2017. at 10:20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const MessageComponent: FunctionComponent<MessageComponentProps> = ({
|
||||||
|
message,
|
||||||
|
}) => {
|
||||||
|
const date = new Date(message.date);
|
||||||
|
const [timeAgoString, setTimeAgoString] = useState(timeAgo(message.date));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkTimeAgo = () => {
|
||||||
|
const newTimeAgoString = timeAgo(message.date);
|
||||||
|
if (timeAgoString !== newTimeAgoString) {
|
||||||
|
setTimeAgoString(newTimeAgoString);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const interval = setInterval(checkTimeAgo, 5000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [message.date, timeAgoString]);
|
||||||
|
|
||||||
const MessageComponent: FunctionComponent<Props> = ({ message }) => {
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<div className="message">
|
||||||
{new Date(message.date).toISOString()}
|
<h2>
|
||||||
{message.content}
|
<AuthorComponent authorId={message.author} />
|
||||||
</span>
|
<sub>
|
||||||
|
<time className="message-date" dateTime={date.toISOString()}>
|
||||||
|
{timeAgoString}
|
||||||
|
</time>
|
||||||
|
</sub>
|
||||||
|
</h2>
|
||||||
|
<div className="message-content">{message.content}</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MessageComponent;
|
export default MessageComponent;
|
||||||
|
|
|
@ -80,11 +80,13 @@ export default function Index(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<main>
|
<main>
|
||||||
<div id="messages-container">
|
<ol id="messages-container">
|
||||||
{messageHistory.map((message, idx) => {
|
{messageHistory.map((message, idx) => (
|
||||||
return <MessageComponent message={message} key={idx} />;
|
<li key={idx}>
|
||||||
})}
|
<MessageComponent message={message} />
|
||||||
</div>
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
<div id="message-writing-area">
|
<div id="message-writing-area">
|
||||||
<span>Ready state: </span>
|
<span>Ready state: </span>
|
||||||
<span style={{ fontWeight: "bold" }}>
|
<span style={{ fontWeight: "bold" }}>
|
||||||
|
|
|
@ -48,9 +48,33 @@ main {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
overflow-anchor: none;
|
||||||
|
list-style: none;
|
||||||
|
padding-inline-start: 0;
|
||||||
|
|
||||||
> :nth-child(even) {
|
> :nth-child(even) {
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> li {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
display: block;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
> h2 {
|
||||||
|
width: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 0;
|
||||||
|
font-size: medium;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.message-date {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#message-writing-area {
|
#message-writing-area {
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue