aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-08-01 00:29:35 +0800
committercrupest <crupest@outlook.com>2023-08-01 00:29:35 +0800
commite6ccc0174a86a0ade240e6551228598cd81f984b (patch)
treed495dff9658c23de22191c4532e14016a6fb96a8 /FrontEnd
parent710ff9d3d2e55113798c39b0595f8f71b12091ef (diff)
downloadtimeline-e6ccc0174a86a0ade240e6551228598cd81f984b.tar.gz
timeline-e6ccc0174a86a0ade240e6551228598cd81f984b.tar.bz2
timeline-e6ccc0174a86a0ade240e6551228598cd81f984b.zip
...
Diffstat (limited to 'FrontEnd')
-rw-r--r--FrontEnd/package.json2
-rw-r--r--FrontEnd/pnpm-lock.yaml35
-rw-r--r--FrontEnd/src/pages/timeline/Timeline.css95
-rw-r--r--FrontEnd/src/pages/timeline/Timeline.tsx77
-rw-r--r--FrontEnd/src/pages/timeline/TimelineCard.tsx2
-rw-r--r--FrontEnd/src/pages/timeline/TimelineDateLabel.css9
-rw-r--r--FrontEnd/src/pages/timeline/TimelineDateLabel.tsx20
-rw-r--r--FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx25
-rw-r--r--FrontEnd/src/pages/timeline/TimelineLine.tsx51
-rw-r--r--FrontEnd/src/pages/timeline/TimelineLoading.tsx16
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostCard.css9
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostCard.tsx22
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostContainer.css3
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostContainer.tsx20
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostEdit.tsx6
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx31
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx18
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostList.css10
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostList.tsx (renamed from FrontEnd/src/pages/timeline/TimelinePostListView.tsx)20
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostView.css23
-rw-r--r--FrontEnd/src/pages/timeline/TimelinePostView.tsx20
21 files changed, 199 insertions, 315 deletions
diff --git a/FrontEnd/package.json b/FrontEnd/package.json
index 56af818c..950670f4 100644
--- a/FrontEnd/package.json
+++ b/FrontEnd/package.json
@@ -12,8 +12,8 @@
"check:fix": "pnpm run type-check && pnpm run lint:fix"
},
"dependencies": {
+ "@floating-ui/react-dom": "^2.0.1",
"@microsoft/signalr": "^7.0.7",
- "@popperjs/core": "^2.11.8",
"axios": "^1.4.0",
"bootstrap": "^5.3.0",
"bootstrap-icons": "^1.10.5",
diff --git a/FrontEnd/pnpm-lock.yaml b/FrontEnd/pnpm-lock.yaml
index cad9a287..24e81c7c 100644
--- a/FrontEnd/pnpm-lock.yaml
+++ b/FrontEnd/pnpm-lock.yaml
@@ -5,12 +5,12 @@ settings:
excludeLinksFromLockfile: false
dependencies:
+ '@floating-ui/react-dom':
+ specifier: ^2.0.1
+ version: 2.0.1(react-dom@18.2.0)(react@18.2.0)
'@microsoft/signalr':
specifier: ^7.0.7
version: 7.0.7
- '@popperjs/core':
- specifier: ^2.11.8
- version: 2.11.8
axios:
specifier: ^1.4.0
version: 1.4.0
@@ -239,6 +239,34 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
+ /@floating-ui/core@1.4.1:
+ resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==}
+ dependencies:
+ '@floating-ui/utils': 0.1.1
+ dev: false
+
+ /@floating-ui/dom@1.5.1:
+ resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==}
+ dependencies:
+ '@floating-ui/core': 1.4.1
+ '@floating-ui/utils': 0.1.1
+ dev: false
+
+ /@floating-ui/react-dom@2.0.1(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+ dependencies:
+ '@floating-ui/dom': 1.5.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /@floating-ui/utils@0.1.1:
+ resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==}
+ dev: false
+
/@humanwhocodes/config-array@0.11.10:
resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==}
engines: {node: '>=10.10.0'}
@@ -3147,6 +3175,7 @@ packages:
/node-gyp-build-optional-packages@5.0.7:
resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
hasBin: true
+ requiresBuild: true
dev: true
optional: true
diff --git a/FrontEnd/src/pages/timeline/Timeline.css b/FrontEnd/src/pages/timeline/Timeline.css
index f071f163..30ad75c9 100644
--- a/FrontEnd/src/pages/timeline/Timeline.css
+++ b/FrontEnd/src/pages/timeline/Timeline.css
@@ -1,7 +1,19 @@
.timeline {
+ --timeline-background-color: #f3f3f3;
+ --timeline-shadow-color: #00000080;
+ --timeline-post-line-color: #eadd2c;
+ --timeline-post-line-shadow: 2px 1px 10px -1px var(--timeline-shadow-color);
+ --timeline-post-card-background-color: rgb(255, 255, 255);
+ --timeline-post-card-shadow: 4px 2px 10px -2px var(--timeline-shadow-color);
+ --timeline-post-card-border-radius: 10px;
+ --timeline-post-text-color: #000000;
+}
+
+.timeline {
z-index: 0;
position: relative;
width: 100%;
+ background-color: var(--timeline-background-color);
}
@keyframes timeline-line-node {
@@ -26,6 +38,7 @@
from {
transform: rotate(0turn);
}
+
to {
transform: rotate(1turn);
}
@@ -42,6 +55,7 @@
transform: translate(0, 100%);
opacity: 0;
}
+
to {
opacity: 1;
}
@@ -71,40 +85,46 @@
}
.timeline-line .segment {
- width: 7px;
+ width: 12px;
background: var(--cru-primary-color);
}
+
.timeline-line .segment.start {
height: 1.8em;
flex: 0 0 auto;
}
+
.timeline-line .segment.end {
flex: 1 1 auto;
}
+
.timeline-line .segment.current-end {
height: 2em;
flex: 0 0 auto;
background: linear-gradient(var(--cru-primary-enhance-color), white);
}
+
.timeline-line .node-container {
flex: 0 0 auto;
position: relative;
width: 18px;
height: 18px;
}
+
.timeline-line .node {
- width: 20px;
- height: 20px;
+ width: 24px;
+ height: 24px;
position: absolute;
background: var(--cru-primary-color);
- left: -1px;
- top: -1px;
+ left: -3px;
+ top: -3px;
border-radius: 50%;
box-sizing: border-box;
z-index: 1;
animation: 1s infinite alternate;
animation-name: timeline-line-node;
}
+
.timeline-line .node-loading-edge {
color: var(--cru-primary-color);
width: 38px;
@@ -116,11 +136,10 @@
z-index: 2;
animation: 1.5s linear infinite timeline-line-node-loading-edge;
}
+
.timeline-line.current .segment.start {
- background: linear-gradient(
- var(--cru-primary-color),
- var(--cru-primary-enhance-color)
- );
+ background: linear-gradient(var(--cru-primary-color),
+ var(--cru-primary-enhance-color));
}
.timeline-line.current .segment.end {
@@ -137,67 +156,11 @@
animation-name: timeline-line-node-loading;
}
-.timeline-item {
- position: relative;
- padding: 0.5em;
-}
-
-.timeline-item-card {
- position: relative;
- padding: 0.5em 0.5em 0.5em 4em;
-}
-
-.timeline-item-card.enter-animation {
- animation: 0.6s forwards;
- opacity: 0;
-}
-
-@media (max-width: 575.98px) {
- .timeline-item-card {
- padding-left: 3em;
- }
-}
-
-.timeline-item-header {
- display: flex;
- align-items: center;
-}
-
-.timeline-avatar {
- border-radius: 50%;
- width: 2em;
- height: 2em;
-}
-
-.timeline-item-delete-button {
- position: absolute;
- right: 0;
- bottom: 0;
-}
-
-.timeline-content {
- white-space: pre-line;
-}
-
.timeline-content-image {
max-width: 80%;
max-height: 200px;
}
-.timeline-date-item {
- position: relative;
- padding: 0.3em 0 0.3em 4em;
-}
-
-.timeline-date-item-badge {
- display: inline-block;
- padding: 0.1em 0.4em;
- border-radius: 0.4em;
- background: #7c7c7c;
- color: white;
- font-size: 0.8em;
-}
-
.timeline-post-item-options-mask {
background: rgba(255, 255, 255, 0.85);
z-index: 100;
@@ -234,4 +197,4 @@
.timeline-top {
position: sticky;
top: 56px;
-}
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/timeline/Timeline.tsx b/FrontEnd/src/pages/timeline/Timeline.tsx
index f93e1623..317d602e 100644
--- a/FrontEnd/src/pages/timeline/Timeline.tsx
+++ b/FrontEnd/src/pages/timeline/Timeline.tsx
@@ -1,4 +1,4 @@
-import * as React from "react";
+import { useState, useEffect } from "react";
import classnames from "classnames";
import { useScrollToBottom } from "@/utilities/hooks";
import { HubConnectionState } from "@microsoft/signalr";
@@ -14,56 +14,49 @@ import {
HttpTimelinePostInfo,
} from "@/http/timeline";
-import { useUser } from "@/services/user";
import { getTimelinePostUpdate$ } from "@/services/timeline";
-import TimelinePostListView from "./TimelinePostListView";
-import TimelineEmptyItem from "./TimelineEmptyItem";
-import TimelineLoading from "./TimelineLoading";
+import TimelinePostList from "./TimelinePostList";
import TimelinePostEdit from "./TimelinePostEdit";
-import TimelinePostEditNoLogin from "./TimelinePostEditNoLogin";
import TimelineCard from "./TimelineCard";
import "./Timeline.css";
export interface TimelineProps {
className?: string;
- style?: React.CSSProperties;
timelineOwner: string;
timelineName: string;
}
-const Timeline: React.FC<TimelineProps> = (props) => {
- const { timelineOwner, timelineName, className, style } = props;
+export function Timeline(props: TimelineProps) {
+ const { timelineOwner, timelineName, className } = props;
- const user = useUser();
-
- const [timeline, setTimeline] = React.useState<HttpTimelineInfo | null>(null);
- const [posts, setPosts] = React.useState<HttpTimelinePostInfo[] | null>(null);
- const [signalrState, setSignalrState] = React.useState<HubConnectionState>(
+ const [timeline, setTimeline] = useState<HttpTimelineInfo | null>(null);
+ const [posts, setPosts] = useState<HttpTimelinePostInfo[] | null>(null);
+ const [signalrState, setSignalrState] = useState<HubConnectionState>(
HubConnectionState.Connecting,
);
- const [error, setError] = React.useState<
+ const [error, setError] = useState<
"offline" | "forbid" | "notfound" | "error" | null
>(null);
- const [currentPage, setCurrentPage] = React.useState(1);
- const [totalPage, setTotalPage] = React.useState(0);
+ const [currentPage, setCurrentPage] = useState(1);
+ const [totalPage, setTotalPage] = useState(0);
- const [timelineReloadKey, setTimelineReloadKey] = React.useState(0);
- const [postsReloadKey, setPostsReloadKey] = React.useState(0);
+ const [timelineReloadKey, setTimelineReloadKey] = useState(0);
+ const [postsReloadKey, setPostsReloadKey] = useState(0);
const updateTimeline = (): void => setTimelineReloadKey((o) => o + 1);
const updatePosts = (): void => setPostsReloadKey((o) => o + 1);
- React.useEffect(() => {
+ useEffect(() => {
setTimeline(null);
setPosts(null);
setError(null);
setSignalrState(HubConnectionState.Connecting);
}, [timelineOwner, timelineName]);
- React.useEffect(() => {
+ useEffect(() => {
getHttpTimelineClient()
.getTimeline(timelineOwner, timelineName)
.then(
@@ -85,7 +78,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
);
}, [timelineOwner, timelineName, timelineReloadKey]);
- React.useEffect(() => {
+ useEffect(() => {
getHttpTimelineClient()
.listPost(timelineOwner, timelineName, 1)
.then(
@@ -110,7 +103,7 @@ const Timeline: React.FC<TimelineProps> = (props) => {
);
}, [timelineOwner, timelineName, postsReloadKey]);
- React.useEffect(() => {
+ useEffect(() => {
const timelinePostUpdate$ = getTimelinePostUpdate$(
timelineOwner,
timelineName,
@@ -154,33 +147,16 @@ const Timeline: React.FC<TimelineProps> = (props) => {
}, currentPage < totalPage);
if (error === "offline") {
- return (
- <div className={className} style={style}>
- Offline.
- </div>
- );
+ return <div className={className}>Offline.</div>;
} else if (error === "notfound") {
- return (
- <div className={className} style={style}>
- Not exist.
- </div>
- );
+ return <div className={className}>Not exist.</div>;
} else if (error === "forbid") {
- return (
- <div className={className} style={style}>
- Forbid.
- </div>
- );
+ return <div className={className}>Forbid.</div>;
} else if (error === "error") {
- return (
- <div className={className} style={style}>
- Error.
- </div>
- );
+ return <div className={className}>Error.</div>;
}
return (
<>
- {timeline == null && posts == null && <TimelineLoading />}
{timeline && (
<TimelineCard
timeline={timeline}
@@ -189,18 +165,15 @@ const Timeline: React.FC<TimelineProps> = (props) => {
/>
)}
{posts && (
- <div style={style} className={classnames("timeline", className)}>
- <TimelineEmptyItem className="timeline-top" height={50} />
- {timeline?.postable ? (
+ <div className={classnames("timeline", className)}>
+ {timeline?.postable && (
<TimelinePostEdit timeline={timeline} onPosted={updatePosts} />
- ) : user == null ? (
- <TimelinePostEditNoLogin />
- ) : null}
- <TimelinePostListView posts={posts} onReload={updatePosts} />
+ )}
+ <TimelinePostList posts={posts} onReload={updatePosts} />
</div>
)}
</>
);
-};
+}
export default Timeline;
diff --git a/FrontEnd/src/pages/timeline/TimelineCard.tsx b/FrontEnd/src/pages/timeline/TimelineCard.tsx
index b287c620..04b34ec1 100644
--- a/FrontEnd/src/pages/timeline/TimelineCard.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineCard.tsx
@@ -123,7 +123,7 @@ export default function TimelineCard(props: TimelinePageCardProps) {
return (
<Card
- color="secondary"
+ color="tertiary"
className={`timeline-card timeline-card-${
collapse ? "collapse" : "expand"
}`}
diff --git a/FrontEnd/src/pages/timeline/TimelineDateLabel.css b/FrontEnd/src/pages/timeline/TimelineDateLabel.css
new file mode 100644
index 00000000..1c66045b
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelineDateLabel.css
@@ -0,0 +1,9 @@
+.timeline-post-date-badge {
+ display: inline-block;
+ padding: 0.1em 0.4em;
+ border-radius: 0.4em;
+ background: #7c7c7c;
+ color: white;
+ font-size: 0.8em;
+ margin-left: 5em;
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx b/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx
index 5f4ac706..eaadcc1a 100644
--- a/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx
+++ b/FrontEnd/src/pages/timeline/TimelineDateLabel.tsx
@@ -1,19 +1,13 @@
-import * as React from "react";
-import TimelineLine from "./TimelineLine";
+import TimelinePostContainer from "./TimelinePostContainer";
-export interface TimelineDateItemProps {
- date: Date;
-}
+import "./TimelineDateLabel.css";
-const TimelineDateLabel: React.FC<TimelineDateItemProps> = ({ date }) => {
+export default function TimelineDateLabel({ date }: { date: Date }) {
return (
- <div className="timeline-date-item">
- <TimelineLine center="none" />
- <div className="timeline-date-item-badge">
+ <TimelinePostContainer>
+ <div className="timeline-post-date-badge">
{date.toLocaleDateString()}
</div>
- </div>
+ </TimelinePostContainer>
);
-};
-
-export default TimelineDateLabel;
+}
diff --git a/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx b/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx
deleted file mode 100644
index 5e0728d4..00000000
--- a/FrontEnd/src/pages/timeline/TimelineEmptyItem.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import * as React from "react";
-import classnames from "classnames";
-
-import TimelineLine, { TimelineLineProps } from "./TimelineLine";
-
-export interface TimelineEmptyItemProps extends Partial<TimelineLineProps> {
- height?: number | string;
- className?: string;
- style?: React.CSSProperties;
-}
-
-const TimelineEmptyItem: React.FC<TimelineEmptyItemProps> = (props) => {
- const { height, style, className, center, ...lineProps } = props;
-
- return (
- <div
- style={{ ...style, height: height }}
- className={classnames("timeline-item", className)}
- >
- <TimelineLine center={center ?? "none"} {...lineProps} />
- </div>
- );
-};
-
-export default TimelineEmptyItem;
diff --git a/FrontEnd/src/pages/timeline/TimelineLine.tsx b/FrontEnd/src/pages/timeline/TimelineLine.tsx
deleted file mode 100644
index 4a87e6e0..00000000
--- a/FrontEnd/src/pages/timeline/TimelineLine.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import * as React from "react";
-import classnames from "classnames";
-
-export interface TimelineLineProps {
- current?: boolean;
- startSegmentLength?: string | number;
- center: "node" | "loading" | "none";
- className?: string;
- style?: React.CSSProperties;
-}
-
-const TimelineLine: React.FC<TimelineLineProps> = ({
- startSegmentLength,
- center,
- current,
- className,
- style,
-}) => {
- return (
- <div
- className={classnames(
- "timeline-line",
- current && "current",
- center === "loading" && "loading",
- className
- )}
- style={style}
- >
- <div className="segment start" style={{ height: startSegmentLength }} />
- {center !== "none" ? (
- <div className="node-container">
- <div className="node"></div>
- {center === "loading" ? (
- <svg className="node-loading-edge" viewBox="0 0 100 100">
- <path
- d="M 50,10 A 40 40 45 0 1 78.28,21.72"
- stroke="currentcolor"
- strokeLinecap="square"
- strokeWidth="8"
- />
- </svg>
- ) : null}
- </div>
- ) : null}
- {center !== "loading" ? <div className="segment end"></div> : null}
- {current && <div className="segment current-end" />}
- </div>
- );
-};
-
-export default TimelineLine;
diff --git a/FrontEnd/src/pages/timeline/TimelineLoading.tsx b/FrontEnd/src/pages/timeline/TimelineLoading.tsx
deleted file mode 100644
index f876cba9..00000000
--- a/FrontEnd/src/pages/timeline/TimelineLoading.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as React from "react";
-
-import TimelineEmptyItem from "./TimelineEmptyItem";
-
-const TimelineLoading: React.FC = () => {
- return (
- <TimelineEmptyItem
- className="timeline-top-loading-enter"
- height={100}
- center="loading"
- startSegmentLength={56}
- />
- );
-};
-
-export default TimelineLoading;
diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.css b/FrontEnd/src/pages/timeline/TimelinePostCard.css
new file mode 100644
index 00000000..3a446f44
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelinePostCard.css
@@ -0,0 +1,9 @@
+.timeline-post-card {
+ padding: 1em 1em 1em 3em;
+ color: var(--timeline-post-text-color);
+ background-color: var(--timeline-post-card-background-color);
+ box-shadow: var(--timeline-post-card-shadow);
+ border-radius: var(--timeline-post-card-border-radius);
+ position: relative;
+ z-index: 1;
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/timeline/TimelinePostCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx
new file mode 100644
index 00000000..83479349
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelinePostCard.tsx
@@ -0,0 +1,22 @@
+import { ReactNode } from "react";
+import classNames from "classnames";
+
+import Card from "@/views/common/Card";
+
+import "./TimelinePostCard.css";
+
+export interface TimelinePostEditCardProps {
+ className?: string;
+ children?: ReactNode;
+}
+
+export default function TimelinePostCard({
+ className,
+ children,
+}: TimelinePostEditCardProps) {
+ return (
+ <Card className={classNames("timeline-post-card", className)}>
+ {children}
+ </Card>
+ );
+}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostContainer.css b/FrontEnd/src/pages/timeline/TimelinePostContainer.css
new file mode 100644
index 00000000..a12f70b1
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelinePostContainer.css
@@ -0,0 +1,3 @@
+.timeline-post-container {
+ padding: 0.5em 1em;
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx b/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx
new file mode 100644
index 00000000..4697268b
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelinePostContainer.tsx
@@ -0,0 +1,20 @@
+import { ReactNode } from "react";
+import classNames from "classnames";
+
+import "./TimelinePostContainer.css";
+
+export interface TimelinePostEditCardProps {
+ className?: string;
+ children?: ReactNode;
+}
+
+export default function TimelinePostContainer({
+ className,
+ children,
+}: TimelinePostEditCardProps) {
+ return (
+ <div className={classNames("timeline-post-container", className)}>
+ {children}
+ </div>
+ );
+}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx b/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx
index c1fa0dd9..b0cc763a 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostEdit.tsx
@@ -18,7 +18,7 @@ import BlobImage from "@/views/common/BlobImage";
import LoadingButton from "@/views/common/button/LoadingButton";
import PopupMenu from "@/views/common/menu/PopupMenu";
import MarkdownPostEdit from "./MarkdownPostEdit";
-import TimelinePostEditCard from "./TimelinePostEditCard";
+import TimelinePostEditCard from "./TimelinePostContainer";
import IconButton from "@/views/common/button/IconButton";
import "./TimelinePostEdit.css";
@@ -118,7 +118,7 @@ export interface TimelinePostEditProps {
}
const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
- const { timeline, style, className, onPosted } = props;
+ const { timeline, className, onPosted } = props;
const { t } = useTranslation();
@@ -195,7 +195,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => {
};
return (
- <TimelinePostEditCard className={className} style={style}>
+ <TimelinePostEditCard className={className}>
{showMarkdown ? (
<MarkdownPostEdit
className="cru-fill-parent"
diff --git a/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx b/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx
deleted file mode 100644
index fdd53983..00000000
--- a/FrontEnd/src/pages/timeline/TimelinePostEditCard.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import * as React from "react";
-import classnames from "classnames";
-
-import Card from "@/views/common/Card";
-import TimelineLine from "./TimelineLine";
-
-import "./TimelinePostEdit.css";
-
-export interface TimelinePostEditCardProps {
- className?: string;
- style?: React.CSSProperties;
- children?: React.ReactNode;
-}
-
-const TimelinePostEdit: React.FC<TimelinePostEditCardProps> = ({
- className,
- style,
- children,
-}) => {
- return (
- <div
- className={classnames("timeline-item timeline-post-edit", className)}
- style={style}
- >
- <TimelineLine center="node" />
- <Card className="timeline-item-card">{children}</Card>
- </div>
- );
-};
-
-export default TimelinePostEdit;
diff --git a/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx b/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx
deleted file mode 100644
index 1ef0a287..00000000
--- a/FrontEnd/src/pages/timeline/TimelinePostEditNoLogin.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import * as React from "react";
-import { Trans } from "react-i18next";
-import { Link } from "react-router-dom";
-
-import TimelinePostEditCard from "./TimelinePostEditCard";
-
-export default function TimelinePostEditNoLogin(): React.ReactElement | null {
- return (
- <TimelinePostEditCard>
- <div className="mt-3 mb-4">
- <Trans
- i18nKey="timeline.postNoLogin"
- components={{ l: <Link to="/login" /> }}
- />
- </div>
- </TimelinePostEditCard>
- );
-}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostList.css b/FrontEnd/src/pages/timeline/TimelinePostList.css
new file mode 100644
index 00000000..bd575554
--- /dev/null
+++ b/FrontEnd/src/pages/timeline/TimelinePostList.css
@@ -0,0 +1,10 @@
+.timeline-post-timeline {
+ position: absolute;
+ left: 2.5em;
+ width: 1em;
+ top: 0;
+ bottom: 0;
+ background-color: var(--timeline-post-line-color);
+ box-shadow: var(--timeline-post-line-shadow);
+ z-index: -1;
+} \ No newline at end of file
diff --git a/FrontEnd/src/pages/timeline/TimelinePostListView.tsx b/FrontEnd/src/pages/timeline/TimelinePostList.tsx
index f878b004..a3501b33 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostListView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostList.tsx
@@ -1,11 +1,12 @@
-import { Fragment } from "react";
-import * as React from "react";
+import { useMemo, Fragment } from "react";
import { HttpTimelinePostInfo } from "@/http/timeline";
import TimelinePostView from "./TimelinePostView";
import TimelineDateLabel from "./TimelineDateLabel";
+import "./TimelinePostList.css";
+
function dateEqual(left: Date, right: Date): boolean {
return (
left.getDate() == right.getDate() &&
@@ -14,15 +15,15 @@ function dateEqual(left: Date, right: Date): boolean {
);
}
-export interface TimelinePostListViewProps {
+interface TimelinePostListViewProps {
posts: HttpTimelinePostInfo[];
onReload: () => void;
}
-const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
+export default function TimelinePostList(props: TimelinePostListViewProps) {
const { posts, onReload } = props;
- const groupedPosts = React.useMemo<
+ const groupedPosts = useMemo<
{
date: Date;
posts: (HttpTimelinePostInfo & { index: number })[];
@@ -51,7 +52,8 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
}, [posts]);
return (
- <>
+ <div>
+ <div className="timeline-post-timeline" />
{groupedPosts.map((group) => {
return (
<Fragment key={group.date.toDateString()}>
@@ -69,8 +71,6 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
</Fragment>
);
})}
- </>
+ </div>
);
-};
-
-export default TimelinePostListView;
+}
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.css b/FrontEnd/src/pages/timeline/TimelinePostView.css
index 2cd8cd6b..229b4a7a 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostView.css
+++ b/FrontEnd/src/pages/timeline/TimelinePostView.css
@@ -1,13 +1,3 @@
-.timeline-post {
- position: relative;
- padding: 0.5em;
-}
-
-.timeline-post-card {
- position: relative;
- padding: 0.5em 0.5em 0.5em 4em;
-}
-
.timeline-post-header {
display: flex;
align-items: center;
@@ -19,10 +9,17 @@
height: 2em;
}
-.timeline-post-delete-button {
+.timeline-post-edit-button {
+ float: right;
+}
+
+.timeline-post-options-mask {
position: absolute;
- right: 0;
- bottom: 0;
+ inset: 0;
+ background-color: hsla(0, 0%, 100%, 0.9);
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
}
.timeline-post-content {
diff --git a/FrontEnd/src/pages/timeline/TimelinePostView.tsx b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
index bdd2e3ef..2648fa21 100644
--- a/FrontEnd/src/pages/timeline/TimelinePostView.tsx
+++ b/FrontEnd/src/pages/timeline/TimelinePostView.tsx
@@ -1,5 +1,4 @@
import { useState } from "react";
-import classNames from "classnames";
import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
@@ -9,13 +8,14 @@ import { useClickOutside } from "@/utilities/hooks";
import UserAvatar from "@/views/common/user/UserAvatar";
import { useDialog } from "@/views/common/dialog";
-import Card from "@/views/common/Card";
import FlatButton from "@/views/common/button/FlatButton";
import ConfirmDialog from "@/views/common/dialog/ConfirmDialog";
-import TimelineLine from "./TimelineLine";
import TimelinePostContentView from "./TimelinePostContentView";
import IconButton from "@/views/common/button/IconButton";
+import TimelinePostContainer from "./TimelinePostContainer";
+import TimelinePostCard from "./TimelinePostCard";
+
import "./TimelinePostView.css";
export interface TimelinePostViewProps {
@@ -26,7 +26,7 @@ export interface TimelinePostViewProps {
}
export function TimelinePostView(props: TimelinePostViewProps) {
- const { post, className, onDeleted } = props;
+ const { post, onDeleted } = props;
const [operationMaskVisible, setOperationMaskVisible] =
useState<boolean>(false);
@@ -43,12 +43,8 @@ export function TimelinePostView(props: TimelinePostViewProps) {
useClickOutside(maskElement, () => setOperationMaskVisible(false));
return (
- <div
- id={`timeline-post-${post.id}`}
- className={classNames("timeline-post cru-primary", className)}
- >
- <TimelineLine center="node" />
- <Card className="timeline-post-card">
+ <TimelinePostContainer>
+ <TimelinePostCard className="cru-primary">
{post.editable && (
<IconButton
icon="chevron-down"
@@ -98,7 +94,7 @@ export function TimelinePostView(props: TimelinePostViewProps) {
/>
</div>
) : null}
- </Card>
+ </TimelinePostCard>
<ConfirmDialog
title="timeline.post.deleteDialog.title"
body="timeline.post.deleteDialog.prompt"
@@ -114,7 +110,7 @@ export function TimelinePostView(props: TimelinePostViewProps) {
}}
{...dialogPropsMap.delete}
/>
- </div>
+ </TimelinePostContainer>
);
}