mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: simplify date editor
This commit is contained in:
@@ -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",
|
||||
|
41
web/pnpm-lock.yaml
generated
41
web/pnpm-lock.yaml
generated
@@ -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):
|
||||
|
49
web/src/components/DateTimeInput.tsx
Normal file
49
web/src/components/DateTimeInput.tsx
Normal file
@@ -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">
|
||||
|
Reference in New Issue
Block a user