mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
feat: support updating display time
This commit is contained in:
@ -277,6 +277,17 @@ func (s *APIV1Service) UpdateMemo(ctx context.Context, request *v1pb.UpdateMemoR
|
|||||||
} else if path == "created_ts" {
|
} else if path == "created_ts" {
|
||||||
createdTs := request.Memo.CreateTime.AsTime().Unix()
|
createdTs := request.Memo.CreateTime.AsTime().Unix()
|
||||||
update.CreatedTs = &createdTs
|
update.CreatedTs = &createdTs
|
||||||
|
} else if path == "display_ts" {
|
||||||
|
displayTs := request.Memo.DisplayTime.AsTime().Unix()
|
||||||
|
memoRelatedSetting, err := s.Store.GetWorkspaceMemoRelatedSetting(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get workspace memo related setting")
|
||||||
|
}
|
||||||
|
if memoRelatedSetting.DisplayWithUpdateTime {
|
||||||
|
update.UpdatedTs = &displayTs
|
||||||
|
} else {
|
||||||
|
update.CreatedTs = &displayTs
|
||||||
|
}
|
||||||
} else if path == "pinned" {
|
} else if path == "pinned" {
|
||||||
if _, err := s.Store.UpsertMemoOrganizer(ctx, &store.MemoOrganizer{
|
if _, err := s.Store.UpsertMemoOrganizer(ctx, &store.MemoOrganizer{
|
||||||
MemoID: id,
|
MemoID: id,
|
||||||
|
@ -77,10 +77,8 @@ export function generateDialog<T extends DialogProps>(
|
|||||||
const cbs: DialogCallback = {
|
const cbs: DialogCallback = {
|
||||||
destroy: () => {
|
destroy: () => {
|
||||||
document.body.style.removeProperty("overflow");
|
document.body.style.removeProperty("overflow");
|
||||||
setTimeout(() => {
|
|
||||||
dialog.unmount();
|
dialog.unmount();
|
||||||
tempDiv.remove();
|
tempDiv.remove();
|
||||||
});
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const ExploreSidebar = (props: Props) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<TagsSection hideTips={true} />
|
<TagsSection readonly={true} />
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -14,7 +14,7 @@ import Icon from "../Icon";
|
|||||||
import showRenameTagDialog from "../RenameTagDialog";
|
import showRenameTagDialog from "../RenameTagDialog";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
hideTips?: boolean;
|
readonly?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TagsSection = (props: Props) => {
|
const TagsSection = (props: Props) => {
|
||||||
@ -62,7 +62,7 @@ const TagsSection = (props: Props) => {
|
|||||||
<div className="flex flex-col justify-start items-start w-full mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
<div className="flex flex-col justify-start items-start w-full mt-3 px-1 h-auto shrink-0 flex-nowrap hide-scrollbar">
|
||||||
<div className="group flex flex-row justify-start items-center w-full gap-1 mb-1">
|
<div className="group flex flex-row justify-start items-center w-full gap-1 mb-1">
|
||||||
<span className="text-sm leading-6 font-mono text-gray-400 select-none">{t("common.tags")}</span>
|
<span className="text-sm leading-6 font-mono text-gray-400 select-none">{t("common.tags")}</span>
|
||||||
{!props.hideTips && (
|
{!props.readonly && (
|
||||||
<div className={clsx("group-hover:block", tagAmounts.length > 0 ? "hidden" : "")}>
|
<div className={clsx("group-hover:block", tagAmounts.length > 0 ? "hidden" : "")}>
|
||||||
<Tooltip title={"Rebuild"} placement="top">
|
<Tooltip title={"Rebuild"} placement="top">
|
||||||
<Icon.RefreshCcw
|
<Icon.RefreshCcw
|
||||||
@ -80,7 +80,7 @@ const TagsSection = (props: Props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
!props.hideTips && (
|
!props.readonly && (
|
||||||
<div className="p-2 border border-dashed dark:border-zinc-800 rounded-md flex flex-row justify-start items-start gap-1 text-gray-400 dark:text-gray-500">
|
<div className="p-2 border border-dashed dark:border-zinc-800 rounded-md flex flex-row justify-start items-start gap-1 text-gray-400 dark:text-gray-500">
|
||||||
<Icon.Tags />
|
<Icon.Tags />
|
||||||
<p className="mt-0.5 text-sm leading-snug italic">{t("tag.create-tags-guide")}</p>
|
<p className="mt-0.5 text-sm leading-snug italic">{t("tag.create-tags-guide")}</p>
|
||||||
|
@ -3,7 +3,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Code: React.FC<Props> = ({ content }: Props) => {
|
const Code: React.FC<Props> = ({ content }: Props) => {
|
||||||
return <code className="inline break-all px-1 font-mono rounded bg-gray-100 dark:bg-zinc-700">{content}</code>;
|
return <code className="inline break-all px-1 font-mono text-sm rounded opacity-80 bg-gray-100 dark:bg-zinc-700">{content}</code>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Code;
|
export default Code;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { IconButton } from "@mui/joy";
|
import { IconButton } from "@mui/joy";
|
||||||
import { useEffect } from "react";
|
import clsx from "clsx";
|
||||||
import { useTagStore } from "@/store/v1";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useMemoStore, useTagStore } from "@/store/v1";
|
||||||
|
import { Memo } from "@/types/proto/api/v1/memo_service";
|
||||||
import MemoEditor, { Props as MemoEditorProps } from ".";
|
import MemoEditor, { Props as MemoEditorProps } from ".";
|
||||||
import { generateDialog } from "../Dialog";
|
import { generateDialog } from "../Dialog";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
@ -8,7 +10,7 @@ import Icon from "../Icon";
|
|||||||
interface Props extends DialogProps, MemoEditorProps {}
|
interface Props extends DialogProps, MemoEditorProps {}
|
||||||
|
|
||||||
const MemoEditorDialog: React.FC<Props> = ({
|
const MemoEditorDialog: React.FC<Props> = ({
|
||||||
memoName: memo,
|
memoName,
|
||||||
parentMemoName,
|
parentMemoName,
|
||||||
placeholder,
|
placeholder,
|
||||||
cacheKey,
|
cacheKey,
|
||||||
@ -17,11 +19,21 @@ const MemoEditorDialog: React.FC<Props> = ({
|
|||||||
destroy,
|
destroy,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const tagStore = useTagStore();
|
const tagStore = useTagStore();
|
||||||
|
const memoStore = useMemoStore();
|
||||||
|
const [displayTime, setDisplayTime] = useState<string | undefined>(memoStore.getMemoByName(memoName || "")?.displayTime?.toISOString());
|
||||||
|
const memoPatchRef = useRef<Partial<Memo>>({
|
||||||
|
displayTime: memoStore.getMemoByName(memoName || "")?.displayTime,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
tagStore.fetchTags(undefined, { skipCache: false });
|
tagStore.fetchTags(undefined, { skipCache: false });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const updateDisplayTime = (displayTime: string) => {
|
||||||
|
setDisplayTime(displayTime);
|
||||||
|
memoPatchRef.current.displayTime = new Date(displayTime);
|
||||||
|
};
|
||||||
|
|
||||||
const handleCloseBtnClick = () => {
|
const handleCloseBtnClick = () => {
|
||||||
destroy();
|
destroy();
|
||||||
};
|
};
|
||||||
@ -35,10 +47,25 @@ const MemoEditorDialog: React.FC<Props> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex flex-row justify-between items-center mb-2">
|
<div className="w-full flex flex-row justify-between items-center">
|
||||||
<div className="flex flex-row justify-start items-center">
|
<div className={clsx("flex flex-row justify-start items-center", !displayTime && "mb-2")}>
|
||||||
|
{displayTime ? (
|
||||||
|
<div className="relative">
|
||||||
|
<span className="cursor-pointer text-gray-500 dark:text-gray-400">{new Date(displayTime).toLocaleString()}</span>
|
||||||
|
<input
|
||||||
|
className="inset-0 absolute z-1 opacity-0"
|
||||||
|
type="datetime-local"
|
||||||
|
value={displayTime}
|
||||||
|
onFocus={(e: any) => e.target.showPicker()}
|
||||||
|
onChange={(e) => updateDisplayTime(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<img className="w-6 h-auto rounded-full shadow" src={"/full-logo.webp"} alt="" />
|
<img className="w-6 h-auto rounded-full shadow" src={"/full-logo.webp"} alt="" />
|
||||||
<p className="ml-1 text-lg opacity-80 dark:text-gray-300">Memos</p>
|
<p className="ml-1 text-lg opacity-80 dark:text-gray-300">Memos</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<IconButton size="sm" onClick={handleCloseBtnClick}>
|
<IconButton size="sm" onClick={handleCloseBtnClick}>
|
||||||
<Icon.X className="w-5 h-auto" />
|
<Icon.X className="w-5 h-auto" />
|
||||||
@ -47,11 +74,12 @@ const MemoEditorDialog: React.FC<Props> = ({
|
|||||||
<div className="flex flex-col justify-start items-start max-w-full w-[36rem]">
|
<div className="flex flex-col justify-start items-start max-w-full w-[36rem]">
|
||||||
<MemoEditor
|
<MemoEditor
|
||||||
className="border-none !p-0 -mb-2"
|
className="border-none !p-0 -mb-2"
|
||||||
cacheKey={`memo-editor-${cacheKey || memo}`}
|
cacheKey={`memo-editor-${cacheKey || memoName}`}
|
||||||
memoName={memo}
|
memoName={memoName}
|
||||||
parentMemoName={parentMemoName}
|
parentMemoName={parentMemoName}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
relationList={relationList}
|
relationList={relationList}
|
||||||
|
memoPatchRef={memoPatchRef}
|
||||||
onConfirm={handleConfirm}
|
onConfirm={handleConfirm}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
|
@ -9,7 +9,7 @@ import { isValidUrl } from "@/helpers/utils";
|
|||||||
import useCurrentUser from "@/hooks/useCurrentUser";
|
import useCurrentUser from "@/hooks/useCurrentUser";
|
||||||
import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1";
|
import { useMemoStore, useResourceStore, useUserStore, useWorkspaceSettingStore } from "@/store/v1";
|
||||||
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
import { MemoRelation, MemoRelation_Type } from "@/types/proto/api/v1/memo_relation_service";
|
||||||
import { Visibility } from "@/types/proto/api/v1/memo_service";
|
import { Memo, Visibility } from "@/types/proto/api/v1/memo_service";
|
||||||
import { Resource } from "@/types/proto/api/v1/resource_service";
|
import { Resource } from "@/types/proto/api/v1/resource_service";
|
||||||
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
import { UserSetting } from "@/types/proto/api/v1/user_service";
|
||||||
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
|
import { WorkspaceMemoRelatedSetting } from "@/types/proto/api/v1/workspace_setting_service";
|
||||||
@ -36,8 +36,8 @@ export interface Props {
|
|||||||
parentMemoName?: string;
|
parentMemoName?: string;
|
||||||
relationList?: MemoRelation[];
|
relationList?: MemoRelation[];
|
||||||
autoFocus?: boolean;
|
autoFocus?: boolean;
|
||||||
|
memoPatchRef?: React.MutableRefObject<Partial<Memo>>;
|
||||||
onConfirm?: (memoName: string) => void;
|
onConfirm?: (memoName: string) => void;
|
||||||
onEditPrevious?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -159,12 +159,6 @@ const MemoEditor = (props: Props) => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!!props.onEditPrevious && event.key === "ArrowDown" && !state.isComposing && editorRef.current.getContent() === "") {
|
|
||||||
event.preventDefault();
|
|
||||||
props.onEditPrevious();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoVisibilityChange = (visibility: Visibility) => {
|
const handleMemoVisibilityChange = (visibility: Visibility) => {
|
||||||
@ -293,13 +287,18 @@ const MemoEditor = (props: Props) => {
|
|||||||
if (memoName) {
|
if (memoName) {
|
||||||
const prevMemo = await memoStore.getOrFetchMemoByName(memoName);
|
const prevMemo = await memoStore.getOrFetchMemoByName(memoName);
|
||||||
if (prevMemo) {
|
if (prevMemo) {
|
||||||
|
const updateMask = ["content", "visibility"];
|
||||||
|
if (props.memoPatchRef?.current?.displayTime) {
|
||||||
|
updateMask.push("display_ts");
|
||||||
|
}
|
||||||
const memo = await memoStore.updateMemo(
|
const memo = await memoStore.updateMemo(
|
||||||
{
|
{
|
||||||
name: prevMemo.name,
|
name: prevMemo.name,
|
||||||
content,
|
content,
|
||||||
visibility: state.memoVisibility,
|
visibility: state.memoVisibility,
|
||||||
|
...props.memoPatchRef?.current,
|
||||||
},
|
},
|
||||||
["content", "visibility"],
|
updateMask,
|
||||||
);
|
);
|
||||||
await memoServiceClient.setMemoResources({
|
await memoServiceClient.setMemoResources({
|
||||||
name: memo.name,
|
name: memo.name,
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { Button } from "@mui/joy";
|
import { Button } from "@mui/joy";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Empty from "@/components/Empty";
|
import Empty from "@/components/Empty";
|
||||||
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
import { HomeSidebar, HomeSidebarDrawer } from "@/components/HomeSidebar";
|
||||||
import Icon from "@/components/Icon";
|
import Icon from "@/components/Icon";
|
||||||
import MemoEditor from "@/components/MemoEditor";
|
import MemoEditor from "@/components/MemoEditor";
|
||||||
import showMemoEditorDialog from "@/components/MemoEditor/MemoEditorDialog";
|
|
||||||
import MemoFilter from "@/components/MemoFilter";
|
import MemoFilter from "@/components/MemoFilter";
|
||||||
import MemoView from "@/components/MemoView";
|
import MemoView from "@/components/MemoView";
|
||||||
import MobileHeader from "@/components/MobileHeader";
|
import MobileHeader from "@/components/MobileHeader";
|
||||||
@ -59,14 +58,6 @@ const Home = () => {
|
|||||||
setNextPageToken(response.nextPageToken);
|
setNextPageToken(response.nextPageToken);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditPrevious = useCallback(() => {
|
|
||||||
const lastMemo = memoList.value[memoList.value.length - 1];
|
|
||||||
showMemoEditorDialog({
|
|
||||||
memoName: lastMemo.name,
|
|
||||||
cacheKey: `${lastMemo.name}-${lastMemo.displayTime}`,
|
|
||||||
});
|
|
||||||
}, [memoList]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
<section className="@container w-full max-w-5xl min-h-full flex flex-col justify-start items-center sm:pt-3 md:pt-6 pb-8">
|
||||||
{!md && (
|
{!md && (
|
||||||
@ -76,7 +67,7 @@ const Home = () => {
|
|||||||
)}
|
)}
|
||||||
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
<div className={clsx("w-full flex flex-row justify-start items-start px-4 sm:px-6 gap-4")}>
|
||||||
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
<div className={clsx(md ? "w-[calc(100%-15rem)]" : "w-full")}>
|
||||||
<MemoEditor className="mb-2" cacheKey="home-memo-editor" onEditPrevious={handleEditPrevious} />
|
<MemoEditor className="mb-2" cacheKey="home-memo-editor" />
|
||||||
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
<div className="flex flex-col justify-start items-start w-full max-w-full">
|
||||||
<MemoFilter className="px-2 pb-2" />
|
<MemoFilter className="px-2 pb-2" />
|
||||||
{sortedMemos.map((memo) => (
|
{sortedMemos.map((memo) => (
|
||||||
|
Reference in New Issue
Block a user