mirror of
https://github.com/usememos/memos.git
synced 2025-03-23 22:20:16 +01:00
feat: personal memos page (#105)
* feat: no need to log in to view memos * chore: add a normal user to seed * feat: page for other members * fix: replace window.location * fix: can not get username on home * fix: check userID * fix: can visit other user's page after login * fix: do not redirect on wrong path * fix: path error when clicked heatmap * refactor: revise for review * chore: remove unused import * refactor: revise for review * feat: update each user's route to /u/:userId. * chore: eslint for import sort * refactor: revise for review
This commit is contained in:
parent
e202d7b8d6
commit
6b5d5e757e
@ -59,6 +59,10 @@ func BasicAuthMiddleware(s *Server, next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
if common.HasPrefixes(c.Path(), "/api/memo", "/api/tag", "/api/shortcut", "/api/user/:id/name") && c.Request().Method == http.MethodGet {
|
||||
return next(c)
|
||||
}
|
||||
|
||||
// If there is openId in query string and related user is found, then skip auth.
|
||||
openID := c.QueryParam("openId")
|
||||
if openID != "" {
|
||||
|
@ -60,7 +60,29 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
|
||||
})
|
||||
|
||||
g.GET("/memo", func(c echo.Context) error {
|
||||
userID := c.Get(getUserIDContextKey()).(int)
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
}
|
||||
}
|
||||
|
||||
memoFind := &api.MemoFind{
|
||||
CreatorID: &userID,
|
||||
}
|
||||
|
@ -59,7 +59,29 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
|
||||
})
|
||||
|
||||
g.GET("/shortcut", func(c echo.Context) error {
|
||||
userID := c.Get(getUserIDContextKey()).(int)
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
}
|
||||
}
|
||||
|
||||
shortcutFind := &api.ShortcutFind{
|
||||
CreatorID: &userID,
|
||||
}
|
||||
|
@ -2,9 +2,11 @@ package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/usememos/memos/api"
|
||||
|
||||
@ -13,7 +15,29 @@ import (
|
||||
|
||||
func (s *Server) registerTagRoutes(g *echo.Group) {
|
||||
g.GET("/tag", func(c echo.Context) error {
|
||||
userID := c.Get(getUserIDContextKey()).(int)
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
if c.QueryParam("userID") != "" {
|
||||
var err error
|
||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
||||
}
|
||||
} else {
|
||||
ownerUserType := api.Owner
|
||||
ownerUser, err := s.Store.FindUser(&api.UserFind{
|
||||
Role: &ownerUserType,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find owner user").SetInternal(err)
|
||||
}
|
||||
if ownerUser == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Owner user do not exist")
|
||||
}
|
||||
userID = ownerUser.ID
|
||||
}
|
||||
}
|
||||
|
||||
contentSearch := "#"
|
||||
normalRowStatus := api.Normal
|
||||
memoFind := api.MemoFind{
|
||||
|
@ -51,6 +51,29 @@ func (s *Server) registerUserRoutes(g *echo.Group) {
|
||||
return nil
|
||||
})
|
||||
|
||||
g.GET("/user/:id/name", func(c echo.Context) error {
|
||||
id, err := strconv.Atoi(c.Param("id"))
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted user id").SetInternal(err)
|
||||
}
|
||||
|
||||
user, err := s.Store.FindUser(&api.UserFind{
|
||||
ID: &id,
|
||||
})
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch user").SetInternal(err)
|
||||
}
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "User not found")
|
||||
}
|
||||
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
||||
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(user.Name)); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode user response").SetInternal(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// GET /api/user/me is used to check if the user is logged in.
|
||||
g.GET("/user/me", func(c echo.Context) error {
|
||||
userSessionID := c.Get(getUserIDContextKey())
|
||||
|
@ -17,3 +17,23 @@ VALUES
|
||||
-- raw password: secret
|
||||
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
user (
|
||||
`id`,
|
||||
`email`,
|
||||
`role`,
|
||||
`name`,
|
||||
`open_id`,
|
||||
`password_hash`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
102,
|
||||
'jack@usememos.com',
|
||||
'USER',
|
||||
'Jack',
|
||||
'jack_open_id',
|
||||
-- raw password: secret
|
||||
'$2a$14$ajq8Q7fbtFRQvXpdCq7Jcuy.Rx1h/L4J60Otx.gyNLbAYctGMJ9tK'
|
||||
);
|
||||
|
@ -42,3 +42,16 @@ VALUES
|
||||
'好好学习,天天向上。🤜🤛',
|
||||
101
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
memo (
|
||||
`id`,
|
||||
`content`,
|
||||
`creator_id`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
104,
|
||||
'好好学习,天天向上。🤜🤛',
|
||||
102
|
||||
);
|
||||
|
@ -23,6 +23,12 @@
|
||||
],
|
||||
"@typescript-eslint/no-empty-interface": ["off"],
|
||||
"@typescript-eslint/no-explicit-any": ["off"],
|
||||
"react/react-in-jsx-scope": "off"
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
"memberSyntaxSortOrder": ["all", "multiple", "single", "none"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { escape, indexOf } from "lodash-es";
|
||||
import { IMAGE_URL_REG, LINK_REG, MEMO_LINK_REG, TAG_REG, UNKNOWN_ID } from "../helpers/consts";
|
||||
import { DONE_BLOCK_REG, parseMarkedToHtml, TODO_BLOCK_REG } from "../helpers/marked";
|
||||
import * as utils from "../helpers/utils";
|
||||
import { editorStateService, locationService, memoService } from "../services";
|
||||
import { editorStateService, locationService, memoService, userService } from "../services";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import Image from "./Image";
|
||||
import showMemoCardDialog from "./MemoCardDialog";
|
||||
@ -112,7 +112,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
} else {
|
||||
locationService.setTagQuery(tagName);
|
||||
}
|
||||
} else if (targetEl.classList.contains("todo-block")) {
|
||||
} else if (targetEl.classList.contains("todo-block") && userService.isNotVisitor()) {
|
||||
const status = targetEl.dataset?.value;
|
||||
const todoElementList = [...(memoContainerRef.current?.querySelectorAll(`span.todo-block[data-value=${status}]`) ?? [])];
|
||||
for (const element of todoElementList) {
|
||||
@ -158,38 +158,40 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
<span className="ml-2">PINNED</span>
|
||||
</Only>
|
||||
</span>
|
||||
<div className="btns-container">
|
||||
<span className="btn more-action-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container">
|
||||
<div className="btns-container">
|
||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleEditMemoClick}>
|
||||
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
||||
<span className="tip-text">Edit</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||
<img className="icon-img" src="/icons/share.svg" alt="" />
|
||||
<span className="tip-text">Share</span>
|
||||
{userService.isNotVisitor() && (
|
||||
<div className="btns-container">
|
||||
<span className="btn more-action-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container">
|
||||
<div className="btns-container">
|
||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleEditMemoClick}>
|
||||
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
||||
<span className="tip-text">Edit</span>
|
||||
</div>
|
||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||
<img className="icon-img" src="/icons/share.svg" alt="" />
|
||||
<span className="tip-text">Share</span>
|
||||
</div>
|
||||
</div>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
Mark
|
||||
</span>
|
||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||
View Story
|
||||
</span>
|
||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||
Archive
|
||||
</span>
|
||||
</div>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
Mark
|
||||
</span>
|
||||
<span className="btn" onClick={handleShowMemoStoryDialog}>
|
||||
View Story
|
||||
</span>
|
||||
<span className="btn archive-btn" onClick={handleArchiveMemoClick}>
|
||||
Archive
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div
|
||||
ref={memoContainerRef}
|
||||
|
@ -55,6 +55,10 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleSignInBtnClick = async () => {
|
||||
locationService.replaceHistory("/signin");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`menu-btns-popup ${shownStatus ? "" : "hidden"}`} ref={popupElRef}>
|
||||
<button className="btn action-btn" onClick={handleAboutBtnClick}>
|
||||
@ -63,8 +67,8 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
||||
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
||||
<span className="icon">🎯</span> Ping
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleSignOutBtnClick}>
|
||||
<span className="icon">👋</span> Sign out
|
||||
<button className="btn action-btn" onClick={userService.isNotVisitor() ? handleSignOutBtnClick : handleSignInBtnClick}>
|
||||
<span className="icon">👋</span> {userService.isNotVisitor() ? "Sign out" : "Sign in"}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { locationService, shortcutService } from "../services";
|
||||
import { locationService, shortcutService, userService } from "../services";
|
||||
import { useAppSelector } from "../store";
|
||||
import * as utils from "../helpers/utils";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
@ -38,9 +38,11 @@ const ShortcutList: React.FC<Props> = () => {
|
||||
<div className="shortcuts-wrapper">
|
||||
<p className="title-text">
|
||||
<span className="normal-text">Shortcuts</span>
|
||||
<span className="btn" onClick={() => showCreateShortcutDialog()}>
|
||||
<img src="/icons/add.svg" alt="add shortcut" />
|
||||
</span>
|
||||
{userService.isNotVisitor() && (
|
||||
<span className="btn" onClick={() => showCreateShortcutDialog()}>
|
||||
<img src="/icons/add.svg" alt="add shortcut" />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<div className="shortcuts-container">
|
||||
{sortedShortcuts.map((s) => {
|
||||
@ -114,28 +116,30 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
||||
<div className="shortcut-text-container">
|
||||
<span className="shortcut-text">{shortcut.title}</span>
|
||||
</div>
|
||||
<div className="btns-container">
|
||||
<span className="action-btn toggle-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="action-btns-wrapper">
|
||||
<div className="action-btns-container">
|
||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||
Edit
|
||||
</span>
|
||||
<span
|
||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||
onClick={handleDeleteMemoClick}
|
||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||
>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
</span>
|
||||
{userService.isNotVisitor() && (
|
||||
<div className="btns-container">
|
||||
<span className="action-btn toggle-btn">
|
||||
<img className="icon-img" src="/icons/more.svg" />
|
||||
</span>
|
||||
<div className="action-btns-wrapper">
|
||||
<div className="action-btns-container">
|
||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||
Edit
|
||||
</span>
|
||||
<span
|
||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||
onClick={handleDeleteMemoClick}
|
||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||
>
|
||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -7,6 +7,7 @@ import UserBanner from "./UserBanner";
|
||||
import UsageHeatMap from "./UsageHeatMap";
|
||||
import ShortcutList from "./ShortcutList";
|
||||
import TagList from "./TagList";
|
||||
import { userService } from "../services";
|
||||
import "../less/siderbar.less";
|
||||
|
||||
interface Props {}
|
||||
@ -48,17 +49,21 @@ const Sidebar: React.FC<Props> = () => {
|
||||
</div>
|
||||
</div>
|
||||
<UsageHeatMap />
|
||||
<div className="action-btns-container">
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> Daily Review
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||
<span className="icon">⚙️</span> Setting
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||
<span className="icon">🗂</span> Archived
|
||||
</button>
|
||||
</div>
|
||||
{userService.isNotVisitor() && (
|
||||
<>
|
||||
<div className="action-btns-container">
|
||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||
<span className="icon">📅</span> Daily Review
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||
<span className="icon">⚙️</span> Setting
|
||||
</button>
|
||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||
<span className="icon">🗂</span> Archived
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<ShortcutList />
|
||||
<TagList />
|
||||
</aside>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService, memoService } from "../services";
|
||||
import { locationService, memoService, userService } from "../services";
|
||||
import useToggle from "../hooks/useToggle";
|
||||
import Only from "./common/OnlyWhen";
|
||||
import * as utils from "../helpers/utils";
|
||||
@ -71,7 +71,7 @@ const TagList: React.FC<Props> = () => {
|
||||
{tags.map((t, idx) => (
|
||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||
))}
|
||||
<Only when={tags.length < 5}>
|
||||
<Only when={userService.isNotVisitor() && tags.length < 5}>
|
||||
<p className="tag-tip-container">
|
||||
Enter <span className="code-text">#tag </span> to create a tag
|
||||
</p>
|
||||
|
@ -73,9 +73,6 @@ const UsageHeatMap: React.FC<Props> = () => {
|
||||
locationService.setFromAndToQuery();
|
||||
setCurrentStat(null);
|
||||
} else if (item.count > 0) {
|
||||
if (!["/"].includes(locationService.getState().pathname)) {
|
||||
locationService.setPathname("/");
|
||||
}
|
||||
locationService.setFromAndToQuery(item.timestamp, item.timestamp + DAILY_TIMESTAMP);
|
||||
setCurrentStat(item);
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { useAppSelector } from "../store";
|
||||
import { locationService } from "../services";
|
||||
import * as api from "../helpers/api";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import MenuBtnsPopup from "./MenuBtnsPopup";
|
||||
import { getUserIdFromPath } from "../services/userService";
|
||||
import { locationService } from "../services";
|
||||
import toastHelper from "./Toast";
|
||||
import { useAppSelector } from "../store";
|
||||
import "../less/user-banner.less";
|
||||
|
||||
interface Props {}
|
||||
@ -10,10 +13,9 @@ const UserBanner: React.FC<Props> = () => {
|
||||
const user = useAppSelector((state) => state.user.user);
|
||||
const [shouldShowPopupBtns, setShouldShowPopupBtns] = useState(false);
|
||||
|
||||
const username = user ? user.name : "Memos";
|
||||
const [username, setUsername] = useState(user ? user.name : "Memos");
|
||||
|
||||
const handleUsernameClick = useCallback(() => {
|
||||
locationService.pushHistory("/");
|
||||
locationService.clearQuery();
|
||||
}, []);
|
||||
|
||||
@ -21,6 +23,27 @@ const UserBanner: React.FC<Props> = () => {
|
||||
setShouldShowPopupBtns(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (username === "Memos") {
|
||||
if (locationService.getState().pathname === "/") {
|
||||
api.getSystemStatus().then(({ data }) => {
|
||||
const { data: status } = data;
|
||||
setUsername(status.owner.name);
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.getUserNameById(Number(getUserIdFromPath()))
|
||||
.then(({ data }) => {
|
||||
const { data: username } = data;
|
||||
setUsername(username);
|
||||
})
|
||||
.catch(() => {
|
||||
toastHelper.error("User not found");
|
||||
});
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="user-banner-container">
|
||||
<div className="username-container" onClick={handleUsernameClick}>
|
||||
|
@ -44,16 +44,20 @@ export function getUserList() {
|
||||
return axios.get<ResponseObject<User[]>>("/api/user");
|
||||
}
|
||||
|
||||
export function getUserNameById(id: number) {
|
||||
return axios.get<ResponseObject<string>>(`/api/user/${id}/name`);
|
||||
}
|
||||
|
||||
export function patchUser(userPatch: UserPatch) {
|
||||
return axios.patch<ResponseObject<User>>("/api/user/me", userPatch);
|
||||
}
|
||||
|
||||
export function getMemoList() {
|
||||
return axios.get<ResponseObject<Memo[]>>("/api/memo");
|
||||
export function getMemoList(userId?: number) {
|
||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo${userId ? "?userID=" + userId : ""}`);
|
||||
}
|
||||
|
||||
export function getArchivedMemoList() {
|
||||
return axios.get<ResponseObject<Memo[]>>("/api/memo?rowStatus=ARCHIVED");
|
||||
export function getArchivedMemoList(userId?: number) {
|
||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo?rowStatus=ARCHIVED${userId ? "&userID=" + userId : ""}`);
|
||||
}
|
||||
|
||||
export function createMemo(memoCreate: MemoCreate) {
|
||||
@ -80,8 +84,8 @@ export function deleteMemo(memoId: MemoId) {
|
||||
return axios.delete(`/api/memo/${memoId}`);
|
||||
}
|
||||
|
||||
export function getShortcutList() {
|
||||
return axios.get<ResponseObject<Shortcut[]>>("/api/shortcut");
|
||||
export function getShortcutList(userId?: number) {
|
||||
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut${userId ? "?userID=" + userId : ""}`);
|
||||
}
|
||||
|
||||
export function createShortcut(shortcutCreate: ShortcutCreate) {
|
||||
@ -100,6 +104,6 @@ export function uploadFile(formData: FormData) {
|
||||
return axios.post<ResponseObject<Resource>>("/api/resource", formData);
|
||||
}
|
||||
|
||||
export function getTagList() {
|
||||
return axios.get<ResponseObject<string[]>>("/api/tag");
|
||||
export function getTagList(userId?: number) {
|
||||
return axios.get<ResponseObject<string[]>>(`/api/tag${userId ? "?userID=" + userId : ""}`);
|
||||
}
|
||||
|
@ -12,6 +12,9 @@ function Home() {
|
||||
const loadingState = useLoading();
|
||||
|
||||
useEffect(() => {
|
||||
if (window.location.pathname !== locationService.getState().pathname) {
|
||||
locationService.replaceHistory("/");
|
||||
}
|
||||
const { user } = userService.getState();
|
||||
if (!user) {
|
||||
userService
|
||||
@ -20,11 +23,10 @@ function Home() {
|
||||
// do nth
|
||||
})
|
||||
.finally(() => {
|
||||
if (userService.getState().user) {
|
||||
loadingState.setFinish();
|
||||
} else {
|
||||
locationService.replaceHistory("/signin");
|
||||
if (userService.getState().user && locationService.getState().pathname !== "/") {
|
||||
locationService.replaceHistory("/");
|
||||
}
|
||||
loadingState.setFinish();
|
||||
});
|
||||
} else {
|
||||
loadingState.setFinish();
|
||||
@ -39,7 +41,7 @@ function Home() {
|
||||
<main className="memos-wrapper">
|
||||
<div className="memos-editor-wrapper">
|
||||
<MemosHeader />
|
||||
<MemoEditor />
|
||||
{userService.isNotVisitor() && <MemoEditor />}
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as api from "../helpers/api";
|
||||
import { createMemo, patchMemo, setMemos, setTags } from "../store/modules/memo";
|
||||
import store from "../store";
|
||||
import { getUserIdFromPath } from "./userService";
|
||||
|
||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||
return {
|
||||
@ -16,7 +17,7 @@ const memoService = {
|
||||
},
|
||||
|
||||
fetchAllMemos: async () => {
|
||||
const { data } = (await api.getMemoList()).data;
|
||||
const { data } = (await api.getMemoList(getUserIdFromPath())).data;
|
||||
const memos = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m));
|
||||
store.dispatch(setMemos(memos));
|
||||
|
||||
@ -24,7 +25,7 @@ const memoService = {
|
||||
},
|
||||
|
||||
fetchArchivedMemos: async () => {
|
||||
const { data } = (await api.getArchivedMemoList()).data;
|
||||
const { data } = (await api.getArchivedMemoList(getUserIdFromPath())).data;
|
||||
const archivedMemos = data.map((m) => {
|
||||
return convertResponseModelMemo(m);
|
||||
});
|
||||
@ -42,7 +43,7 @@ const memoService = {
|
||||
},
|
||||
|
||||
updateTagsState: async () => {
|
||||
const { data } = (await api.getTagList()).data;
|
||||
const { data } = (await api.getTagList(getUserIdFromPath())).data;
|
||||
store.dispatch(setTags(data));
|
||||
},
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import * as api from "../helpers/api";
|
||||
import store from "../store/";
|
||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
||||
import { getUserIdFromPath } from "./userService";
|
||||
|
||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
||||
return {
|
||||
@ -16,7 +17,7 @@ const shortcutService = {
|
||||
},
|
||||
|
||||
getMyAllShortcuts: async () => {
|
||||
const { data } = (await api.getShortcutList()).data;
|
||||
const { data } = (await api.getShortcutList(getUserIdFromPath())).data;
|
||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||
store.dispatch(setShortcuts(shortcuts));
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { locationService } from ".";
|
||||
import * as api from "../helpers/api";
|
||||
import store from "../store";
|
||||
import { setUser, patchUser } from "../store/modules/user";
|
||||
@ -10,11 +11,20 @@ const convertResponseModelUser = (user: User): User => {
|
||||
};
|
||||
};
|
||||
|
||||
export const getUserIdFromPath = () => {
|
||||
const path = locationService.getState().pathname.slice(3);
|
||||
return !isNaN(Number(path)) ? Number(path) : undefined;
|
||||
};
|
||||
|
||||
const userService = {
|
||||
getState: () => {
|
||||
return store.getState().user;
|
||||
},
|
||||
|
||||
isNotVisitor: () => {
|
||||
return store.getState().user.user !== undefined;
|
||||
},
|
||||
|
||||
doSignIn: async () => {
|
||||
const { data: user } = (await api.getUser()).data;
|
||||
if (user) {
|
||||
|
@ -20,7 +20,7 @@ interface State {
|
||||
}
|
||||
|
||||
const getValidPathname = (pathname: string): string => {
|
||||
if (["/", "/signin"].includes(pathname)) {
|
||||
if (["/", "/signin"].includes(pathname) || pathname.match(/^\/u\/(\d+)/)) {
|
||||
return pathname;
|
||||
} else {
|
||||
return "/";
|
||||
|
Loading…
x
Reference in New Issue
Block a user