diff --git a/api/memo.go b/api/memo.go index 9f852ebd..44df4e4d 100644 --- a/api/memo.go +++ b/api/memo.go @@ -42,18 +42,17 @@ type Memo struct { type MemoCreate struct { // Standard fields CreatorID int - // Used to import memos with a clearly created ts. - CreatedTs *int64 `json:"createdTs"` // Domain specific fields - Visibility Visibility - Content string `json:"content"` + Visibility Visibility `json:"visibility"` + Content string `json:"content"` } type MemoPatch struct { ID int // Standard fields + CreatedTs *int64 `json:"createdTs"` RowStatus *RowStatus `json:"rowStatus"` // Domain specific fields diff --git a/store/memo.go b/store/memo.go index c601cf04..036bb5f2 100644 --- a/store/memo.go +++ b/store/memo.go @@ -202,12 +202,8 @@ func (s *Store) DeleteMemo(ctx context.Context, delete *api.MemoDelete) error { func createMemoRaw(ctx context.Context, tx *sql.Tx, create *api.MemoCreate) (*memoRaw, error) { set := []string{"creator_id", "content", "visibility"} - placeholder := []string{"?", "?", "?"} args := []interface{}{create.CreatorID, create.Content, create.Visibility} - - if v := create.CreatedTs; v != nil { - set, placeholder, args = append(set, "created_ts"), append(placeholder, "?"), append(args, *v) - } + placeholder := []string{"?", "?", "?"} query := ` INSERT INTO memo ( @@ -235,12 +231,15 @@ func createMemoRaw(ctx context.Context, tx *sql.Tx, create *api.MemoCreate) (*me func patchMemoRaw(ctx context.Context, tx *sql.Tx, patch *api.MemoPatch) (*memoRaw, error) { set, args := []string{}, []interface{}{} - if v := patch.Content; v != nil { - set, args = append(set, "content = ?"), append(args, *v) + if v := patch.CreatedTs; v != nil { + set, args = append(set, "created_ts = ?"), append(args, *v) } if v := patch.RowStatus; v != nil { set, args = append(set, "row_status = ?"), append(args, *v) } + if v := patch.Content; v != nil { + set, args = append(set, "content = ?"), append(args, *v) + } if v := patch.Visibility; v != nil { set, args = append(set, "visibility = ?"), append(args, *v) } diff --git a/web/src/components/ChangeMemoCreatedTsDialog.tsx b/web/src/components/ChangeMemoCreatedTsDialog.tsx new file mode 100644 index 00000000..86ebd389 --- /dev/null +++ b/web/src/components/ChangeMemoCreatedTsDialog.tsx @@ -0,0 +1,99 @@ +import { useEffect, useState } from "react"; +import dayjs from "dayjs"; +import useI18n from "../hooks/useI18n"; +import { memoService } from "../services"; +import Icon from "./Icon"; +import { generateDialog } from "./Dialog"; +import toastHelper from "./Toast"; +import "../less/change-memo-created-ts-dialog.less"; + +interface Props extends DialogProps { + memoId: MemoId; +} + +const ChangeMemoCreatedTsDialog: React.FC = (props: Props) => { + const { t } = useI18n(); + const { destroy, memoId } = props; + const [createdAt, setCreatedAt] = useState(""); + const maxDatetimeValue = dayjs().format("YYYY-MM-DDTHH:mm"); + + useEffect(() => { + const memo = memoService.getMemoById(memoId); + if (memo) { + const datetime = dayjs(memo.createdTs).format("YYYY-MM-DDTHH:mm"); + setCreatedAt(datetime); + } else { + toastHelper.error("Memo not found."); + destroy(); + } + }, []); + + const handleCloseBtnClick = () => { + destroy(); + }; + + const handleDatetimeInputChange = (e: React.ChangeEvent) => { + const datetime = e.target.value as string; + setCreatedAt(datetime); + }; + + const handleSaveBtnClick = async () => { + const nowTs = dayjs().unix(); + const createdTs = dayjs(createdAt).unix(); + + if (createdTs > nowTs) { + toastHelper.error("Invalid created datetime."); + return; + } + + try { + await memoService.patchMemo({ + id: memoId, + createdTs, + }); + toastHelper.info("Memo created datetime changed."); + handleCloseBtnClick(); + } catch (error: any) { + console.error(error); + toastHelper.error(error.response.data.message); + } + }; + + return ( + <> +
+

Change memo created time

+ +
+
+ +
+ + {t("common.cancel")} + + + {t("common.save")} + +
+
+ + ); +}; + +function showChangeMemoCreatedTsDialog(memoId: MemoId) { + generateDialog( + { + className: "change-memo-created-ts-dialog", + }, + ChangeMemoCreatedTsDialog, + { + memoId, + } + ); +} + +export default showChangeMemoCreatedTsDialog; diff --git a/web/src/components/MemoCardDialog.tsx b/web/src/components/MemoCardDialog.tsx index f8fb70c3..277fb201 100644 --- a/web/src/components/MemoCardDialog.tsx +++ b/web/src/components/MemoCardDialog.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useCallback } from "react"; import { editorStateService, memoService, userService } from "../services"; +import { useAppSelector } from "../store"; import { IMAGE_URL_REG, MEMO_LINK_REG, UNKNOWN_ID, VISIBILITY_SELECTOR_ITEMS } from "../helpers/consts"; import * as utils from "../helpers/utils"; import { formatMemoContent, parseHtmlToRawText } from "../helpers/marked"; @@ -9,6 +10,7 @@ import { generateDialog } from "./Dialog"; import Image from "./Image"; import Icon from "./Icon"; import Selector from "./common/Selector"; +import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog"; import "../less/memo-card-dialog.less"; interface LinkedMemo extends Memo { @@ -21,6 +23,7 @@ interface Props extends DialogProps { } const MemoCardDialog: React.FC = (props: Props) => { + const memos = useAppSelector((state) => state.memo.memos); const [memo, setMemo] = useState({ ...props.memo, }); @@ -69,7 +72,12 @@ const MemoCardDialog: React.FC = (props: Props) => { }; fetchLinkedMemos(); - }, [memo.id]); + setMemo(memoService.getMemoById(memo.id) as Memo); + }, [memos, memo.id]); + + const handleMemoCreatedAtClick = () => { + showChangeMemoCreatedTsDialog(memo.id); + }; const handleMemoContentClick = useCallback(async (e: React.MouseEvent) => { const targetEl = e.target as HTMLElement; @@ -136,7 +144,9 @@ const MemoCardDialog: React.FC = (props: Props) => {
-

