diff --git a/api/memo.go b/api/memo.go index f627b323..0fff6cd7 100644 --- a/api/memo.go +++ b/api/memo.go @@ -37,6 +37,7 @@ 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"` @@ -59,7 +60,8 @@ type MemoPatch struct { ID int // Standard fields - CreatedTs *int64 `json:"createdTs"` + CreatedTs *int64 `json:"createdTs"` + UpdatedTs *int64 RowStatus *RowStatus `json:"rowStatus"` // Domain specific fields diff --git a/api/user_setting.go b/api/user_setting.go index c1858491..78f794a1 100644 --- a/api/user_setting.go +++ b/api/user_setting.go @@ -16,7 +16,7 @@ const ( UserSettingEditorFontStyleKey UserSettingKey = "editorFontStyle" // UserSettingEditorFontStyleKey is the key type for mobile editor style. UserSettingMobileEditorStyleKey UserSettingKey = "mobileEditorStyle" - // UserSettingMemoSortOptionKey is the key type for sort time option. + // UserSettingMemoSortOptionKey is the key type for memo sort option. UserSettingMemoSortOptionKey UserSettingKey = "memoSortOption" ) @@ -42,7 +42,7 @@ var ( UserSettingMemoVisibilityValue = []Visibility{Privite, Protected, Public} UserSettingEditorFontStyleValue = []string{"normal", "mono"} UserSettingMobileEditorStyleValue = []string{"normal", "float"} - UserSettingSortTimeOptionKeyValue = []string{"created_ts", "updated_ts"} + UserSettingMemoSortOptionKeyValue = []string{"created_ts", "updated_ts"} ) type UserSetting struct { @@ -135,7 +135,7 @@ func (upsert UserSettingUpsert) Validate() error { } invalid := true - for _, value := range UserSettingSortTimeOptionKeyValue { + for _, value := range UserSettingMemoSortOptionKeyValue { if memoSortOption == value { invalid = false break diff --git a/server/memo.go b/server/memo.go index 2e990215..ebafa03b 100644 --- a/server/memo.go +++ b/server/memo.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "sort" "strconv" "strings" "time" @@ -101,8 +102,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find memo").SetInternal(err) } + currentTs := time.Now().Unix() memoPatch := &api.MemoPatch{ - ID: memoID, + ID: memoID, + UpdatedTs: ¤tTs, } if err := json.NewDecoder(c.Request().Body).Decode(memoPatch); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted patch memo request").SetInternal(err) @@ -175,6 +178,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to fetch memo list").SetInternal(err) } + sort.Slice(list, func(i, j int) bool { + return list[i].DisplayTs > list[j].DisplayTs + }) + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(list)); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode memo list response").SetInternal(err) @@ -227,6 +234,10 @@ func (s *Server) registerMemoRoutes(g *echo.Group) { 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 + }) + c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8) if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(list)); err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode all memo list response").SetInternal(err) diff --git a/store/memo.go b/store/memo.go index d65e9f00..b6680fcb 100644 --- a/store/memo.go +++ b/store/memo.go @@ -3,6 +3,7 @@ package store import ( "context" "database/sql" + "encoding/json" "fmt" "strings" @@ -41,6 +42,7 @@ func (raw *memoRaw) toMemo() *api.Memo { // Domain specific fields Content: raw.Content, Visibility: raw.Visibility, + DisplayTs: raw.CreatedTs, } } @@ -62,6 +64,25 @@ func (s *Store) ComposeMemo(ctx context.Context, memo *api.Memo) (*api.Memo, err return nil, err } + memoSortOptionKey := api.UserSettingMemoSortOptionKey + memoSortOption, err := s.FindUserSetting(ctx, &api.UserSettingFind{ + UserID: memo.CreatorID, + Key: &memoSortOptionKey, + }) + if err != nil { + return nil, err + } + memoSortOptionValue := "created_ts" + if memoSortOption != nil { + err = json.Unmarshal([]byte(memoSortOption.Value), &memoSortOptionValue) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal user setting memo sort option value") + } + } + if memoSortOptionValue == "updated_ts" { + memo.DisplayTs = memo.UpdatedTs + } + return memo, nil } @@ -244,6 +265,9 @@ func patchMemoRaw(ctx context.Context, tx *sql.Tx, patch *api.MemoPatch) (*memoR if v := patch.CreatedTs; v != nil { set, args = append(set, "created_ts = ?"), append(args, *v) } + if v := patch.UpdatedTs; v != nil { + set, args = append(set, "updated_ts = ?"), append(args, *v) + } if v := patch.RowStatus; v != nil { set, args = append(set, "row_status = ?"), append(args, *v) } diff --git a/web/src/components/Memo.tsx b/web/src/components/Memo.tsx index 16659fe9..8aeb13a1 100644 --- a/web/src/components/Memo.tsx +++ b/web/src/components/Memo.tsx @@ -7,7 +7,6 @@ import { useNavigate } from "react-router-dom"; import "dayjs/locale/zh"; import { UNKNOWN_ID } from "../helpers/consts"; import { editorStateService, locationService, memoService, userService } from "../services"; -import { useAppSelector } from "../store"; import Icon from "./Icon"; import toastHelper from "./Toast"; import MemoContent from "./MemoContent"; @@ -33,20 +32,17 @@ export const getFormatedMemoTimeStr = (time: number, locale = "en"): string => { const Memo: React.FC = (props: Props) => { const memo = props.memo; - const user = useAppSelector((state) => state.user.user); const { t, i18n } = useTranslation(); const navigate = useNavigate(); - const [createdAtStr, setCreatedAtStr] = useState(getFormatedMemoTimeStr(memo.createdTs, i18n.language)); - const [updatedAtStr, setUpdatedAtStr] = useState(getFormatedMemoTimeStr(memo.updatedTs, i18n.language)); + const [displayTimeStr, setDisplayTimeStr] = useState(getFormatedMemoTimeStr(memo.displayTs, i18n.language)); const memoContainerRef = useRef(null); const isVisitorMode = userService.isVisitorMode(); useEffect(() => { let intervalFlag: any = -1; - if (Date.now() - memo.createdTs < 1000 * 60 * 60 * 24) { + if (Date.now() - memo.displayTs < 1000 * 60 * 60 * 24) { intervalFlag = setInterval(() => { - setCreatedAtStr(getFormatedMemoTimeStr(memo.createdTs, i18n.language)); - setUpdatedAtStr(getFormatedMemoTimeStr(memo.updatedTs, i18n.language)); + setDisplayTimeStr(getFormatedMemoTimeStr(memo.displayTs, i18n.language)); }, 1000 * 1); } @@ -186,13 +182,12 @@ const Memo: React.FC = (props: Props) => { editorStateService.setEditMemoWithId(memo.id); }; - const timeStr = user?.setting.memoSortOption === "created_ts" ? createdAtStr : `${t("common.update-on")} ${updatedAtStr}`; return (
- {timeStr} + {displayTimeStr} {memo.visibility !== "PRIVATE" && !isVisitorMode && ( {memo.visibility} diff --git a/web/src/components/MemoEditor.tsx b/web/src/components/MemoEditor.tsx index 3786f635..d4c9e99d 100644 --- a/web/src/components/MemoEditor.tsx +++ b/web/src/components/MemoEditor.tsx @@ -174,7 +174,6 @@ const MemoEditor: React.FC = () => { }); locationService.clearQuery(); } - locationService.setUpdatedFlag(); } catch (error: any) { console.error(error); toastHelper.error(error.response.data.message); diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index 83c384bf..1c254dd1 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -12,8 +12,7 @@ import "../less/memo-list.less"; const MemoList = () => { const { t } = useTranslation(); const query = useAppSelector((state) => state.location.query); - const updatedTime = useAppSelector((state) => state.location.updatedTime); - const user = useAppSelector((state) => state.user.user); + const memoSortOption = useAppSelector((state) => state.user.user?.setting.memoSortOption); const { memos, isFetching } = useAppSelector((state) => state.memo); const { tag: tagQuery, duration, type: memoType, text: textQuery, shortcutId } = query ?? {}; @@ -72,8 +71,8 @@ const MemoList = () => { const pinnedMemos = shownMemos.filter((m) => m.pinned); const unpinnedMemos = shownMemos.filter((m) => !m.pinned); - const memoSorting = (m1: Memo, m2: Memo) => { - return user?.setting.memoSortOption === "created_ts" ? m2.createdTs - m1.createdTs : m2.updatedTs - m1.updatedTs; + const memoSorting = (mi: Memo, mj: Memo) => { + return mj.displayTs - mi.displayTs; }; pinnedMemos.sort(memoSorting); unpinnedMemos.sort(memoSorting); @@ -89,19 +88,19 @@ const MemoList = () => { console.error(error); toastHelper.error(error.response.data.message); }); - }, [updatedTime]); + }, [memoSortOption]); useEffect(() => { const pageWrapper = document.body.querySelector(".page-wrapper"); if (pageWrapper) { pageWrapper.scrollTo(0, 0); } - }, [query, updatedTime]); + }, [query]); return (
{sortedMemos.map((memo) => ( - + ))} {isFetching ? (
diff --git a/web/src/services/locationService.ts b/web/src/services/locationService.ts index bb165ca2..6e75d5cd 100644 --- a/web/src/services/locationService.ts +++ b/web/src/services/locationService.ts @@ -1,7 +1,6 @@ import { stringify } from "qs"; import store from "../store"; -import { setQuery, setPathname, setUpdatedTime, Query, updateStateWithLocation } from "../store/modules/location"; -import { getTimeStampByDate } from "../helpers/utils"; +import { setQuery, setPathname, Query, updateStateWithLocation } from "../store/modules/location"; const updateLocationUrl = (method: "replace" | "push" = "replace") => { const { query, pathname, hash } = store.getState().location; @@ -113,10 +112,6 @@ const locationService = { ); updateLocationUrl(); }, - - setUpdatedFlag: () => { - store.dispatch(setUpdatedTime(getTimeStampByDate(new Date()).toString())); - }, }; export default locationService; diff --git a/web/src/services/memoService.ts b/web/src/services/memoService.ts index da700a34..95ad6416 100644 --- a/web/src/services/memoService.ts +++ b/web/src/services/memoService.ts @@ -8,6 +8,7 @@ const convertResponseModelMemo = (memo: Memo): Memo => { ...memo, createdTs: memo.createdTs * 1000, updatedTs: memo.updatedTs * 1000, + displayTs: memo.displayTs * 1000, }; }; diff --git a/web/src/store/modules/location.ts b/web/src/store/modules/location.ts index 38b6e178..7130f190 100644 --- a/web/src/store/modules/location.ts +++ b/web/src/store/modules/location.ts @@ -17,7 +17,6 @@ interface State { pathname: string; hash: string; query: Query; - updatedTime: string; } const getValidPathname = (pathname: string): string => { @@ -36,7 +35,6 @@ const getStateFromLocation = () => { pathname: getValidPathname(pathname), hash: hash, query: {}, - updatedTime: "", }; if (search !== "") { @@ -88,15 +86,9 @@ const locationSlice = createSlice({ }, }; }, - setUpdatedTime: (state, action: PayloadAction) => { - return { - ...state, - updatedTime: action.payload, - }; - }, }, }); -export const { setPathname, setQuery, setUpdatedTime, updateStateWithLocation } = locationSlice.actions; +export const { setPathname, setQuery, updateStateWithLocation } = locationSlice.actions; export default locationSlice.reducer; diff --git a/web/src/store/modules/memo.ts b/web/src/store/modules/memo.ts index aa089d03..add469ae 100644 --- a/web/src/store/modules/memo.ts +++ b/web/src/store/modules/memo.ts @@ -18,13 +18,13 @@ const memoSlice = createSlice({ setMemos: (state, action: PayloadAction) => { return { ...state, - memos: action.payload.filter((m) => m.rowStatus === "NORMAL").sort((a, b) => b.createdTs - a.createdTs), + memos: action.payload.filter((m) => m.rowStatus === "NORMAL"), }; }, createMemo: (state, action: PayloadAction) => { return { ...state, - memos: state.memos.concat(action.payload).sort((a, b) => b.createdTs - a.createdTs), + memos: state.memos.concat(action.payload), }; }, patchMemo: (state, action: PayloadAction>) => { diff --git a/web/src/types/modules/memo.d.ts b/web/src/types/modules/memo.d.ts index 7f921f3b..180509c3 100644 --- a/web/src/types/modules/memo.d.ts +++ b/web/src/types/modules/memo.d.ts @@ -13,6 +13,7 @@ interface Memo { content: string; visibility: Visibility; pinned: boolean; + displayTs: TimeStamp; creator: User; resourceList: Resource[];