mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
refactor: visitor view (#107)
* refactor: update api * refactor: visitor view * chore: update seed data
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
type Login struct {
|
type Signin struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
@ -13,33 +13,33 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) registerAuthRoutes(g *echo.Group) {
|
func (s *Server) registerAuthRoutes(g *echo.Group) {
|
||||||
g.POST("/auth/login", func(c echo.Context) error {
|
g.POST("/auth/signin", func(c echo.Context) error {
|
||||||
login := &api.Login{}
|
signin := &api.Signin{}
|
||||||
if err := json.NewDecoder(c.Request().Body).Decode(login); err != nil {
|
if err := json.NewDecoder(c.Request().Body).Decode(signin); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted login request").SetInternal(err)
|
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted signin request").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
userFind := &api.UserFind{
|
userFind := &api.UserFind{
|
||||||
Email: &login.Email,
|
Email: &signin.Email,
|
||||||
}
|
}
|
||||||
user, err := s.Store.FindUser(userFind)
|
user, err := s.Store.FindUser(userFind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", login.Email)).SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find user by email %s", signin.Email)).SetInternal(err)
|
||||||
}
|
}
|
||||||
if user == nil {
|
if user == nil {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", login.Email))
|
return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("User not found with email %s", signin.Email))
|
||||||
} else if user.RowStatus == api.Archived {
|
} else if user.RowStatus == api.Archived {
|
||||||
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", login.Email))
|
return echo.NewHTTPError(http.StatusForbidden, fmt.Sprintf("User has been archived with email %s", signin.Email))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare the stored hashed password, with the hashed version of the password that was received.
|
// Compare the stored hashed password, with the hashed version of the password that was received.
|
||||||
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(login.Password)); err != nil {
|
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(signin.Password)); err != nil {
|
||||||
// If the two passwords don't match, return a 401 status.
|
// If the two passwords don't match, return a 401 status.
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect password").SetInternal(err)
|
return echo.NewHTTPError(http.StatusUnauthorized, "Incorrect password").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = setUserSession(c, user); err != nil {
|
if err = setUserSession(c, user); err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set login session").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to set signin session").SetInternal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
||||||
|
@ -60,31 +60,17 @@ func (s *Server) registerMemoRoutes(g *echo.Group) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
g.GET("/memo", func(c echo.Context) error {
|
g.GET("/memo", func(c echo.Context) error {
|
||||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
memoFind := &api.MemoFind{}
|
||||||
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{
|
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||||
CreatorID: &userID,
|
memoFind.CreatorID = &userID
|
||||||
|
} else {
|
||||||
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find memo")
|
||||||
|
}
|
||||||
|
|
||||||
|
memoFind.CreatorID = &userID
|
||||||
}
|
}
|
||||||
|
|
||||||
rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
|
rowStatus := api.RowStatus(c.QueryParam("rowStatus"))
|
||||||
|
@ -45,7 +45,7 @@ func NewServer(profile *profile.Profile) *Server {
|
|||||||
HTML5: true,
|
HTML5: true,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// In dev mode, set the const secret key to make login session persistence.
|
// In dev mode, set the const secret key to make signin session persistence.
|
||||||
secret := []byte("usememos")
|
secret := []byte("usememos")
|
||||||
if profile.Mode == "prod" {
|
if profile.Mode == "prod" {
|
||||||
secret = securecookie.GenerateRandomKey(16)
|
secret = securecookie.GenerateRandomKey(16)
|
||||||
|
@ -59,32 +59,19 @@ func (s *Server) registerShortcutRoutes(g *echo.Group) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
g.GET("/shortcut", func(c echo.Context) error {
|
g.GET("/shortcut", func(c echo.Context) error {
|
||||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
shortcutFind := &api.ShortcutFind{}
|
||||||
if !ok {
|
|
||||||
if c.QueryParam("userID") != "" {
|
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||||
var err error
|
shortcutFind.CreatorID = &userID
|
||||||
userID, err = strconv.Atoi(c.QueryParam("userID"))
|
} else {
|
||||||
if err != nil {
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("ID is not a number: %s", c.QueryParam("userID")))
|
if !ok {
|
||||||
}
|
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find shortcut")
|
||||||
} 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.CreatorID = &userID
|
||||||
}
|
}
|
||||||
|
|
||||||
shortcutFind := &api.ShortcutFind{
|
|
||||||
CreatorID: &userID,
|
|
||||||
}
|
|
||||||
list, err := s.Store.FindShortcutList(shortcutFind)
|
list, err := s.Store.FindShortcutList(shortcutFind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch shortcut list").SetInternal(err)
|
||||||
|
@ -2,7 +2,6 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
@ -15,37 +14,24 @@ import (
|
|||||||
|
|
||||||
func (s *Server) registerTagRoutes(g *echo.Group) {
|
func (s *Server) registerTagRoutes(g *echo.Group) {
|
||||||
g.GET("/tag", func(c echo.Context) error {
|
g.GET("/tag", func(c echo.Context) error {
|
||||||
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 := "#"
|
contentSearch := "#"
|
||||||
normalRowStatus := api.Normal
|
normalRowStatus := api.Normal
|
||||||
memoFind := api.MemoFind{
|
memoFind := api.MemoFind{
|
||||||
CreatorID: &userID,
|
|
||||||
ContentSearch: &contentSearch,
|
ContentSearch: &contentSearch,
|
||||||
RowStatus: &normalRowStatus,
|
RowStatus: &normalRowStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if userID, err := strconv.Atoi(c.QueryParam("creatorId")); err == nil {
|
||||||
|
memoFind.CreatorID = &userID
|
||||||
|
} else {
|
||||||
|
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||||
|
if !ok {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Missing creatorId to find shortcut")
|
||||||
|
}
|
||||||
|
|
||||||
|
memoFind.CreatorID = &userID
|
||||||
|
}
|
||||||
|
|
||||||
memoList, err := s.Store.FindMemoList(&memoFind)
|
memoList, err := s.Store.FindMemoList(&memoFind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo list").SetInternal(err)
|
||||||
|
@ -52,6 +52,24 @@ INSERT INTO
|
|||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
104,
|
104,
|
||||||
'好好学习,天天向上。🤜🤛',
|
'#TODO
|
||||||
|
- [x] Take more photos about **🌄 sunset**;
|
||||||
|
- [ ] Clean the classroom;
|
||||||
|
- [ ] Watch *👦 The Boys*;
|
||||||
|
(👆 click to toggle status)
|
||||||
|
',
|
||||||
|
102
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO
|
||||||
|
memo (
|
||||||
|
`id`,
|
||||||
|
`content`,
|
||||||
|
`creator_id`
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
105,
|
||||||
|
'三人行,必有我师焉!👨🏫',
|
||||||
102
|
102
|
||||||
);
|
);
|
||||||
|
@ -11,12 +11,14 @@
|
|||||||
"@reduxjs/toolkit": "^1.8.1",
|
"@reduxjs/toolkit": "^1.8.1",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
|
"qs": "^6.11.0",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-redux": "^8.0.1"
|
"react-redux": "^8.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash-es": "^4.17.5",
|
"@types/lodash-es": "^4.17.5",
|
||||||
|
"@types/qs": "^6.9.7",
|
||||||
"@types/react": "^18.0.9",
|
"@types/react": "^18.0.9",
|
||||||
"@types/react-dom": "^18.0.4",
|
"@types/react-dom": "^18.0.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||||
|
@ -112,7 +112,11 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
} else {
|
} else {
|
||||||
locationService.setTagQuery(tagName);
|
locationService.setTagQuery(tagName);
|
||||||
}
|
}
|
||||||
} else if (targetEl.classList.contains("todo-block") && userService.isNotVisitor()) {
|
} else if (targetEl.classList.contains("todo-block")) {
|
||||||
|
if (userService.isVisitorMode()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const status = targetEl.dataset?.value;
|
const status = targetEl.dataset?.value;
|
||||||
const todoElementList = [...(memoContainerRef.current?.querySelectorAll(`span.todo-block[data-value=${status}]`) ?? [])];
|
const todoElementList = [...(memoContainerRef.current?.querySelectorAll(`span.todo-block[data-value=${status}]`) ?? [])];
|
||||||
for (const element of todoElementList) {
|
for (const element of todoElementList) {
|
||||||
@ -158,40 +162,38 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
<span className="ml-2">PINNED</span>
|
<span className="ml-2">PINNED</span>
|
||||||
</Only>
|
</Only>
|
||||||
</span>
|
</span>
|
||||||
{userService.isNotVisitor() && (
|
<div className={`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`}>
|
||||||
<div className="btns-container">
|
<span className="btn more-action-btn">
|
||||||
<span className="btn more-action-btn">
|
<img className="icon-img" src="/icons/more.svg" />
|
||||||
<img className="icon-img" src="/icons/more.svg" />
|
</span>
|
||||||
</span>
|
<div className="more-action-btns-wrapper">
|
||||||
<div className="more-action-btns-wrapper">
|
<div className="more-action-btns-container">
|
||||||
<div className="more-action-btns-container">
|
<div className="btns-container">
|
||||||
<div className="btns-container">
|
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||||
<div className="btn" onClick={handleTogglePinMemoBtnClick}>
|
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
||||||
<img className="icon-img" src="/icons/pin.svg" alt="" />
|
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
||||||
<span className="tip-text">{memo.pinned ? "Unpin" : "Pin"}</span>
|
</div>
|
||||||
</div>
|
<div className="btn" onClick={handleEditMemoClick}>
|
||||||
<div className="btn" onClick={handleEditMemoClick}>
|
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
||||||
<img className="icon-img" src="/icons/edit.svg" alt="" />
|
<span className="tip-text">Edit</span>
|
||||||
<span className="tip-text">Edit</span>
|
</div>
|
||||||
</div>
|
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
||||||
<div className="btn" onClick={handleGenMemoImageBtnClick}>
|
<img className="icon-img" src="/icons/share.svg" alt="" />
|
||||||
<img className="icon-img" src="/icons/share.svg" alt="" />
|
<span className="tip-text">Share</span>
|
||||||
<span className="tip-text">Share</span>
|
|
||||||
</div>
|
|
||||||
</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>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
ref={memoContainerRef}
|
ref={memoContainerRef}
|
||||||
|
@ -67,8 +67,8 @@ const MenuBtnsPopup: React.FC<Props> = (props: Props) => {
|
|||||||
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
<button className="btn action-btn" onClick={handlePingBtnClick}>
|
||||||
<span className="icon">🎯</span> Ping
|
<span className="icon">🎯</span> Ping
|
||||||
</button>
|
</button>
|
||||||
<button className="btn action-btn" onClick={userService.isNotVisitor() ? handleSignOutBtnClick : handleSignInBtnClick}>
|
<button className="btn action-btn" onClick={!userService.isVisitorMode() ? handleSignOutBtnClick : handleSignInBtnClick}>
|
||||||
<span className="icon">👋</span> {userService.isNotVisitor() ? "Sign out" : "Sign in"}
|
<span className="icon">👋</span> {!userService.isVisitorMode() ? "Sign out" : "Sign in"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import { useAppSelector } from "../store";
|
|||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import useToggle from "../hooks/useToggle";
|
import useToggle from "../hooks/useToggle";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import Only from "./common/OnlyWhen";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
import showCreateShortcutDialog from "./CreateShortcutDialog";
|
||||||
import "../less/shortcut-list.less";
|
import "../less/shortcut-list.less";
|
||||||
@ -38,11 +39,11 @@ const ShortcutList: React.FC<Props> = () => {
|
|||||||
<div className="shortcuts-wrapper">
|
<div className="shortcuts-wrapper">
|
||||||
<p className="title-text">
|
<p className="title-text">
|
||||||
<span className="normal-text">Shortcuts</span>
|
<span className="normal-text">Shortcuts</span>
|
||||||
{userService.isNotVisitor() && (
|
<Only when={!userService.isVisitorMode()}>
|
||||||
<span className="btn" onClick={() => showCreateShortcutDialog()}>
|
<span className="btn" onClick={() => showCreateShortcutDialog()}>
|
||||||
<img src="/icons/add.svg" alt="add shortcut" />
|
<img src="/icons/add.svg" alt="add shortcut" />
|
||||||
</span>
|
</span>
|
||||||
)}
|
</Only>
|
||||||
</p>
|
</p>
|
||||||
<div className="shortcuts-container">
|
<div className="shortcuts-container">
|
||||||
{sortedShortcuts.map((s) => {
|
{sortedShortcuts.map((s) => {
|
||||||
@ -66,9 +67,6 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
|||||||
if (isActive) {
|
if (isActive) {
|
||||||
locationService.setMemoShortcut(undefined);
|
locationService.setMemoShortcut(undefined);
|
||||||
} else {
|
} else {
|
||||||
if (!["/"].includes(locationService.getState().pathname)) {
|
|
||||||
locationService.setPathname("/");
|
|
||||||
}
|
|
||||||
locationService.setMemoShortcut(shortcut.id);
|
locationService.setMemoShortcut(shortcut.id);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -116,30 +114,28 @@ const ShortcutContainer: React.FC<ShortcutContainerProps> = (props: ShortcutCont
|
|||||||
<div className="shortcut-text-container">
|
<div className="shortcut-text-container">
|
||||||
<span className="shortcut-text">{shortcut.title}</span>
|
<span className="shortcut-text">{shortcut.title}</span>
|
||||||
</div>
|
</div>
|
||||||
{userService.isNotVisitor() && (
|
<div className={`btns-container ${userService.isVisitorMode() ? "!hidden" : ""}`}>
|
||||||
<div className="btns-container">
|
<span className="action-btn toggle-btn">
|
||||||
<span className="action-btn toggle-btn">
|
<img className="icon-img" src="/icons/more.svg" />
|
||||||
<img className="icon-img" src="/icons/more.svg" />
|
</span>
|
||||||
</span>
|
<div className="action-btns-wrapper">
|
||||||
<div className="action-btns-wrapper">
|
<div className="action-btns-container">
|
||||||
<div className="action-btns-container">
|
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
||||||
<span className="btn" onClick={handlePinShortcutBtnClick}>
|
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
||||||
{shortcut.rowStatus === "ARCHIVED" ? "Unpin" : "Pin"}
|
</span>
|
||||||
</span>
|
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
||||||
<span className="btn" onClick={handleEditShortcutBtnClick}>
|
Edit
|
||||||
Edit
|
</span>
|
||||||
</span>
|
<span
|
||||||
<span
|
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
||||||
className={`btn delete-btn ${showConfirmDeleteBtn ? "final-confirm" : ""}`}
|
onClick={handleDeleteMemoClick}
|
||||||
onClick={handleDeleteMemoClick}
|
onMouseLeave={handleDeleteBtnMouseLeave}
|
||||||
onMouseLeave={handleDeleteBtnMouseLeave}
|
>
|
||||||
>
|
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
||||||
{showConfirmDeleteBtn ? "Delete!" : "Delete"}
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import * as utils from "../helpers/utils";
|
import * as utils from "../helpers/utils";
|
||||||
import { userService } from "../services";
|
import { userService } from "../services";
|
||||||
|
import Only from "./common/OnlyWhen";
|
||||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||||
import showSettingDialog from "./SettingDialog";
|
import showSettingDialog from "./SettingDialog";
|
||||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||||
@ -49,21 +50,19 @@ const Sidebar: React.FC<Props> = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UsageHeatMap />
|
<UsageHeatMap />
|
||||||
{userService.isNotVisitor() && (
|
<Only when={!userService.isVisitorMode()}>
|
||||||
<>
|
<div className="action-btns-container">
|
||||||
<div className="action-btns-container">
|
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
||||||
<button className="btn action-btn" onClick={() => showDailyReviewDialog()}>
|
<span className="icon">📅</span> Daily Review
|
||||||
<span className="icon">📅</span> Daily Review
|
</button>
|
||||||
</button>
|
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
||||||
<button className="btn action-btn" onClick={handleMyAccountBtnClick}>
|
<span className="icon">⚙️</span> Setting
|
||||||
<span className="icon">⚙️</span> Setting
|
</button>
|
||||||
</button>
|
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
||||||
<button className="btn action-btn" onClick={handleArchivedBtnClick}>
|
<span className="icon">🗂</span> Archived
|
||||||
<span className="icon">🗂</span> Archived
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</Only>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<ShortcutList />
|
<ShortcutList />
|
||||||
<TagList />
|
<TagList />
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -71,7 +71,7 @@ const TagList: React.FC<Props> = () => {
|
|||||||
{tags.map((t, idx) => (
|
{tags.map((t, idx) => (
|
||||||
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
<TagItemContainer key={t.text + "-" + idx} tag={t} tagQuery={query?.tag} />
|
||||||
))}
|
))}
|
||||||
<Only when={userService.isNotVisitor() && tags.length < 5}>
|
<Only when={!userService.isVisitorMode() && tags.length < 5}>
|
||||||
<p className="tag-tip-container">
|
<p className="tag-tip-container">
|
||||||
Enter <span className="code-text">#tag </span> to create a tag
|
Enter <span className="code-text">#tag </span> to create a tag
|
||||||
</p>
|
</p>
|
||||||
@ -97,9 +97,6 @@ const TagItemContainer: React.FC<TagItemContainerProps> = (props: TagItemContain
|
|||||||
locationService.setTagQuery(undefined);
|
locationService.setTagQuery(undefined);
|
||||||
} else {
|
} else {
|
||||||
utils.copyTextToClipboard(`#${tag.text} `);
|
utils.copyTextToClipboard(`#${tag.text} `);
|
||||||
if (!["/"].includes(locationService.getState().pathname)) {
|
|
||||||
locationService.setPathname("/");
|
|
||||||
}
|
|
||||||
locationService.setTagQuery(tag.text);
|
locationService.setTagQuery(tag.text);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { getUserIdFromPath } from "../services/userService";
|
import userService from "../services/userService";
|
||||||
import { locationService } from "../services";
|
import { locationService } from "../services";
|
||||||
import { useAppSelector } from "../store";
|
import { useAppSelector } from "../store";
|
||||||
import toastHelper from "./Toast";
|
import toastHelper from "./Toast";
|
||||||
@ -31,15 +31,18 @@ const UserBanner: React.FC<Props> = () => {
|
|||||||
setUsername(status.owner.name);
|
setUsername(status.owner.name);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
api
|
const currentUserId = userService.getCurrentUserId();
|
||||||
.getUserNameById(Number(getUserIdFromPath()))
|
if (currentUserId) {
|
||||||
.then(({ data }) => {
|
api
|
||||||
const { data: username } = data;
|
.getUserNameById(currentUserId)
|
||||||
setUsername(username);
|
.then(({ data }) => {
|
||||||
})
|
const { data: username } = data;
|
||||||
.catch(() => {
|
setUsername(username);
|
||||||
toastHelper.error("User not found");
|
})
|
||||||
});
|
.catch(() => {
|
||||||
|
toastHelper.error("User not found");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -12,8 +12,8 @@ export function getSystemStatus() {
|
|||||||
return axios.get<ResponseObject<SystemStatus>>("/api/status");
|
return axios.get<ResponseObject<SystemStatus>>("/api/status");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function login(email: string, password: string) {
|
export function signin(email: string, password: string) {
|
||||||
return axios.post<ResponseObject<User>>("/api/auth/login", {
|
return axios.post<ResponseObject<User>>("/api/auth/signin", {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
});
|
});
|
||||||
@ -52,12 +52,15 @@ export function patchUser(userPatch: UserPatch) {
|
|||||||
return axios.patch<ResponseObject<User>>("/api/user/me", userPatch);
|
return axios.patch<ResponseObject<User>>("/api/user/me", userPatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMemoList(userId?: number) {
|
export function getMemoList(memoFind?: MemoFind) {
|
||||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo${userId ? "?userID=" + userId : ""}`);
|
const queryList = [];
|
||||||
}
|
if (memoFind?.creatorId) {
|
||||||
|
queryList.push(`creatorId=${memoFind.creatorId}`);
|
||||||
export function getArchivedMemoList(userId?: number) {
|
}
|
||||||
return axios.get<ResponseObject<Memo[]>>(`/api/memo?rowStatus=ARCHIVED${userId ? "&userID=" + userId : ""}`);
|
if (memoFind?.rowStatus) {
|
||||||
|
queryList.push(`rowStatus=${memoFind.rowStatus}`);
|
||||||
|
}
|
||||||
|
return axios.get<ResponseObject<Memo[]>>(`/api/memo?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createMemo(memoCreate: MemoCreate) {
|
export function createMemo(memoCreate: MemoCreate) {
|
||||||
@ -84,8 +87,12 @@ export function deleteMemo(memoId: MemoId) {
|
|||||||
return axios.delete(`/api/memo/${memoId}`);
|
return axios.delete(`/api/memo/${memoId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShortcutList(userId?: number) {
|
export function getShortcutList(shortcutFind: ShortcutFind) {
|
||||||
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut${userId ? "?userID=" + userId : ""}`);
|
const queryList = [];
|
||||||
|
if (shortcutFind?.creatorId) {
|
||||||
|
queryList.push(`creatorId=${shortcutFind.creatorId}`);
|
||||||
|
}
|
||||||
|
return axios.get<ResponseObject<Shortcut[]>>(`/api/shortcut?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createShortcut(shortcutCreate: ShortcutCreate) {
|
export function createShortcut(shortcutCreate: ShortcutCreate) {
|
||||||
@ -104,6 +111,10 @@ export function uploadFile(formData: FormData) {
|
|||||||
return axios.post<ResponseObject<Resource>>("/api/resource", formData);
|
return axios.post<ResponseObject<Resource>>("/api/resource", formData);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getTagList(userId?: number) {
|
export function getTagList(tagFind?: TagFind) {
|
||||||
return axios.get<ResponseObject<string[]>>(`/api/tag${userId ? "?userID=" + userId : ""}`);
|
const queryList = [];
|
||||||
|
if (tagFind?.creatorId) {
|
||||||
|
queryList.push(`creatorId=${tagFind.creatorId}`);
|
||||||
|
}
|
||||||
|
return axios.get<ResponseObject<string[]>>(`/api/tag?${queryList.join("&")}`);
|
||||||
}
|
}
|
||||||
|
@ -126,38 +126,6 @@ export function throttle(fn: FunctionType, delay: number) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function transformObjectToParamsString(object: KVObject): string {
|
|
||||||
const params = [];
|
|
||||||
const keys = Object.keys(object).sort();
|
|
||||||
|
|
||||||
for (const key of keys) {
|
|
||||||
const val = object[key];
|
|
||||||
if (val) {
|
|
||||||
if (typeof val === "object") {
|
|
||||||
params.push(...transformObjectToParamsString(val).split("&"));
|
|
||||||
} else {
|
|
||||||
params.push(`${key}=${val}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return params.join("&");
|
|
||||||
}
|
|
||||||
|
|
||||||
export function transformParamsStringToObject(paramsString: string): KVObject {
|
|
||||||
const object: KVObject = {};
|
|
||||||
const params = paramsString.split("&");
|
|
||||||
|
|
||||||
for (const p of params) {
|
|
||||||
const [key, val] = p.split("=");
|
|
||||||
if (key && val) {
|
|
||||||
object[key] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterObjectNullKeys(object: KVObject): KVObject {
|
export function filterObjectNullKeys(object: KVObject): KVObject {
|
||||||
if (!object) {
|
if (!object) {
|
||||||
return {};
|
return {};
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { locationService, userService } from "../services";
|
import { userService } from "../services";
|
||||||
import Sidebar from "../components/Sidebar";
|
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
|
import Only from "../components/common/OnlyWhen";
|
||||||
|
import Sidebar from "../components/Sidebar";
|
||||||
import MemosHeader from "../components/MemosHeader";
|
import MemosHeader from "../components/MemosHeader";
|
||||||
import MemoEditor from "../components/MemoEditor";
|
import MemoEditor from "../components/MemoEditor";
|
||||||
import MemoFilter from "../components/MemoFilter";
|
import MemoFilter from "../components/MemoFilter";
|
||||||
@ -12,25 +13,14 @@ function Home() {
|
|||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (window.location.pathname !== locationService.getState().pathname) {
|
userService
|
||||||
locationService.replaceHistory("/");
|
.doSignIn()
|
||||||
}
|
.catch(() => {
|
||||||
const { user } = userService.getState();
|
// do nth
|
||||||
if (!user) {
|
})
|
||||||
userService
|
.finally(() => {
|
||||||
.doSignIn()
|
loadingState.setFinish();
|
||||||
.catch(() => {
|
});
|
||||||
// do nth
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
if (userService.getState().user && locationService.getState().pathname !== "/") {
|
|
||||||
locationService.replaceHistory("/");
|
|
||||||
}
|
|
||||||
loadingState.setFinish();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
loadingState.setFinish();
|
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -41,7 +31,9 @@ function Home() {
|
|||||||
<main className="memos-wrapper">
|
<main className="memos-wrapper">
|
||||||
<div className="memos-editor-wrapper">
|
<div className="memos-editor-wrapper">
|
||||||
<MemosHeader />
|
<MemosHeader />
|
||||||
{userService.isNotVisitor() && <MemoEditor />}
|
<Only when={!userService.isVisitorMode()}>
|
||||||
|
<MemoEditor />
|
||||||
|
</Only>
|
||||||
<MemoFilter />
|
<MemoFilter />
|
||||||
</div>
|
</div>
|
||||||
<MemoList />
|
<MemoList />
|
||||||
|
@ -63,7 +63,7 @@ const Signin: React.FC<Props> = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
actionBtnLoadingState.setLoading();
|
actionBtnLoadingState.setLoading();
|
||||||
await api.login(email, password);
|
await api.signin(email, password);
|
||||||
const user = await userService.doSignIn();
|
const user = await userService.doSignIn();
|
||||||
if (user) {
|
if (user) {
|
||||||
locationService.replaceHistory("/");
|
locationService.replaceHistory("/");
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import * as utils from "../helpers/utils";
|
import { stringify } from "qs";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import { setQuery, setPathname, Query } from "../store/modules/location";
|
import { setQuery, setPathname, Query } from "../store/modules/location";
|
||||||
|
|
||||||
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
const updateLocationUrl = (method: "replace" | "push" = "replace") => {
|
||||||
const { query, pathname, hash } = store.getState().location;
|
const { query, pathname, hash } = store.getState().location;
|
||||||
let queryString = utils.transformObjectToParamsString(query ?? {});
|
let queryString = stringify(query);
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
queryString = "?" + queryString;
|
queryString = "?" + queryString;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import { createMemo, patchMemo, setMemos, setTags } from "../store/modules/memo";
|
import { createMemo, patchMemo, setMemos, setTags } from "../store/modules/memo";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
import { getUserIdFromPath } from "./userService";
|
import userService from "./userService";
|
||||||
|
|
||||||
const convertResponseModelMemo = (memo: Memo): Memo => {
|
const convertResponseModelMemo = (memo: Memo): Memo => {
|
||||||
return {
|
return {
|
||||||
@ -17,7 +17,10 @@ const memoService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchAllMemos: async () => {
|
fetchAllMemos: async () => {
|
||||||
const { data } = (await api.getMemoList(getUserIdFromPath())).data;
|
const memoFind: MemoFind = {
|
||||||
|
creatorId: userService.getCurrentUserId(),
|
||||||
|
};
|
||||||
|
const { data } = (await api.getMemoList(memoFind)).data;
|
||||||
const memos = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m));
|
const memos = data.filter((m) => m.rowStatus !== "ARCHIVED").map((m) => convertResponseModelMemo(m));
|
||||||
store.dispatch(setMemos(memos));
|
store.dispatch(setMemos(memos));
|
||||||
|
|
||||||
@ -25,7 +28,11 @@ const memoService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchArchivedMemos: async () => {
|
fetchArchivedMemos: async () => {
|
||||||
const { data } = (await api.getArchivedMemoList(getUserIdFromPath())).data;
|
const memoFind: MemoFind = {
|
||||||
|
creatorId: userService.getCurrentUserId(),
|
||||||
|
rowStatus: "ARCHIVED",
|
||||||
|
};
|
||||||
|
const { data } = (await api.getMemoList(memoFind)).data;
|
||||||
const archivedMemos = data.map((m) => {
|
const archivedMemos = data.map((m) => {
|
||||||
return convertResponseModelMemo(m);
|
return convertResponseModelMemo(m);
|
||||||
});
|
});
|
||||||
@ -43,7 +50,10 @@ const memoService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateTagsState: async () => {
|
updateTagsState: async () => {
|
||||||
const { data } = (await api.getTagList(getUserIdFromPath())).data;
|
const tagFind: TagFind = {
|
||||||
|
creatorId: userService.getCurrentUserId(),
|
||||||
|
};
|
||||||
|
const { data } = (await api.getTagList(tagFind)).data;
|
||||||
store.dispatch(setTags(data));
|
store.dispatch(setTags(data));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import store from "../store/";
|
import store from "../store/";
|
||||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../store/modules/shortcut";
|
||||||
import { getUserIdFromPath } from "./userService";
|
import userService from "./userService";
|
||||||
|
|
||||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
||||||
return {
|
return {
|
||||||
@ -17,7 +17,10 @@ const shortcutService = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
getMyAllShortcuts: async () => {
|
getMyAllShortcuts: async () => {
|
||||||
const { data } = (await api.getShortcutList(getUserIdFromPath())).data;
|
const shortcutFind: ShortcutFind = {
|
||||||
|
creatorId: userService.getCurrentUserId(),
|
||||||
|
};
|
||||||
|
const { data } = (await api.getShortcutList(shortcutFind)).data;
|
||||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
||||||
store.dispatch(setShortcuts(shortcuts));
|
store.dispatch(setShortcuts(shortcuts));
|
||||||
},
|
},
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { isUndefined } from "lodash-es";
|
||||||
import { locationService } from ".";
|
import { locationService } from ".";
|
||||||
import * as api from "../helpers/api";
|
import * as api from "../helpers/api";
|
||||||
import store from "../store";
|
import store from "../store";
|
||||||
@ -11,18 +12,26 @@ const convertResponseModelUser = (user: User): User => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserIdFromPath = () => {
|
|
||||||
const path = locationService.getState().pathname.slice(3);
|
|
||||||
return !isNaN(Number(path)) ? Number(path) : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const userService = {
|
const userService = {
|
||||||
getState: () => {
|
getState: () => {
|
||||||
return store.getState().user;
|
return store.getState().user;
|
||||||
},
|
},
|
||||||
|
|
||||||
isNotVisitor: () => {
|
isVisitorMode: () => {
|
||||||
return store.getState().user.user !== undefined;
|
return !isUndefined(userService.getUserIdFromPath());
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentUserId: () => {
|
||||||
|
return userService.getUserIdFromPath() ?? store.getState().user.user?.id;
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserIdFromPath: () => {
|
||||||
|
const userIdRegex = /^\/u\/(\d+).*/;
|
||||||
|
const result = locationService.getState().pathname.match(userIdRegex);
|
||||||
|
if (result && result.length === 2) {
|
||||||
|
return Number(result[1]);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
|
|
||||||
doSignIn: async () => {
|
doSignIn: async () => {
|
||||||
|
@ -20,7 +20,8 @@ interface State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getValidPathname = (pathname: string): string => {
|
const getValidPathname = (pathname: string): string => {
|
||||||
if (["/", "/signin"].includes(pathname) || pathname.match(/^\/u\/(\d+)/)) {
|
const userPageUrlRegex = /^\/u\/\d+.*/;
|
||||||
|
if (["/", "/signin"].includes(pathname) || userPageUrlRegex.test(pathname)) {
|
||||||
return pathname;
|
return pathname;
|
||||||
} else {
|
} else {
|
||||||
return "/";
|
return "/";
|
||||||
|
5
web/src/types/modules/memo.d.ts
vendored
5
web/src/types/modules/memo.d.ts
vendored
@ -22,3 +22,8 @@ interface MemoPatch {
|
|||||||
content?: string;
|
content?: string;
|
||||||
rowStatus?: RowStatus;
|
rowStatus?: RowStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MemoFind {
|
||||||
|
creatorId?: UserId;
|
||||||
|
rowStatus?: RowStatus;
|
||||||
|
}
|
||||||
|
5
web/src/types/modules/shortcut.d.ts
vendored
5
web/src/types/modules/shortcut.d.ts
vendored
@ -3,6 +3,7 @@ type ShortcutId = number;
|
|||||||
interface Shortcut {
|
interface Shortcut {
|
||||||
id: ShortcutId;
|
id: ShortcutId;
|
||||||
|
|
||||||
|
creatorId: UserId;
|
||||||
rowStatus: RowStatus;
|
rowStatus: RowStatus;
|
||||||
createdTs: TimeStamp;
|
createdTs: TimeStamp;
|
||||||
updatedTs: TimeStamp;
|
updatedTs: TimeStamp;
|
||||||
@ -22,3 +23,7 @@ interface ShortcutPatch {
|
|||||||
payload?: string;
|
payload?: string;
|
||||||
rowStatus?: RowStatus;
|
rowStatus?: RowStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ShortcutFind {
|
||||||
|
creatorId?: UserId;
|
||||||
|
}
|
||||||
|
3
web/src/types/modules/tag.d.ts
vendored
Normal file
3
web/src/types/modules/tag.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
interface TagFind {
|
||||||
|
creatorId?: UserId;
|
||||||
|
}
|
@ -358,6 +358,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf"
|
||||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||||
|
|
||||||
|
"@types/qs@^6.9.7":
|
||||||
|
version "6.9.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
|
||||||
|
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
|
||||||
|
|
||||||
"@types/react-dom@^18.0.4":
|
"@types/react-dom@^18.0.4":
|
||||||
version "18.0.4"
|
version "18.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.4.tgz#dcbcadb277bcf6c411ceff70069424c57797d375"
|
||||||
@ -1988,6 +1993,13 @@ punycode@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
|
||||||
|
|
||||||
|
qs@^6.11.0:
|
||||||
|
version "6.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||||
|
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
queue-microtask@^1.2.2:
|
queue-microtask@^1.2.2:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
|
||||||
|
Reference in New Issue
Block a user