{utils.getDateTimeString(memo.createdTs)}

+

+ {utils.getDateTimeString(memo.createdTs)} +

<> diff --git a/web/src/components/MemoList.tsx b/web/src/components/MemoList.tsx index 6d34846f..9ff0f9f0 100644 --- a/web/src/components/MemoList.tsx +++ b/web/src/components/MemoList.tsx @@ -97,7 +97,7 @@ const MemoList: React.FC = () => { return (
{sortedMemos.map((memo) => ( - + ))}

diff --git a/web/src/helpers/utils.ts b/web/src/helpers/utils.ts index 284fcf8c..9382333b 100644 --- a/web/src/helpers/utils.ts +++ b/web/src/helpers/utils.ts @@ -44,10 +44,6 @@ export function getDateString(t: Date | number | string): string { return `${year}/${month}/${date}`; } -export function getDataStringWithTs(ts: number): string { - return getDateTimeString(ts * 1000); -} - export function getTimeString(t: Date | number | string): string { const d = new Date(getTimeStampByDate(t)); diff --git a/web/src/less/change-memo-created-ts-dialog.less b/web/src/less/change-memo-created-ts-dialog.less new file mode 100644 index 00000000..1dc7a23a --- /dev/null +++ b/web/src/less/change-memo-created-ts-dialog.less @@ -0,0 +1,47 @@ +@import "./mixin.less"; + +.change-memo-created-ts-dialog { + > .dialog-container { + @apply w-72; + + > .dialog-content-container { + .flex(column, flex-start, flex-start); + + > .tip-text { + @apply bg-gray-400 text-xs p-2 rounded-lg; + } + + > .form-label { + @apply flex flex-col justify-start items-start relative w-full leading-relaxed; + + &.input-form-label { + @apply py-3 pb-1; + + > input { + @apply w-full p-2 text-sm leading-6 rounded border border-gray-400 bg-transparent; + } + } + } + + > .btns-container { + @apply flex flex-row justify-end items-center mt-2 w-full; + + > .btn { + @apply text-sm px-4 py-2 rounded ml-2 bg-gray-400; + + &:hover { + @apply opacity-80; + } + + &.confirm-btn { + @apply bg-green-600 text-white shadow-inner; + } + + &.cancel-btn { + background-color: unset; + } + } + } + } + } +} diff --git a/web/src/less/change-password-dialog.less b/web/src/less/change-password-dialog.less index a1b9050f..423103a2 100644 --- a/web/src/less/change-password-dialog.less +++ b/web/src/less/change-password-dialog.less @@ -29,7 +29,7 @@ @apply mt-2 w-full; > .btn { - @apply text-sm px-4 py-2 rounded mr-2 bg-gray-400; + @apply text-sm px-4 py-2 rounded ml-2 bg-gray-400; &:hover { @apply opacity-80; diff --git a/web/src/types/modules/memo.d.ts b/web/src/types/modules/memo.d.ts index bcc0fb66..c92ed320 100644 --- a/web/src/types/modules/memo.d.ts +++ b/web/src/types/modules/memo.d.ts @@ -17,13 +17,14 @@ interface Memo { interface MemoCreate { content: string; - createdTs?: TimeStamp; + visibility?: Visibility; } interface MemoPatch { id: MemoId; - content?: string; + createdTs?: TimeStamp; rowStatus?: RowStatus; + content?: string; visibility?: Visibility; }