chore: simplify date editor

pull/4734/head
Johnny 3 days ago
parent 673026ffa1
commit 2595e32f83

@ -32,7 +32,6 @@
"mobx": "^6.13.7",
"mobx-react-lite": "^4.1.0",
"react": "^18.3.1",
"react-datepicker": "^8.4.0",
"react-dom": "^18.3.1",
"react-force-graph-2d": "^1.27.1",
"react-hot-toast": "^2.5.2",

@ -80,9 +80,6 @@ importers:
react:
specifier: ^18.3.1
version: 18.3.1
react-datepicker:
specifier: ^8.4.0
version: 8.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
@ -1021,12 +1018,6 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/react@0.27.9':
resolution: {integrity: sha512-Y0aCJBNtfVF6ikI1kVzA0WzSAhVBz79vFWOhvb5MLCRNODZ1ylGSLTuncchR7JsLyn9QzV6JD44DyZhhOtvpRw==}
peerDependencies:
react: '>=17.0.0'
react-dom: '>=17.0.0'
'@floating-ui/utils@0.2.9':
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
@ -2267,9 +2258,6 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
dayjs@1.11.13:
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
@ -3306,12 +3294,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
react-datepicker@8.4.0:
resolution: {integrity: sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==}
peerDependencies:
react: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom@18.3.1:
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies:
@ -3695,9 +3677,6 @@ packages:
systemjs@6.15.1:
resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==}
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
tailwind-merge@2.6.0:
resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
@ -4925,14 +4904,6 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@floating-ui/react@0.27.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/utils': 0.2.9
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tabbable: 6.2.0
'@floating-ui/utils@0.2.9': {}
'@github/relative-time-element@4.4.8': {}
@ -6248,8 +6219,6 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
date-fns@4.1.0: {}
dayjs@1.11.13: {}
debug@4.4.1:
@ -7407,14 +7376,6 @@ snapshots:
queue-microtask@1.2.3: {}
react-datepicker@8.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@floating-ui/react': 0.27.9(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
clsx: 2.1.1
date-fns: 4.1.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-dom@18.3.1(react@18.3.1):
dependencies:
loose-envify: 1.4.0
@ -7872,8 +7833,6 @@ snapshots:
systemjs@6.15.1: {}
tabbable@6.2.0: {}
tailwind-merge@2.6.0: {}
tailwindcss-animate@1.0.7(tailwindcss@3.4.17):

@ -0,0 +1,49 @@
import { isEqual } from "lodash-es";
import toast from "react-hot-toast";
import { cn } from "@/utils";
// Helper function to convert Date to local datetime string.
const toLocalDateTimeString = (date: Date | undefined): string => {
if (!date) return "";
return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().slice(0, -1);
};
interface Props {
label: string;
value: Date | undefined;
originalValue: Date | undefined;
onChange: (date: Date) => void;
}
const DateTimeInput: React.FC<Props> = ({ label, value, originalValue, onChange }) => {
return (
<div className="w-full flex items-center gap-2">
<span>{label}</span>
<input
type="text"
className={cn(
"flex-1 px-1 bg-transparent rounded text-xs transition-all",
"border-transparent focus:border-gray-300 dark:focus:border-zinc-700",
!isEqual(value, originalValue) && "border-gray-300 dark:border-zinc-700",
"border",
)}
defaultValue={toLocalDateTimeString(value)}
onBlur={(e) => {
const inputValue = e.target.value;
if (inputValue) {
const date = new Date(inputValue);
if (!isNaN(date.getTime())) {
onChange(date);
} else {
toast.error("Invalid datetime format. Use format: 2023-12-31T23:59:59");
e.target.value = toLocalDateTimeString(value);
}
}
}}
placeholder="YYYY-MM-DDTHH:mm:ss"
/>
</div>
);
};
export default DateTimeInput;

