diff --git a/api/memo.go b/api/memo.go index b34403d7..cc76418d 100644 --- a/api/memo.go +++ b/api/memo.go @@ -1,5 +1,8 @@ package api +// MaxContentLength means the max memo content bytes is 1MB. +const MaxContentLength = 1 << 30 + // Visibility is the type of a visibility. type Visibility string @@ -37,7 +40,6 @@ type Memo struct { Content string `json:"content"` Visibility Visibility `json:"visibility"` Pinned bool `json:"pinned"` - DisplayTs int64 `json:"displayTs"` // Related fields Creator *User `json:"creator"` @@ -86,8 +88,8 @@ type MemoFind struct { VisibilityList []Visibility // Pagination - Limit int - Offset int + Limit *int + Offset *int } type MemoDelete struct { diff --git a/api/user_setting.go b/api/user_setting.go index c6c8b477..a396261f 100644 --- a/api/user_setting.go +++ b/api/user_setting.go @@ -16,8 +16,6 @@ const ( UserSettingAppearanceKey UserSettingKey = "appearance" // UserSettingMemoVisibilityKey is the key type for user preference memo default visibility. UserSettingMemoVisibilityKey UserSettingKey = "memoVisibility" - // UserSettingMemoDisplayTsOptionKey is the key type for memo display ts option. - UserSettingMemoDisplayTsOptionKey UserSettingKey = "memoDisplayTsOption" ) // String returns the string format of UserSettingKey type. @@ -29,17 +27,14 @@ func (key UserSettingKey) String() string { return "appearance" case UserSettingMemoVisibilityKey: return "memoVisibility" - case UserSettingMemoDisplayTsOptionKey: - return "memoDisplayTsOption" } return "" } var ( - UserSettingLocaleValue = []string{"en", "zh", "vi", "fr", "nl", "sv", "de", "es", "uk", "ru", "it", "hant", "ko"} - UserSettingAppearanceValue = []string{"system", "light", "dark"} - UserSettingMemoVisibilityValue = []Visibility{Private, Protected, Public} - UserSettingMemoDisplayTsOptionKeyValue = []string{"created_ts", "updated_ts"} + UserSettingLocaleValue = []string{"en", "zh", "vi", "fr", "nl", "sv", "de", "es", "uk", "ru", "it", "hant", "ko"} + UserSettingAppearanceValue = []string{"system", "light", "dark"} + UserSettingMemoVisibilityValue = []Visibility{Private, Protected, Public} ) type UserSetting struct { @@ -83,15 +78,6 @@ func (upsert UserSettingUpsert) Validate() error { if !slices.Contains(UserSettingMemoVisibilityValue, memoVisibilityValue) { return fmt.Errorf("invalid user setting memo visibility value") } - } else if upsert.Key == UserSettingMemoDisplayTsOptionKey { - memoDisplayTsOption := "created_ts" - err := json.Unmarshal([]byte(upsert.Value), &memoDisplayTsOption) - if err != nil { - return fmt.Errorf("failed to unmarshal user setting memo display ts option") - } - if !slices.Contains(UserSettingMemoDisplayTsOptionKeyValue, memoDisplayTsOption) { - return fmt.Errorf("invalid user setting memo display ts option value") - } } else { return fmt.Errorf("invalid user setting key") } diff --git a/server/memo.go b/server/memo.go index 78e7cafd..0b0d2888 100644 --- a/server/memo.go +++ b/server/memo.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "sort" "strconv" "strings" "time" @@ -71,6 +70,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { } } + if len(memoCreate.Content) > api.MaxContentLength { + return echo.NewHTTPError(http.StatusBadRequest, "Content size overflow, up to 1MB").SetInternal(err) + } + memoCreate.CreatorID = userID memo, err := s.Store.CreateMemo(ctx, memoCreate) if err != nil { @@ -127,6 +130,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch memo request").SetInternal(err) } + if memoPatch.Content != nil && len(*memoPatch.Content) > api.MaxContentLength { + return echo.NewHTTPError(http.StatusBadRequest, "Content size overflow, up to 1MB").SetInternal(err) + } + memo, err = s.Store.PatchMemo(ctx, memoPatch) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to patch memo").SetInternal(err) @@ -192,10 +199,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { memoFind.VisibilityList = visibilityList } if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil { - memoFind.Limit = limit + memoFind.Limit = &limit } if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil { - memoFind.Offset = offset + memoFind.Offset = &offset } list, err := s.Store.FindMemoList(ctx, memoFind) @@ -214,20 +221,9 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { } } - sort.Slice(pinnedMemoList, func(i, j int) bool { - return pinnedMemoList[i].DisplayTs > pinnedMemoList[j].DisplayTs - }) - sort.Slice(unpinnedMemoList, func(i, j int) bool { - return unpinnedMemoList[i].DisplayTs > unpinnedMemoList[j].DisplayTs - }) - memoList := []*api.Memo{} memoList = append(memoList, pinnedMemoList...) memoList = append(memoList, unpinnedMemoList...) - - if memoFind.Limit != 0 { - memoList = memoList[memoFind.Offset:common.Min(len(memoList), memoFind.Offset+memoFind.Limit)] - } return c.JSON(http.StatusOK, composeResponse(memoList)) }) @@ -399,11 +395,11 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err) } - displayTsList := []int64{} + createdTsList := []int64{} for _, memo := range list { - displayTsList = append(displayTsList, memo.DisplayTs) + createdTsList = append(createdTsList, memo.CreatedTs) } - return c.JSON(http.StatusOK, composeResponse(displayTsList)) + return c.JSON(http.StatusOK, composeResponse(createdTsList)) }) g.GET("/memo/all", func(c echo.Context) error { @@ -436,10 +432,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { memoFind.VisibilityList = visibilityList } if limit, err := strconv.Atoi(c.QueryParam("limit")); err == nil { - memoFind.Limit = limit + memoFind.Limit = &limit } if offset, err := strconv.Atoi(c.QueryParam("offset")); err == nil { - memoFind.Offset = offset + memoFind.Offset = &offset } // Only fetch normal status memos. @@ -450,14 +446,6 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch all memo list").SetInternal(err) } - - sort.Slice(list, func(i, j int) bool { - return list[i].DisplayTs > list[j].DisplayTs - }) - - if memoFind.Limit != 0 { - list = list[memoFind.Offset:common.Min(len(list), memoFind.Offset+memoFind.Limit)] - } return c.JSON(http.StatusOK, composeResponse(list)) }) diff --git a/store/memo.go b/store/memo.go index 469491e6..328dd0e0 100644 --- a/store/memo.go +++ b/store/memo.go @@ -3,7 +3,6 @@ package store import ( "context" "database/sql" - "encoding/json" "fmt" "strings" @@ -43,7 +42,6 @@ func (raw *memoRaw) toMemo() *api.Memo { // Domain specific fields Content: raw.Content, Visibility: raw.Visibility, - DisplayTs: raw.CreatedTs, Pinned: raw.Pinned, } } @@ -56,25 +54,6 @@ func (s *Store) ComposeMemo(ctx context.Context, memo *api.Memo) (*api.Memo, err return nil, err } - memoDisplayTsOptionKey := api.UserSettingMemoDisplayTsOptionKey - memoDisplayTsOptionSetting, err := s.FindUserSetting(ctx, &api.UserSettingFind{ - UserID: memo.CreatorID, - Key: &memoDisplayTsOptionKey, - }) - if err != nil { - return nil, err - } - memoDisplayTsOptionValue := "created_ts" - if memoDisplayTsOptionSetting != nil { - err = json.Unmarshal([]byte(memoDisplayTsOptionSetting.Value), &memoDisplayTsOptionValue) - if err != nil { - return nil, fmt.Errorf("failed to unmarshal user setting memo display ts option value") - } - } - if memoDisplayTsOptionValue == "updated_ts" { - memo.DisplayTs = memo.UpdatedTs - } - return memo, nil } @@ -329,6 +308,13 @@ func findMemoRawList(ctx context.Context, tx *sql.Tx, find *api.MemoFind) ([]*me WHERE ` + strings.Join(where, " AND ") + ` ORDER BY memo.created_ts DESC ` + if find.Limit != nil { + query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit) + if find.Offset != nil { + query = fmt.Sprintf("%s OFFSET %d", query, *find.Offset) + } + } + rows, err := tx.QueryContext(ctx, query, args...) if err != nil { return nil, FormatError(err) diff --git a/web/src/components/DailyMemo.tsx b/web/src/components/DailyMemo.tsx index 00125945..1555b766 100644 --- a/web/src/components/DailyMemo.tsx +++ b/web/src/components/DailyMemo.tsx @@ -9,7 +9,7 @@ interface Props { const DailyMemo: React.FC = (props: Props) => { const { memo } = props; - const displayTimeStr = utils.getTimeString(memo.displayTs); + const createdTimeStr = utils.getTimeString(memo.createdTs); const displayConfig: DisplayConfig = { enableExpand: false, }; @@ -17,7 +17,7 @@ const DailyMemo: React.FC = (props: Props) => { return (
- {displayTimeStr} + {createdTimeStr}
diff --git a/web/src/components/DailyReviewDialog.tsx b/web/src/components/DailyReviewDialog.tsx index 8ababb87..b8acd07d 100644 --- a/web/src/components/DailyReviewDialog.tsx +++ b/web/src/components/DailyReviewDialog.tsx @@ -31,10 +31,10 @@ const DailyReviewDialog: React.FC = (props: Props) => { .filter( (m) => m.rowStatus === "NORMAL" && - utils.getTimeStampByDate(m.displayTs) >= currentDateStamp && - utils.getTimeStampByDate(m.displayTs) < currentDateStamp + DAILY_TIMESTAMP + utils.getTimeStampByDate(m.createdTs) >= currentDateStamp && + utils.getTimeStampByDate(m.createdTs) < currentDateStamp + DAILY_TIMESTAMP ) - .sort((a, b) => utils.getTimeStampByDate(a.displayTs) - utils.getTimeStampByDate(b.displayTs)); + .sort((a, b) => utils.getTimeStampByDate(a.createdTs) - utils.getTimeStampByDate(b.createdTs)); const handleShareBtnClick = () => { if (!memosElRef.current) { diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index c256a965..b75388dd 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -1,3 +1,4 @@ +import { Tooltip } from "@mui/joy"; import copy from "copy-to-clipboard"; import dayjs from "dayjs"; import { memo, useEffect, useRef, useState } from "react"; @@ -34,15 +35,16 @@ const Memo: React.FC = (props: Props) => { const locationStore = useLocationStore(); const userStore = useUserStore(); const memoStore = useMemoStore(); - const [displayTimeStr, setDisplayTimeStr] = useState(getFormatedMemoTimeStr(memo.displayTs, i18n.language)); + const [createdTimeStr, setCreatedTimeStr] = useState(getFormatedMemoTimeStr(memo.createdTs, i18n.language)); const memoContainerRef = useRef(null); const isVisitorMode = userStore.isVisitorMode(); + const updatedTimeStr = getFormatedMemoTimeStr(memo.updatedTs, i18n.language); useEffect(() => { let intervalFlag: any = -1; - if (Date.now() - memo.displayTs < 1000 * 60 * 60 * 24) { + if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) { intervalFlag = setInterval(() => { - setDisplayTimeStr(getFormatedMemoTimeStr(memo.displayTs, i18n.language)); + setCreatedTimeStr(getFormatedMemoTimeStr(memo.createdTs, i18n.language)); }, 1000 * 1); } @@ -166,7 +168,7 @@ const Memo: React.FC = (props: Props) => { editorStore.setEditMemoWithId(memo.id); }; - const handleMemoDisplayTimeClick = () => { + const handleMemoCreatedTimeClick = () => { showChangeMemoCreatedTsDialog(memo.id); }; @@ -184,9 +186,11 @@ const Memo: React.FC = (props: Props) => { {memo.pinned &&
}
- - {displayTimeStr} - + + + {createdTimeStr} + + {memo.visibility !== "PRIVATE" && !isVisitorMode && ( { const { t } = useTranslation(); - const userStore = useUserStore(); const memoStore = useMemoStore(); const shortcutStore = useShortcutStore(); const locationStore = useLocationStore(); const query = locationStore.state.query; - const memoDisplayTsOption = userStore.state.user?.setting.memoDisplayTsOption; const { memos, isFetching } = memoStore.state; const [isComplete, setIsComplete] = useState(false); @@ -54,7 +52,7 @@ const MemoList = () => { if ( duration && duration.from < duration.to && - (utils.getTimeStampByDate(memo.displayTs) < duration.from || utils.getTimeStampByDate(memo.displayTs) > duration.to) + (utils.getTimeStampByDate(memo.createdTs) < duration.from || utils.getTimeStampByDate(memo.createdTs) > duration.to) ) { shouldShow = false; } @@ -79,7 +77,7 @@ const MemoList = () => { const pinnedMemos = shownMemos.filter((m) => m.pinned); const unpinnedMemos = shownMemos.filter((m) => !m.pinned); const memoSort = (mi: Memo, mj: Memo) => { - return mj.displayTs - mi.displayTs; + return mj.createdTs - mi.createdTs; }; pinnedMemos.sort(memoSort); unpinnedMemos.sort(memoSort); @@ -99,7 +97,7 @@ const MemoList = () => { console.error(error); toastHelper.error(error.response.data.message); }); - }, [memoDisplayTsOption]); + }, []); useEffect(() => { const pageWrapper = document.body.querySelector(".page-wrapper"); @@ -134,7 +132,7 @@ const MemoList = () => { return (
{sortedMemos.map((memo) => ( - + ))} {isFetching ? (
diff --git a/web/src/components/Settings/PreferencesSection.tsx b/web/src/components/Settings/PreferencesSection.tsx index 07654528..28ff71e6 100644 --- a/web/src/components/Settings/PreferencesSection.tsx +++ b/web/src/components/Settings/PreferencesSection.tsx @@ -2,7 +2,7 @@ import { Select, Switch, Option } from "@mui/joy"; import React from "react"; import { useTranslation } from "react-i18next"; import { useGlobalStore, useUserStore } from "../../store/module"; -import { VISIBILITY_SELECTOR_ITEMS, MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS } from "../../helpers/consts"; +import { VISIBILITY_SELECTOR_ITEMS } from "../../helpers/consts"; import AppearanceSelect from "../AppearanceSelect"; import LocaleSelect from "../LocaleSelect"; import "../../less/settings/preferences-section.less"; @@ -20,13 +20,6 @@ const PreferencesSection = () => { }; }); - const memoDisplayTsOptionSelectorItems = MEMO_DISPLAY_TS_OPTION_SELECTOR_ITEMS.map((item) => { - return { - value: item.value, - text: t(`setting.preference-section.${item.value}`), - }; - }); - const handleLocaleSelectChange = async (locale: Locale) => { await userStore.upsertUserSetting("locale", locale); globalStore.setLocale(locale); @@ -41,10 +34,6 @@ const PreferencesSection = () => { await userStore.upsertUserSetting("memoVisibility", value); }; - const handleMemoDisplayTsOptionChanged = async (value: string) => { - await userStore.upsertUserSetting("memoDisplayTsOption", value); - }; - const handleIsFoldingEnabledChanged = (event: React.ChangeEvent) => { userStore.upsertLocalSetting({ ...localSetting, enableFoldMemo: event.target.checked }); }; @@ -83,24 +72,6 @@ const PreferencesSection = () => { ))}
-