mirror of
https://github.com/usememos/memos.git
synced 2024-12-24 03:33:52 +03:00
feat: memo editor dialog (#1772)
* feat: memo editor dialog * chore: update mark * chore: update
This commit is contained in:
parent
25ce36e495
commit
dd8c10743d
@ -1,17 +1,19 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { NavLink, useLocation } from "react-router-dom";
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLayoutStore, useUserStore } from "@/store/module";
|
import { useGlobalStore, useLayoutStore, useUserStore } from "@/store/module";
|
||||||
import { resolution } from "@/utils/layout";
|
import { resolution } from "@/utils/layout";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
import UserBanner from "./UserBanner";
|
||||||
import showSettingDialog from "./SettingDialog";
|
import showSettingDialog from "./SettingDialog";
|
||||||
import showAskAIDialog from "./AskAIDialog";
|
import showAskAIDialog from "./AskAIDialog";
|
||||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||||
import UserBanner from "./UserBanner";
|
import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const globalStore = useGlobalStore();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
@ -57,7 +59,7 @@ const Header = () => {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${
|
`${
|
||||||
isActive && "bg-white dark:bg-zinc-700 shadow"
|
isActive && "bg-white dark:bg-zinc-700 shadow"
|
||||||
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
} px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -70,7 +72,7 @@ const Header = () => {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${
|
`${
|
||||||
isActive && "bg-white dark:bg-zinc-700 shadow"
|
isActive && "bg-white dark:bg-zinc-700 shadow"
|
||||||
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
} px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -83,7 +85,7 @@ const Header = () => {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${
|
`${
|
||||||
isActive && "bg-white dark:bg-zinc-700 shadow"
|
isActive && "bg-white dark:bg-zinc-700 shadow"
|
||||||
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
} px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -98,7 +100,7 @@ const Header = () => {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${
|
`${
|
||||||
isActive && "bg-white dark:bg-zinc-700 shadow"
|
isActive && "bg-white dark:bg-zinc-700 shadow"
|
||||||
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
} px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -109,25 +111,35 @@ const Header = () => {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
id="header-ask-ai"
|
id="header-ask-ai"
|
||||||
className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
className="px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
||||||
onClick={() => showAskAIDialog()}
|
onClick={() => showAskAIDialog()}
|
||||||
>
|
>
|
||||||
<Icon.Bot className="mr-3 w-6 h-auto opacity-70" /> {t("ask-ai.title")}
|
<Icon.Bot className="mr-3 w-6 h-auto opacity-70" /> {t("ask-ai.title")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="header-archived-memo"
|
id="header-archived-memo"
|
||||||
className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
className="px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
||||||
onClick={() => showArchivedMemoDialog()}
|
onClick={() => showArchivedMemoDialog()}
|
||||||
>
|
>
|
||||||
<Icon.Archive className="mr-3 w-6 h-auto opacity-70" /> {t("common.archived")}
|
<Icon.Archive className="mr-3 w-6 h-auto opacity-70" /> {t("common.archived")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="header-settings"
|
id="header-settings"
|
||||||
className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
className="px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
||||||
onClick={() => showSettingDialog()}
|
onClick={() => showSettingDialog()}
|
||||||
>
|
>
|
||||||
<Icon.Settings className="mr-3 w-6 h-auto opacity-70" /> {t("common.settings")}
|
<Icon.Settings className="mr-3 w-6 h-auto opacity-70" /> {t("common.settings")}
|
||||||
</button>
|
</button>
|
||||||
|
{globalStore.isDev() && (
|
||||||
|
<div className="pr-3 pl-1 w-full">
|
||||||
|
<button
|
||||||
|
className="mt-2 w-full py-3 rounded-full flex flex-row justify-center items-center bg-green-600 font-medium text-white dark:opacity-80 hover:shadow hover:opacity-90"
|
||||||
|
onClick={() => showMemoEditorDialog()}
|
||||||
|
>
|
||||||
|
<Icon.Edit3 className="w-4 h-auto mr-1" /> New
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isVisitorMode && (
|
{isVisitorMode && (
|
||||||
@ -138,7 +150,7 @@ const Header = () => {
|
|||||||
className={({ isActive }) =>
|
className={({ isActive }) =>
|
||||||
`${
|
`${
|
||||||
isActive && "bg-white dark:bg-zinc-700 shadow"
|
isActive && "bg-white dark:bg-zinc-700 shadow"
|
||||||
} px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
} px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<>
|
<>
|
||||||
@ -147,7 +159,7 @@ const Header = () => {
|
|||||||
</NavLink>
|
</NavLink>
|
||||||
<button
|
<button
|
||||||
id="header-about"
|
id="header-about"
|
||||||
className="px-4 pr-5 py-2 rounded-lg flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
className="px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
||||||
onClick={() => showAboutSiteDialog()}
|
onClick={() => showAboutSiteDialog()}
|
||||||
>
|
>
|
||||||
<Icon.CupSoda className="mr-3 w-6 h-auto opacity-70" /> {t("common.about")}
|
<Icon.CupSoda className="mr-3 w-6 h-auto opacity-70" /> {t("common.about")}
|
||||||
|
@ -3,9 +3,9 @@ import { memo, useEffect, useRef, useState } from "react";
|
|||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useEditorStore, useFilterStore, useMemoStore, useUserStore } from "@/store/module";
|
import { useFilterStore, useMemoStore, useUserStore } from "@/store/module";
|
||||||
import { getRelativeTimeString } from "@/helpers/datetime";
|
|
||||||
import { UNKNOWN_ID } from "@/helpers/consts";
|
import { UNKNOWN_ID } from "@/helpers/consts";
|
||||||
|
import { getRelativeTimeString } from "@/helpers/datetime";
|
||||||
import { useMemoCacheStore } from "@/store/zustand";
|
import { useMemoCacheStore } from "@/store/zustand";
|
||||||
import Tooltip from "./kit/Tooltip";
|
import Tooltip from "./kit/Tooltip";
|
||||||
import Divider from "./kit/Divider";
|
import Divider from "./kit/Divider";
|
||||||
@ -17,6 +17,7 @@ import MemoRelationListView from "./MemoRelationListView";
|
|||||||
import showShareMemo from "./ShareMemoDialog";
|
import showShareMemo from "./ShareMemoDialog";
|
||||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
import showPreviewImageDialog from "./PreviewImageDialog";
|
||||||
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
import showChangeMemoCreatedTsDialog from "./ChangeMemoCreatedTsDialog";
|
||||||
|
import showMemoEditorDialog from "./MemoEditor/MemoEditorDialog";
|
||||||
import "@/less/memo.less";
|
import "@/less/memo.less";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -28,7 +29,6 @@ interface Props {
|
|||||||
const Memo: React.FC<Props> = (props: Props) => {
|
const Memo: React.FC<Props> = (props: Props) => {
|
||||||
const { memo, readonly, showRelatedMemos } = props;
|
const { memo, readonly, showRelatedMemos } = props;
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const editorStore = useEditorStore();
|
|
||||||
const filterStore = useFilterStore();
|
const filterStore = useFilterStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
@ -78,7 +78,21 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEditMemoClick = () => {
|
const handleEditMemoClick = () => {
|
||||||
editorStore.setEditMemoWithId(memo.id);
|
showMemoEditorDialog({
|
||||||
|
memoId: memo.id,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMarkMemoClick = () => {
|
||||||
|
showMemoEditorDialog({
|
||||||
|
relationList: [
|
||||||
|
{
|
||||||
|
memoId: UNKNOWN_ID,
|
||||||
|
relatedMemoId: memo.id,
|
||||||
|
type: "REFERENCE",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleArchiveMemoClick = async () => {
|
const handleArchiveMemoClick = async () => {
|
||||||
@ -91,10 +105,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
toast.error(error.response.data.message);
|
toast.error(error.response.data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorStore.getState().editMemoId === memo.id) {
|
|
||||||
editorStore.clearEditMemo();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteMemoClick = async () => {
|
const handleDeleteMemoClick = async () => {
|
||||||
@ -180,7 +190,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
editorStore.setEditMemoWithId(memo.id);
|
handleEditMemoClick();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMemoCreatedTimeClick = (e: React.MouseEvent) => {
|
const handleMemoCreatedTimeClick = (e: React.MouseEvent) => {
|
||||||
@ -199,15 +209,6 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkMemo = () => {
|
|
||||||
const relation: MemoRelation = {
|
|
||||||
memoId: UNKNOWN_ID,
|
|
||||||
relatedMemoId: memo.id,
|
|
||||||
type: "REFERENCE",
|
|
||||||
};
|
|
||||||
editorStore.setRelationList(uniqWith([...editorStore.state.relationList, relation], isEqual));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}>
|
<div className={`memo-wrapper ${"memos-" + memo.id} ${memo.pinned && !readonly ? "pinned" : ""}`} ref={memoContainerRef}>
|
||||||
@ -253,7 +254,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||||||
<Icon.Share className="w-4 h-auto mr-2" />
|
<Icon.Share className="w-4 h-auto mr-2" />
|
||||||
{t("common.share")}
|
{t("common.share")}
|
||||||
</span>
|
</span>
|
||||||
<span className="btn" onClick={handleMarkMemo}>
|
<span className="btn" onClick={handleMarkMemoClick}>
|
||||||
<Icon.Link className="w-4 h-auto mr-2" />
|
<Icon.Link className="w-4 h-auto mr-2" />
|
||||||
Mark
|
Mark
|
||||||
</span>
|
</span>
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
import { toLower } from "lodash-es";
|
import { toLower } from "lodash-es";
|
||||||
import { useEffect } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
|
import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
|
||||||
import { useEditorStore, useGlobalStore } from "@/store/module";
|
import { useGlobalStore } from "@/store/module";
|
||||||
import Selector from "@/components/kit/Selector";
|
import Selector from "@/components/kit/Selector";
|
||||||
|
|
||||||
const MemoVisibilitySelector = () => {
|
interface Props {
|
||||||
|
value: Visibility;
|
||||||
|
onChange: (value: Visibility) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoVisibilitySelector = (props: Props) => {
|
||||||
|
const { value, onChange } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const editorStore = useEditorStore();
|
|
||||||
const {
|
const {
|
||||||
state: { systemStatus },
|
state: { systemStatus },
|
||||||
} = useGlobalStore();
|
} = useGlobalStore();
|
||||||
const editorState = editorStore.state;
|
|
||||||
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
const memoVisibilityOptionSelectorItems = VISIBILITY_SELECTOR_ITEMS.map((item) => {
|
||||||
return {
|
return {
|
||||||
value: item.value,
|
value: item.value,
|
||||||
@ -19,15 +22,8 @@ const MemoVisibilitySelector = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
const handleMemoVisibilityOptionChanged = async (visibility: string) => {
|
||||||
if (systemStatus.disablePublicMemos) {
|
onChange(visibility as Visibility);
|
||||||
editorStore.setMemoVisibility("PRIVATE");
|
|
||||||
}
|
|
||||||
}, [systemStatus.disablePublicMemos, editorState.memoVisibility]);
|
|
||||||
|
|
||||||
const handleMemoVisibilityOptionChanged = async (value: string) => {
|
|
||||||
const visibilityValue = value as Visibility;
|
|
||||||
editorStore.setMemoVisibility(visibilityValue);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -35,7 +31,7 @@ const MemoVisibilitySelector = () => {
|
|||||||
className="visibility-selector"
|
className="visibility-selector"
|
||||||
disabled={systemStatus.disablePublicMemos}
|
disabled={systemStatus.disablePublicMemos}
|
||||||
tooltipTitle={t("memo.visibility.disabled")}
|
tooltipTitle={t("memo.visibility.disabled")}
|
||||||
value={editorState.memoVisibility}
|
value={value}
|
||||||
dataSource={memoVisibilityOptionSelectorItems}
|
dataSource={memoVisibilityOptionSelectorItems}
|
||||||
handleValueChanged={handleMemoVisibilityOptionChanged}
|
handleValueChanged={handleMemoVisibilityOptionChanged}
|
||||||
/>
|
/>
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { useEditorStore } from "@/store/module";
|
|
||||||
import Icon from "@/components/Icon";
|
|
||||||
import showCreateResourceDialog from "@/components/CreateResourceDialog";
|
|
||||||
import showResourcesSelectorDialog from "@/components/ResourcesSelectorDialog";
|
|
||||||
|
|
||||||
const ResourceSelector = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const editorStore = useEditorStore();
|
|
||||||
|
|
||||||
const handleUploadFileBtnClick = () => {
|
|
||||||
showCreateResourceDialog({
|
|
||||||
onConfirm: (resourceList) => {
|
|
||||||
editorStore.setResourceList([...editorStore.state.resourceList, ...resourceList]);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="action-btn relative group">
|
|
||||||
<Icon.FileText className="icon-img" />
|
|
||||||
<div className="hidden flex-col justify-start items-start absolute top-6 left-0 mt-1 p-1 z-1 rounded w-auto overflow-auto font-mono shadow bg-zinc-200 dark:bg-zinc-600 group-hover:flex">
|
|
||||||
<div
|
|
||||||
className="w-full flex text-black dark:text-gray-300 cursor-pointer rounded text-sm leading-6 px-2 truncate hover:bg-zinc-300 dark:hover:bg-zinc-700 shrink-0"
|
|
||||||
onClick={handleUploadFileBtnClick}
|
|
||||||
>
|
|
||||||
<Icon.Plus className="w-4 mr-1" />
|
|
||||||
<span>{t("common.create")}</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="w-full flex text-black dark:text-gray-300 cursor-pointer rounded text-sm leading-6 px-2 truncate hover:bg-zinc-300 dark:hover:bg-zinc-700 shrink-0"
|
|
||||||
onClick={showResourcesSelectorDialog}
|
|
||||||
>
|
|
||||||
<Icon.Database className="w-4 mr-1" />
|
|
||||||
<span>{t("editor.resources")}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
export default ResourceSelector;
|
|
39
web/src/components/MemoEditor/MemoEditorDialog.tsx
Normal file
39
web/src/components/MemoEditor/MemoEditorDialog.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { generateDialog } from "../Dialog";
|
||||||
|
import Icon from "../Icon";
|
||||||
|
import MemoEditor from ".";
|
||||||
|
|
||||||
|
interface Props extends DialogProps {
|
||||||
|
memoId?: MemoId;
|
||||||
|
relationList?: MemoRelation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const MemoEditorDialog: React.FC<Props> = ({ memoId, relationList, destroy }: Props) => {
|
||||||
|
const handleCloseBtnClick = () => {
|
||||||
|
destroy();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="dialog-header-container">
|
||||||
|
<p className="title-text flex items-center">MEMOS</p>
|
||||||
|
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||||
|
<Icon.X />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col justify-start items-start max-w-full w-[36rem]">
|
||||||
|
<MemoEditor memoId={memoId} relationList={relationList} onConfirm={handleCloseBtnClick} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function showMemoEditorDialog(props: Pick<Props, "memoId" | "relationList"> = {}): void {
|
||||||
|
generateDialog(
|
||||||
|
{
|
||||||
|
className: "memo-editor-dialog",
|
||||||
|
dialogName: "memo-editor-dialog",
|
||||||
|
},
|
||||||
|
MemoEditorDialog,
|
||||||
|
props
|
||||||
|
);
|
||||||
|
}
|
@ -1,17 +1,20 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useEditorStore } from "@/store/module";
|
|
||||||
import { useMemoCacheStore } from "@/store/zustand";
|
import { useMemoCacheStore } from "@/store/zustand";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
relationList: MemoRelation[];
|
||||||
|
setRelationList: (relationList: MemoRelation[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface FormatedMemoRelation extends MemoRelation {
|
interface FormatedMemoRelation extends MemoRelation {
|
||||||
relatedMemo: Memo;
|
relatedMemo: Memo;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RelationListView = () => {
|
const RelationListView = (props: Props) => {
|
||||||
const editorStore = useEditorStore();
|
const { relationList, setRelationList } = props;
|
||||||
const memoCacheStore = useMemoCacheStore();
|
const memoCacheStore = useMemoCacheStore();
|
||||||
const [formatedMemoRelationList, setFormatedMemoRelationList] = useState<FormatedMemoRelation[]>([]);
|
const [formatedMemoRelationList, setFormatedMemoRelationList] = useState<FormatedMemoRelation[]>([]);
|
||||||
const relationList = editorStore.state.relationList;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchRelatedMemoList = async () => {
|
const fetchRelatedMemoList = async () => {
|
||||||
@ -30,7 +33,7 @@ const RelationListView = () => {
|
|||||||
|
|
||||||
const handleDeleteRelation = async (memoRelation: FormatedMemoRelation) => {
|
const handleDeleteRelation = async (memoRelation: FormatedMemoRelation) => {
|
||||||
const newRelationList = relationList.filter((relation) => relation.relatedMemoId !== memoRelation.relatedMemoId);
|
const newRelationList = relationList.filter((relation) => relation.relatedMemoId !== memoRelation.relatedMemoId);
|
||||||
editorStore.setRelationList(newRelationList);
|
setRelationList(newRelationList);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,20 +1,23 @@
|
|||||||
import { useEditorStore } from "@/store/module";
|
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import ResourceIcon from "../ResourceIcon";
|
import ResourceIcon from "../ResourceIcon";
|
||||||
|
|
||||||
const ResourceListView = () => {
|
interface Props {
|
||||||
const editorStore = useEditorStore();
|
resourceList: Resource[];
|
||||||
const editorState = editorStore.state;
|
setResourceList: (resourceList: Resource[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResourceListView = (props: Props) => {
|
||||||
|
const { resourceList, setResourceList } = props;
|
||||||
|
|
||||||
const handleDeleteResource = async (resourceId: ResourceId) => {
|
const handleDeleteResource = async (resourceId: ResourceId) => {
|
||||||
editorStore.setResourceList(editorState.resourceList.filter((resource) => resource.id !== resourceId));
|
setResourceList(resourceList.filter((resource) => resource.id !== resourceId));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{editorState.resourceList && editorState.resourceList.length > 0 && (
|
{resourceList.length > 0 && (
|
||||||
<div className="w-full flex flex-row justify-start flex-wrap gap-2 mt-2">
|
<div className="w-full flex flex-row justify-start flex-wrap gap-2 mt-2">
|
||||||
{editorState.resourceList.map((resource) => {
|
{resourceList.map((resource) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={resource.id}
|
key={resource.id}
|
||||||
|
@ -5,13 +5,13 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { getMatchedNodes } from "@/labs/marked";
|
import { getMatchedNodes } from "@/labs/marked";
|
||||||
import { upsertMemoResource } from "@/helpers/api";
|
import { upsertMemoResource } from "@/helpers/api";
|
||||||
import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts";
|
import { TAB_SPACE_WIDTH, UNKNOWN_ID } from "@/helpers/consts";
|
||||||
import { useEditorStore, useFilterStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "@/store/module";
|
import { useFilterStore, useGlobalStore, useMemoStore, useResourceStore, useTagStore, useUserStore } from "@/store/module";
|
||||||
import storage from "@/helpers/storage";
|
import storage from "@/helpers/storage";
|
||||||
import { clearContentQueryParam, getContentQueryParam } from "@/helpers/utils";
|
import { clearContentQueryParam, getContentQueryParam } from "@/helpers/utils";
|
||||||
import Icon from "../Icon";
|
import Icon from "../Icon";
|
||||||
import Editor, { EditorRefActions } from "./Editor";
|
import Editor, { EditorRefActions } from "./Editor";
|
||||||
|
import showCreateResourceDialog from "../CreateResourceDialog";
|
||||||
import TagSelector from "./ActionButton/TagSelector";
|
import TagSelector from "./ActionButton/TagSelector";
|
||||||
import ResourceSelector from "./ActionButton/ResourceSelector";
|
|
||||||
import MemoVisibilitySelector from "./ActionButton/MemoVisibilitySelector";
|
import MemoVisibilitySelector from "./ActionButton/MemoVisibilitySelector";
|
||||||
import ResourceListView from "./ResourceListView";
|
import ResourceListView from "./ResourceListView";
|
||||||
import RelationListView from "./RelationListView";
|
import RelationListView from "./RelationListView";
|
||||||
@ -24,73 +24,75 @@ const getInitialContent = (): string => {
|
|||||||
return getContentQueryParam() ?? storage.get(["editorContentCache"]).editorContentCache ?? "";
|
return getContentQueryParam() ?? storage.get(["editorContentCache"]).editorContentCache ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
const setEditorContentCache = (content: string) => {
|
interface Props {
|
||||||
storage.set({
|
className?: string;
|
||||||
editorContentCache: content,
|
memoId?: MemoId;
|
||||||
});
|
relationList?: MemoRelation[];
|
||||||
};
|
onConfirm?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
|
memoVisibility: Visibility;
|
||||||
|
resourceList: Resource[];
|
||||||
|
relationList: MemoRelation[];
|
||||||
fullscreen: boolean;
|
fullscreen: boolean;
|
||||||
isUploadingResource: boolean;
|
isUploadingResource: boolean;
|
||||||
isRequesting: boolean;
|
isRequesting: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MemoEditor = () => {
|
const MemoEditor = (props: Props) => {
|
||||||
|
const { className, memoId, onConfirm } = props;
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
|
const {
|
||||||
|
state: { systemStatus },
|
||||||
|
} = useGlobalStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const editorStore = useEditorStore();
|
|
||||||
const filterStore = useFilterStore();
|
const filterStore = useFilterStore();
|
||||||
const memoStore = useMemoStore();
|
const memoStore = useMemoStore();
|
||||||
const tagStore = useTagStore();
|
const tagStore = useTagStore();
|
||||||
const resourceStore = useResourceStore();
|
const resourceStore = useResourceStore();
|
||||||
|
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
|
memoVisibility: "PRIVATE",
|
||||||
|
resourceList: [],
|
||||||
|
relationList: props.relationList ?? [],
|
||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
isUploadingResource: false,
|
isUploadingResource: false,
|
||||||
isRequesting: false,
|
isRequesting: false,
|
||||||
});
|
});
|
||||||
const [allowSave, setAllowSave] = useState<boolean>(false);
|
const [hasContent, setHasContent] = useState<boolean>(false);
|
||||||
const [isInIME, setIsInIME] = useState(false);
|
const [isInIME, setIsInIME] = useState(false);
|
||||||
const editorState = editorStore.state;
|
|
||||||
const prevEditorStateRef = useRef(editorState);
|
|
||||||
const editorRef = useRef<EditorRefActions>(null);
|
const editorRef = useRef<EditorRefActions>(null);
|
||||||
const user = userStore.state.user as User;
|
const user = userStore.state.user as User;
|
||||||
const setting = user.setting;
|
const setting = user.setting;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { editingMemoIdCache } = storage.get(["editingMemoIdCache"]);
|
let visibility = setting.memoVisibility;
|
||||||
if (editingMemoIdCache) {
|
if (systemStatus.disablePublicMemos && visibility === "PUBLIC") {
|
||||||
editorStore.setEditMemoWithId(editingMemoIdCache);
|
visibility = "PRIVATE";
|
||||||
} else {
|
|
||||||
editorStore.setMemoVisibility(setting.memoVisibility);
|
|
||||||
}
|
}
|
||||||
}, []);
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
memoVisibility: visibility,
|
||||||
|
}));
|
||||||
|
}, [setting.memoVisibility, systemStatus.disablePublicMemos]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editorState.editMemoId) {
|
if (memoId) {
|
||||||
memoStore.getMemoById(editorState.editMemoId ?? UNKNOWN_ID).then((memo) => {
|
memoStore.getMemoById(memoId ?? UNKNOWN_ID).then((memo) => {
|
||||||
if (memo) {
|
if (memo) {
|
||||||
handleEditorFocus();
|
handleEditorFocus();
|
||||||
editorStore.setMemoVisibility(memo.visibility);
|
setState((prevState) => ({
|
||||||
editorStore.setResourceList(memo.resourceList);
|
...prevState,
|
||||||
editorStore.setRelationList(memo.relationList);
|
memoVisibility: memo.visibility,
|
||||||
|
resourceList: memo.resourceList,
|
||||||
|
relationList: memo.relationList,
|
||||||
|
}));
|
||||||
editorRef.current?.setContent(memo.content ?? "");
|
editorRef.current?.setContent(memo.content ?? "");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
storage.set({
|
|
||||||
editingMemoIdCache: editorState.editMemoId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
storage.remove(["editingMemoIdCache"]);
|
|
||||||
}
|
}
|
||||||
|
}, [memoId]);
|
||||||
prevEditorStateRef.current = editorState;
|
|
||||||
}, [editorState.editMemoId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handleEditorFocus();
|
|
||||||
}, [editorStore.state.relationList]);
|
|
||||||
|
|
||||||
const handleKeyDown = (event: React.KeyboardEvent) => {
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
@ -159,6 +161,38 @@ const MemoEditor = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMemoVisibilityChange = (visibility: Visibility) => {
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
memoVisibility: visibility,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadFileBtnClick = () => {
|
||||||
|
showCreateResourceDialog({
|
||||||
|
onConfirm: (resourceList) => {
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
resourceList: [...prevState.resourceList, ...resourceList],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetResourceList = (resourceList: Resource[]) => {
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
resourceList,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSetRelationList = (relationList: MemoRelation[]) => {
|
||||||
|
setState((prevState) => ({
|
||||||
|
...prevState,
|
||||||
|
relationList,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const handleUploadResource = async (file: File) => {
|
const handleUploadResource = async (file: File) => {
|
||||||
setState((state) => {
|
setState((state) => {
|
||||||
return {
|
return {
|
||||||
@ -190,14 +224,16 @@ const MemoEditor = () => {
|
|||||||
const resource = await handleUploadResource(file);
|
const resource = await handleUploadResource(file);
|
||||||
if (resource) {
|
if (resource) {
|
||||||
uploadedResourceList.push(resource);
|
uploadedResourceList.push(resource);
|
||||||
if (editorState.editMemoId) {
|
if (memoId) {
|
||||||
await upsertMemoResource(editorState.editMemoId, resource.id);
|
await upsertMemoResource(memoId, resource.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (uploadedResourceList.length > 0) {
|
if (uploadedResourceList.length > 0) {
|
||||||
const resourceList = editorStore.getState().resourceList;
|
setState((prevState) => ({
|
||||||
editorStore.setResourceList([...resourceList, ...uploadedResourceList]);
|
...prevState,
|
||||||
|
resourceList: [...prevState.resourceList, ...uploadedResourceList],
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -215,6 +251,10 @@ const MemoEditor = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleContentChange = (content: string) => {
|
||||||
|
setHasContent(content !== "");
|
||||||
|
};
|
||||||
|
|
||||||
const handleSaveBtnClick = async () => {
|
const handleSaveBtnClick = async () => {
|
||||||
if (state.isRequesting) {
|
if (state.isRequesting) {
|
||||||
return;
|
return;
|
||||||
@ -228,26 +268,24 @@ const MemoEditor = () => {
|
|||||||
});
|
});
|
||||||
const content = editorRef.current?.getContent() ?? "";
|
const content = editorRef.current?.getContent() ?? "";
|
||||||
try {
|
try {
|
||||||
const { editMemoId } = editorStore.getState();
|
if (memoId && memoId !== UNKNOWN_ID) {
|
||||||
if (editMemoId && editMemoId !== UNKNOWN_ID) {
|
const prevMemo = await memoStore.getMemoById(memoId ?? UNKNOWN_ID);
|
||||||
const prevMemo = await memoStore.getMemoById(editMemoId ?? UNKNOWN_ID);
|
|
||||||
|
|
||||||
if (prevMemo) {
|
if (prevMemo) {
|
||||||
await memoStore.patchMemo({
|
await memoStore.patchMemo({
|
||||||
id: prevMemo.id,
|
id: prevMemo.id,
|
||||||
content,
|
content,
|
||||||
visibility: editorState.memoVisibility,
|
visibility: state.memoVisibility,
|
||||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
resourceIdList: state.resourceList.map((resource) => resource.id),
|
||||||
relationList: editorState.relationList,
|
relationList: state.relationList,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
editorStore.clearEditMemo();
|
|
||||||
} else {
|
} else {
|
||||||
await memoStore.createMemo({
|
await memoStore.createMemo({
|
||||||
content,
|
content,
|
||||||
visibility: editorState.memoVisibility,
|
visibility: state.memoVisibility,
|
||||||
resourceIdList: editorState.resourceList.map((resource) => resource.id),
|
resourceIdList: state.resourceList.map((resource) => resource.id),
|
||||||
relationList: editorState.relationList,
|
relationList: state.relationList,
|
||||||
});
|
});
|
||||||
filterStore.clearFilter();
|
filterStore.clearFilter();
|
||||||
}
|
}
|
||||||
@ -275,28 +313,18 @@ const MemoEditor = () => {
|
|||||||
fullscreen: false,
|
fullscreen: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
editorStore.setResourceList([]);
|
setState((prevState) => ({
|
||||||
editorStore.setRelationList([]);
|
...prevState,
|
||||||
setEditorContentCache("");
|
resourceList: [],
|
||||||
|
relationList: [],
|
||||||
|
}));
|
||||||
editorRef.current?.setContent("");
|
editorRef.current?.setContent("");
|
||||||
clearContentQueryParam();
|
clearContentQueryParam();
|
||||||
};
|
if (onConfirm) {
|
||||||
|
onConfirm();
|
||||||
const handleCancelEdit = () => {
|
|
||||||
if (editorState.editMemoId) {
|
|
||||||
editorStore.clearEditMemo();
|
|
||||||
editorStore.setResourceList([]);
|
|
||||||
editorStore.setRelationList([]);
|
|
||||||
editorRef.current?.setContent("");
|
|
||||||
setEditorContentCache("");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleContentChange = (content: string) => {
|
|
||||||
setAllowSave(content !== "");
|
|
||||||
setEditorContentCache(content);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckBoxBtnClick = () => {
|
const handleCheckBoxBtnClick = () => {
|
||||||
if (!editorRef.current) {
|
if (!editorRef.current) {
|
||||||
return;
|
return;
|
||||||
@ -344,12 +372,6 @@ const MemoEditor = () => {
|
|||||||
editorRef.current?.focus();
|
editorRef.current?.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEditorBlur = () => {
|
|
||||||
// do nothing
|
|
||||||
};
|
|
||||||
|
|
||||||
const isEditing = Boolean(editorState.editMemoId && editorState.editMemoId !== UNKNOWN_ID);
|
|
||||||
|
|
||||||
const editorConfig = useMemo(
|
const editorConfig = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
className: `memo-editor`,
|
className: `memo-editor`,
|
||||||
@ -362,14 +384,15 @@ const MemoEditor = () => {
|
|||||||
[state.fullscreen, i18n.language]
|
[state.fullscreen, i18n.language]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const allowSave = (hasContent || state.resourceList.length > 0) && !state.isUploadingResource && !state.isRequesting;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`memo-editor-container ${isEditing ? "edit-ing" : ""} ${state.fullscreen ? "fullscreen" : ""}`}
|
className={`${className} memo-editor-container ${state.fullscreen ? "fullscreen" : ""}`}
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onDrop={handleDropEvent}
|
onDrop={handleDropEvent}
|
||||||
onFocus={handleEditorFocus}
|
onFocus={handleEditorFocus}
|
||||||
onBlur={handleEditorBlur}
|
|
||||||
onCompositionStart={() => setIsInIME(true)}
|
onCompositionStart={() => setIsInIME(true)}
|
||||||
onCompositionEnd={() => setIsInIME(false)}
|
onCompositionEnd={() => setIsInIME(false)}
|
||||||
>
|
>
|
||||||
@ -383,25 +406,20 @@ const MemoEditor = () => {
|
|||||||
<button className="action-btn">
|
<button className="action-btn">
|
||||||
<Icon.Code className="icon-img" onClick={handleCodeBlockBtnClick} />
|
<Icon.Code className="icon-img" onClick={handleCodeBlockBtnClick} />
|
||||||
</button>
|
</button>
|
||||||
<ResourceSelector />
|
<button className="action-btn">
|
||||||
|
<Icon.Image className="icon-img" onClick={handleUploadFileBtnClick} />
|
||||||
|
</button>
|
||||||
<button className="action-btn" onClick={handleFullscreenBtnClick}>
|
<button className="action-btn" onClick={handleFullscreenBtnClick}>
|
||||||
{state.fullscreen ? <Icon.Minimize className="icon-img" /> : <Icon.Maximize className="icon-img" />}
|
{state.fullscreen ? <Icon.Minimize className="icon-img" /> : <Icon.Maximize className="icon-img" />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ResourceListView />
|
<ResourceListView resourceList={state.resourceList} setResourceList={handleSetResourceList} />
|
||||||
<RelationListView />
|
<RelationListView relationList={state.relationList} setRelationList={handleSetRelationList} />
|
||||||
<div className="editor-footer-container">
|
<div className="editor-footer-container">
|
||||||
<MemoVisibilitySelector />
|
<MemoVisibilitySelector value={state.memoVisibility} onChange={handleMemoVisibilityChange} />
|
||||||
<div className="buttons-container">
|
<div className="buttons-container">
|
||||||
<button className={`action-btn cancel-btn ${isEditing ? "" : "!hidden"}`} onClick={handleCancelEdit}>
|
<button className="action-btn confirm-btn" disabled={!allowSave} onClick={handleSaveBtnClick}>
|
||||||
{t("editor.cancel-edit")}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className="action-btn confirm-btn"
|
|
||||||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
|
||||||
onClick={handleSaveBtnClick}
|
|
||||||
>
|
|
||||||
{t("editor.save")}
|
{t("editor.save")}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
import { Button, Checkbox } from "@mui/joy";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { toast } from "react-hot-toast";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import useLoading from "@/hooks/useLoading";
|
|
||||||
import { useEditorStore, useResourceStore } from "@/store/module";
|
|
||||||
import { getResourceUrl } from "@/utils/resource";
|
|
||||||
import Icon from "./Icon";
|
|
||||||
import { generateDialog } from "./Dialog";
|
|
||||||
import showPreviewImageDialog from "./PreviewImageDialog";
|
|
||||||
import "@/less/resources-selector-dialog.less";
|
|
||||||
|
|
||||||
type Props = DialogProps;
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
checkedArray: boolean[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const ResourcesSelectorDialog: React.FC<Props> = (props: Props) => {
|
|
||||||
const { destroy } = props;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const loadingState = useLoading();
|
|
||||||
const editorStore = useEditorStore();
|
|
||||||
const resourceStore = useResourceStore();
|
|
||||||
const resources = resourceStore.state.resources;
|
|
||||||
const [state, setState] = useState<State>({
|
|
||||||
checkedArray: [],
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
resourceStore
|
|
||||||
.fetchResourceList()
|
|
||||||
.catch((error) => {
|
|
||||||
console.error(error);
|
|
||||||
toast.error(error.response.data.message);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
loadingState.setFinish();
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const checkedResourceIdArray = editorStore.state.resourceList.map((resource) => resource.id);
|
|
||||||
setState({
|
|
||||||
checkedArray: resources.map((resource) => {
|
|
||||||
return checkedResourceIdArray.includes(resource.id);
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
}, [resources]);
|
|
||||||
|
|
||||||
const handlePreviewBtnClick = (resource: Resource) => {
|
|
||||||
const resourceUrl = getResourceUrl(resource);
|
|
||||||
if (resource.type.startsWith("image")) {
|
|
||||||
showPreviewImageDialog(
|
|
||||||
resources.filter((r) => r.type.startsWith("image")).map((r) => getResourceUrl(r)),
|
|
||||||
resources.findIndex((r) => r.id === resource.id)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
window.open(resourceUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleCheckboxChange = (index: number) => {
|
|
||||||
const newCheckedArr = state.checkedArray;
|
|
||||||
newCheckedArr[index] = !newCheckedArr[index];
|
|
||||||
setState({
|
|
||||||
checkedArray: newCheckedArr,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleConfirmBtnClick = () => {
|
|
||||||
const resourceList = resources.filter((_, index) => {
|
|
||||||
return state.checkedArray[index];
|
|
||||||
});
|
|
||||||
editorStore.setResourceList(resourceList);
|
|
||||||
destroy();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="dialog-header-container">
|
|
||||||
<p className="title-text">{t("common.resources")}</p>
|
|
||||||
<button className="btn close-btn" onClick={destroy}>
|
|
||||||
<Icon.X className="icon-img" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className="dialog-content-container">
|
|
||||||
{loadingState.isLoading ? (
|
|
||||||
<div className="loading-text-container">
|
|
||||||
<p className="tip-text">{t("resource.fetching-data")}</p>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="resource-table-container">
|
|
||||||
<div className="fields-container">
|
|
||||||
<span className="field-text name-text">{t("common.name")}</span>
|
|
||||||
<span className="field-text type-text">Type</span>
|
|
||||||
<span></span>
|
|
||||||
</div>
|
|
||||||
{resources.length === 0 ? (
|
|
||||||
<p className="tip-text">{t("resource.no-resources")}</p>
|
|
||||||
) : (
|
|
||||||
resources.map((resource, index) => (
|
|
||||||
<div key={resource.id} className="resource-container">
|
|
||||||
<span className="field-text name-text cursor-pointer" onClick={() => handlePreviewBtnClick(resource)}>
|
|
||||||
{resource.filename}
|
|
||||||
</span>
|
|
||||||
<span className="field-text type-text">{resource.type}</span>
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<Checkbox checked={state.checkedArray[index]} onChange={() => handleCheckboxChange(index)} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex justify-between w-full mt-2 px-2">
|
|
||||||
<span className="text-sm font-mono text-gray-400 leading-8">
|
|
||||||
{t("message.count-selected-resources")}: {state.checkedArray.filter((checked) => checked).length}
|
|
||||||
</span>
|
|
||||||
<div className="flex flex-row justify-start items-center">
|
|
||||||
<Button onClick={handleConfirmBtnClick}>{t("common.confirm")}</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function showResourcesSelectorDialog() {
|
|
||||||
generateDialog(
|
|
||||||
{
|
|
||||||
className: "resources-selector-dialog",
|
|
||||||
dialogName: "resources-selector-dialog",
|
|
||||||
},
|
|
||||||
ResourcesSelectorDialog,
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
}
|
|
@ -22,10 +22,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.edit-ing {
|
|
||||||
@apply border-blue-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .memo-editor {
|
> .memo-editor {
|
||||||
@apply mt-4 flex flex-col justify-start items-start relative w-full h-auto bg-inherit dark:text-gray-200;
|
@apply mt-4 flex flex-col justify-start items-start relative w-full h-auto bg-inherit dark:text-gray-200;
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
|||||||
import globalReducer from "./reducer/global";
|
import globalReducer from "./reducer/global";
|
||||||
import userReducer from "./reducer/user";
|
import userReducer from "./reducer/user";
|
||||||
import memoReducer from "./reducer/memo";
|
import memoReducer from "./reducer/memo";
|
||||||
import editorReducer from "./reducer/editor";
|
|
||||||
import shortcutReducer from "./reducer/shortcut";
|
import shortcutReducer from "./reducer/shortcut";
|
||||||
import filterReducer from "./reducer/filter";
|
import filterReducer from "./reducer/filter";
|
||||||
import resourceReducer from "./reducer/resource";
|
import resourceReducer from "./reducer/resource";
|
||||||
@ -17,7 +16,6 @@ const store = configureStore({
|
|||||||
user: userReducer,
|
user: userReducer,
|
||||||
memo: memoReducer,
|
memo: memoReducer,
|
||||||
tag: tagReducer,
|
tag: tagReducer,
|
||||||
editor: editorReducer,
|
|
||||||
shortcut: shortcutReducer,
|
shortcut: shortcutReducer,
|
||||||
filter: filterReducer,
|
filter: filterReducer,
|
||||||
resource: resourceReducer,
|
resource: resourceReducer,
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
import store, { useAppSelector } from "..";
|
|
||||||
import { setEditMemoId, setMemoVisibility, setRelationList, setResourceList } from "../reducer/editor";
|
|
||||||
|
|
||||||
export const useEditorStore = () => {
|
|
||||||
const state = useAppSelector((state) => state.editor);
|
|
||||||
|
|
||||||
return {
|
|
||||||
state,
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().editor;
|
|
||||||
},
|
|
||||||
setEditMemoWithId: (editMemoId: MemoId) => {
|
|
||||||
store.dispatch(setEditMemoId(editMemoId));
|
|
||||||
},
|
|
||||||
clearEditMemo: () => {
|
|
||||||
store.dispatch(setEditMemoId());
|
|
||||||
},
|
|
||||||
setMemoVisibility: (memoVisibility: Visibility) => {
|
|
||||||
store.dispatch(setMemoVisibility(memoVisibility));
|
|
||||||
},
|
|
||||||
setResourceList: (resourceList: Resource[]) => {
|
|
||||||
store.dispatch(setResourceList(resourceList));
|
|
||||||
},
|
|
||||||
setRelationList: (relationList: MemoRelation[]) => {
|
|
||||||
store.dispatch(setRelationList(relationList));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,4 +1,3 @@
|
|||||||
export * from "./editor";
|
|
||||||
export * from "./global";
|
export * from "./global";
|
||||||
export * from "./filter";
|
export * from "./filter";
|
||||||
export * from "./memo";
|
export * from "./memo";
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
memoVisibility: Visibility;
|
|
||||||
resourceList: Resource[];
|
|
||||||
relationList: MemoRelation[];
|
|
||||||
editMemoId?: MemoId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const editorSlice = createSlice({
|
|
||||||
name: "editor",
|
|
||||||
initialState: {
|
|
||||||
memoVisibility: "PRIVATE",
|
|
||||||
resourceList: [],
|
|
||||||
relationList: [],
|
|
||||||
} as State,
|
|
||||||
reducers: {
|
|
||||||
setEditMemoId: (state, action: PayloadAction<Option<MemoId>>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
editMemoId: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setMemoVisibility: (state, action: PayloadAction<Visibility>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
memoVisibility: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setResourceList: (state, action: PayloadAction<Resource[]>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
resourceList: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setRelationList: (state, action: PayloadAction<MemoRelation[]>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
relationList: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setEditMemoId, setMemoVisibility, setResourceList, setRelationList } = editorSlice.actions;
|
|
||||||
|
|
||||||
export default editorSlice.reducer;
|
|
Loading…
Reference in New Issue
Block a user