@ -3,7 +3,6 @@ import { isEqual } from "lodash-es";
import { LoaderIcon, SendIcon } from "lucide-react";
import { observer } from "mobx-react-lite";
import React, { useEffect, useMemo, useRef, useState } from "react";
import DatePicker from "react-datepicker";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import useLocalStorage from "react-use/lib/useLocalStorage";
@ -19,6 +18,7 @@ import { UserSetting } from "@/types/proto/api/v1/user_service";
import { cn } from "@/utils";
import { useTranslate } from "@/utils/i18n";
import { convertVisibilityFromString } from "@/utils/memo";
import DateTimeInput from "../DateTimeInput";
import AddMemoRelationPopover from "./ActionButton/AddMemoRelationPopover";
import LocationSelector from "./ActionButton/LocationSelector";
import MarkdownMenu from "./ActionButton/MarkdownMenu";
@ -30,7 +30,6 @@ import RelationListView from "./RelationListView";
import ResourceListView from "./ResourceListView";
import { handleEditorKeydownWithMarkdownShortcuts, hyperlinkHighlightedText } from "./handlers";
import { MemoEditorContext } from "./types";
import "react-datepicker/dist/react-datepicker.css";
export interface Props {
className?: string;
@ -71,7 +70,10 @@ const MemoEditor = observer((props: Props) => {
isComposing: false,
isDraggingFile: false,
});
const [displayTime, setDisplayTime] = useState<Date | undefined>();
const [createTime, setCreateTime] = useState<Date | undefined>();
const [updateTime, setUpdateTime] = useState<Date | undefined>();
const [originalCreateTime, setOriginalCreateTime] = useState<Date | undefined>();
const [originalUpdateTime, setOriginalUpdateTime] = useState<Date | undefined>();
const [hasContent, setHasContent] = useState<boolean>(false);
const [isVisibilitySelectorOpen, setIsVisibilitySelectorOpen] = useState(false);
const editorRef = useRef<EditorRefActions>(null);
@ -119,7 +121,10 @@ const MemoEditor = observer((props: Props) => {
const memo = await memoStore.getOrFetchMemoByName(memoName);
if (memo) {
handleEditorFocus();
setDisplayTime(memo.displayTime);
setCreateTime(memo.createTime);
setUpdateTime(memo.updateTime);
setOriginalCreateTime(memo.createTime);
setOriginalUpdateTime(memo.updateTime);
setState((prevState) => ({
...prevState,
memoVisibility: memo.visibility,
@ -361,9 +366,13 @@ const MemoEditor = observer((props: Props) => {
if (["content", "resources", "relations", "location"].some((key) => updateMask.has(key))) {
updateMask.add("update_time");
}
if (!isEqual(displayTime, prevMemo.displayTime)) {
updateMask.add("display_time");
memoPatch.displayTime = displayTime;
if (createTime && !isEqual(createTime, prevMemo.createTime)) {
updateMask.add("create_time");
memoPatch.createTime = createTime;
}
if (updateTime && !isEqual(updateTime, prevMemo.updateTime)) {
updateMask.add("update_time");
memoPatch.updateTime = updateTime;
}
if (updateMask.size === 0) {
toast.error("No changes detected");
@ -487,19 +496,6 @@ const MemoEditor = observer((props: Props) => {
onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd}
>
{memoName && displayTime && (
<DatePicker
selected={displayTime}
onChange={(date) => date && setDisplayTime(date)}
showTimeSelect
showMonthDropdown
showYearDropdown
yearDropdownItemNumber={5}
dateFormatCalendar=" "
customInput={<span className="cursor-pointer text-sm text-gray-400 dark:text-gray-500">{displayTime.toLocaleString()}</span>}
calendarClassName="ml-24 sm:ml-44"
/>
)}
<Editor ref={editorRef} {...editorConfig} />
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
<RelationListView relationList={referenceRelations} setRelationList={handleSetRelationList} />
@ -547,6 +543,20 @@ const MemoEditor = observer((props: Props) => {
/>
</div>
</div>
{/* Show memo metadata if memoName is provided */}
{memoName && (
<div className="w-full mb-4 text-xs leading-5 px-4 opacity-60 font-mono text-gray-500 dark:text-zinc-500">
{!isEqual(createTime, updateTime) && (
<DateTimeInput label="Updated" value={updateTime} originalValue={originalUpdateTime} onChange={setUpdateTime} />
)}
<DateTimeInput label="Created" value={createTime} originalValue={originalCreateTime} onChange={setCreateTime} />
<div className="w-full flex items-center gap-2">
<span>ID:</span>
<span>{memoName}</span>
</div>
</div>
)}
</MemoEditorContext.Provider>
);
});

@ -1,9 +1,7 @@
import dayjs from "dayjs";
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import DatePicker from "react-datepicker";
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
import i18n from "@/i18n";
import type { MonthNavigatorProps } from "@/types/statistics";
import "react-datepicker/dist/react-datepicker.css";
export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorProps) => {
const currentMonth = dayjs(visibleMonth).toDate();
@ -18,25 +16,9 @@ export const MonthNavigator = ({ visibleMonth, onMonthChange }: MonthNavigatorPr
return (
<div className="w-full mb-1 flex flex-row justify-between items-center gap-1">
<div className="relative text-sm font-medium inline-flex flex-row items-center w-auto dark:text-gray-400">
<DatePicker
selected={currentMonth}
onChange={(date) => {
if (date) {
onMonthChange(dayjs(date).format("YYYY-MM"));
}
}}
dateFormat="MMMM yyyy"
showMonthYearPicker
showFullMonthYearPicker
customInput={
<span className="cursor-pointer text-base hover:text-gray-600 dark:hover:text-gray-300 transition-colors">
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
</span>
}
popperPlacement="bottom-start"
calendarClassName="!bg-white !border-gray-200 !font-normal !shadow-lg"
/>
<div className="relative text-sm inline-flex flex-row items-center w-auto gap-2 dark:text-gray-400">
<CalendarIcon className="w-4 h-4" />
{currentMonth.toLocaleString(i18n.language, { year: "numeric", month: "long" })}
</div>
<div className="flex justify-end items-center shrink-0 gap-1">
<button className="p-1 cursor-pointer hover:opacity-80 transition-opacity" onClick={handlePrevMonth} aria-label="Previous month">

Loading…
Cancel
Save