mirror of https://github.com/usememos/memos
feat: add explore page (#205)
parent
5eea1339c9
commit
e9ac6affef
@ -0,0 +1,29 @@
|
|||||||
|
import { useRef } from "react";
|
||||||
|
import { formatMemoContent } from "../helpers/marked";
|
||||||
|
import "../less/memo-content.less";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className: string;
|
||||||
|
content: string;
|
||||||
|
onMemoContentClick: (e: React.MouseEvent) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoContent: React.FC<Props> = (props: Props) => {
|
||||||
|
const { className, content, onMemoContentClick } = props;
|
||||||
|
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const handleMemoContentClick = async (e: React.MouseEvent) => {
|
||||||
|
onMemoContentClick(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={memoContentContainerRef}
|
||||||
|
className={`memo-content-text ${className}`}
|
||||||
|
onClick={handleMemoContentClick}
|
||||||
|
dangerouslySetInnerHTML={{ __html: formatMemoContent(content) }}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MemoContent;
|
@ -0,0 +1,58 @@
|
|||||||
|
@import "./mixin.less";
|
||||||
|
|
||||||
|
.page-wrapper.explore {
|
||||||
|
@apply relative top-0 w-full h-screen overflow-y-auto overflow-x-hidden;
|
||||||
|
background-color: #f6f5f4;
|
||||||
|
|
||||||
|
> .page-container {
|
||||||
|
@apply relative w-full min-h-screen mx-auto flex flex-col justify-start items-center;
|
||||||
|
|
||||||
|
> .page-header {
|
||||||
|
@apply relative max-w-2xl w-full min-h-full flex flex-row justify-start items-center px-4 sm:pr-6;
|
||||||
|
|
||||||
|
> .logo-img {
|
||||||
|
@apply h-14 w-auto mt-6 mb-2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .memos-wrapper {
|
||||||
|
@apply relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4 sm:pr-6;
|
||||||
|
|
||||||
|
> .memo-container {
|
||||||
|
@apply flex flex-col justify-start items-start w-full p-4 mt-2 bg-white rounded-lg border border-white hover:border-gray-200;
|
||||||
|
|
||||||
|
> .memo-header {
|
||||||
|
@apply mb-2 w-full flex flex-row justify-start items-center text-sm font-mono text-gray-400;
|
||||||
|
|
||||||
|
> .split-text {
|
||||||
|
@apply mx-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .name-text {
|
||||||
|
@apply hover:text-green-600 hover:underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .memo-content {
|
||||||
|
@apply cursor-default;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
@apply cursor-default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .addtion-btn-container {
|
||||||
|
@apply fixed bottom-12 left-1/2 -translate-x-1/2;
|
||||||
|
|
||||||
|
> .btn {
|
||||||
|
@apply bg-blue-600 text-white px-4 py-2 rounded-3xl shadow-2xl hover:opacity-80;
|
||||||
|
|
||||||
|
> .icon {
|
||||||
|
@apply text-lg mr-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,86 @@
|
|||||||
|
import dayjs from "dayjs";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { locationService, memoService, userService } from "../services";
|
||||||
|
import { useAppSelector } from "../store";
|
||||||
|
import useI18n from "../hooks/useI18n";
|
||||||
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import MemoContent from "../components/MemoContent";
|
||||||
|
import "../less/explore.less";
|
||||||
|
|
||||||
|
interface State {
|
||||||
|
memos: Memo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Explore = () => {
|
||||||
|
const { t, locale } = useI18n();
|
||||||
|
const user = useAppSelector((state) => state.user.user);
|
||||||
|
const location = useAppSelector((state) => state.location);
|
||||||
|
const [state, setState] = useState<State>({
|
||||||
|
memos: [],
|
||||||
|
});
|
||||||
|
const loadingState = useLoading();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
userService
|
||||||
|
.initialState()
|
||||||
|
.catch()
|
||||||
|
.finally(async () => {
|
||||||
|
const { host } = userService.getState();
|
||||||
|
if (!host) {
|
||||||
|
locationService.replaceHistory("/auth");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
memoService.fetchAllMemos().then((memos) => {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
memos,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
loadingState.setFinish();
|
||||||
|
});
|
||||||
|
}, [location]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="page-wrapper explore">
|
||||||
|
{loadingState.isLoading ? null : (
|
||||||
|
<div className="page-container">
|
||||||
|
<div className="page-header">
|
||||||
|
<img className="logo-img" src="/logo-full.webp" alt="" />
|
||||||
|
</div>
|
||||||
|
<main className="memos-wrapper">
|
||||||
|
{state.memos.map((memo) => {
|
||||||
|
const createdAtStr = dayjs(memo.createdTs).locale(locale).format("YYYY/MM/DD HH:mm:ss");
|
||||||
|
return (
|
||||||
|
<div className="memo-container" key={memo.id}>
|
||||||
|
<div className="memo-header">
|
||||||
|
<span className="time-text">{createdAtStr}</span>
|
||||||
|
<span className="split-text">by</span>
|
||||||
|
<a className="name-text" href={`/u/${memo.creator.id}`}>
|
||||||
|
{memo.creator.name}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<MemoContent className="memo-content" content={memo.content} onMemoContentClick={() => undefined} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div className="addtion-btn-container">
|
||||||
|
{user ? (
|
||||||
|
<button className="btn" onClick={() => (window.location.href = "/")}>
|
||||||
|
<span className="icon">🏠</span> {t("common.back-to-home")}
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button className="btn" onClick={() => (window.location.href = "/auth")}>
|
||||||
|
<span className="icon">👉</span> {t("common.sign-in")}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Explore;
|
Loading…
Reference in New Issue