mirror of
https://github.com/usememos/memos.git
synced 2024-12-19 00:51:30 +03:00
chore: add data empty placeholder (#1913)
This commit is contained in:
parent
7e391bd53d
commit
0292f472e0
BIN
web/public/assets/empty.png
Normal file
BIN
web/public/assets/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 127 KiB |
@ -3,10 +3,10 @@ import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useMemoStore } from "@/store/module";
|
||||
import { getDateTimeString } from "@/helpers/datetime";
|
||||
import useToggle from "@/hooks/useToggle";
|
||||
import Icon from "./Icon";
|
||||
import MemoContent from "./MemoContent";
|
||||
import MemoResourceListView from "./MemoResourceListView";
|
||||
import { showCommonDialog } from "./Dialog/CommonDialog";
|
||||
import "@/less/memo.less";
|
||||
|
||||
interface Props {
|
||||
@ -17,19 +17,17 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
const { memo } = props;
|
||||
const { t } = useTranslation();
|
||||
const memoStore = useMemoStore();
|
||||
const [showConfirmDeleteBtn, toggleConfirmDeleteBtn] = useToggle(false);
|
||||
|
||||
const handleDeleteMemoClick = async () => {
|
||||
if (showConfirmDeleteBtn) {
|
||||
try {
|
||||
showCommonDialog({
|
||||
title: t("memo.delete-memo"),
|
||||
content: t("memo.delete-confirm"),
|
||||
style: "warning",
|
||||
dialogName: "delete-memo-dialog",
|
||||
onConfirm: async () => {
|
||||
await memoStore.deleteMemoById(memo.id);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
}
|
||||
} else {
|
||||
toggleConfirmDeleteBtn();
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRestoreMemoClick = async () => {
|
||||
@ -46,14 +44,8 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseLeaveMemoWrapper = () => {
|
||||
if (showConfirmDeleteBtn) {
|
||||
toggleConfirmDeleteBtn(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`memo-wrapper archived ${"memos-" + memo.id}`} onMouseLeave={handleMouseLeaveMemoWrapper}>
|
||||
<div className={`memo-wrapper archived ${"memos-" + memo.id}`}>
|
||||
<div className="memo-top-wrapper">
|
||||
<div className="status-text-container">
|
||||
<span className="time-text">{getDateTimeString(memo.updatedTs)}</span>
|
||||
@ -65,10 +57,7 @@ const ArchivedMemo: React.FC<Props> = (props: Props) => {
|
||||
</button>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("common.delete")} placement="top">
|
||||
<button
|
||||
onClick={handleDeleteMemoClick}
|
||||
className={`text-gray-500 dark:text-gray-400 ${showConfirmDeleteBtn ? "text-red-600" : ""}`}
|
||||
>
|
||||
<button onClick={handleDeleteMemoClick} className="text-gray-500 dark:text-gray-400">
|
||||
<Icon.Trash className="w-4 h-auto cursor-pointer" />
|
||||
</button>
|
||||
</Tooltip>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { Button, Divider, Input, Option, Select, Typography } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "react-hot-toast";
|
||||
import * as api from "@/helpers/api";
|
||||
import { UNKNOWN_ID } from "@/helpers/consts";
|
||||
import { absolutifyLink } from "@/helpers/utils";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Icon from "./Icon";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const templateList: IdentityProvider[] = [
|
||||
{
|
||||
@ -170,7 +170,6 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||
if (type === "OAUTH2") {
|
||||
if (
|
||||
oauth2Config.clientId === "" ||
|
||||
oauth2Config.clientSecret === "" ||
|
||||
oauth2Config.authUrl === "" ||
|
||||
oauth2Config.tokenUrl === "" ||
|
||||
oauth2Config.userInfoUrl === "" ||
|
||||
@ -179,7 +178,13 @@ const CreateIdentityProviderDialog: React.FC<Props> = (props: Props) => {
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (isCreating) {
|
||||
if (oauth2Config.clientSecret === "") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import * as api from "@/helpers/api";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import Icon from "./Icon";
|
||||
import RequiredBadge from "./RequiredBadge";
|
||||
import HelpButton from "./kit/HelpButton";
|
||||
import LearnMore from "./LearnMore";
|
||||
|
||||
interface Props extends DialogProps {
|
||||
storage?: ObjectStorage;
|
||||
@ -183,11 +183,9 @@ const CreateStorageServiceDialog: React.FC<Props> = (props: Props) => {
|
||||
onChange={(e) => setPartialS3Config({ bucket: e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
<div className="flex flex-row">
|
||||
<Typography className="!mb-1" level="body2">
|
||||
{t("setting.storage-section.path")}
|
||||
</Typography>
|
||||
<HelpButton text={t("setting.storage-section.path-description")} url="https://usememos.com/docs/local-storage" />
|
||||
<div className="flex flex-row items-center mb-1">
|
||||
<Typography level="body2">{t("setting.storage-section.path")}</Typography>
|
||||
<LearnMore className="ml-1" title={t("setting.storage-section.path-description")} url="https://usememos.com/docs/local-storage" />
|
||||
</div>
|
||||
<Input
|
||||
className="mb-2"
|
||||
|
9
web/src/components/Empty.tsx
Normal file
9
web/src/components/Empty.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
const Empty = () => {
|
||||
return (
|
||||
<div className="mx-auto">
|
||||
<img className="w-24 h-auto dark:opacity-40" src="/assets/empty.png" alt="" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Empty;
|
@ -132,12 +132,12 @@ const Header = () => {
|
||||
<Icon.Settings className="mr-3 w-6 h-auto opacity-70" /> {t("common.settings")}
|
||||
</>
|
||||
</NavLink>
|
||||
<div className="pr-3 pl-1 w-full">
|
||||
<div id="header-memo">
|
||||
<button
|
||||
className="mt-2 w-full py-3 rounded-full flex flex-row justify-center items-center bg-green-600 text-white hover:shadow hover:opacity-80"
|
||||
className="px-4 pr-5 py-2 rounded-full flex flex-row items-center text-lg text-gray-800 dark:text-gray-300 bg-gray-50/100 dark:bg-zinc-700/20 hover:bg-white hover:shadow dark:hover:bg-zinc-700"
|
||||
onClick={() => showMemoEditorDialog()}
|
||||
>
|
||||
<Icon.Edit3 className="w-4 h-auto mr-1" />
|
||||
<Icon.Feather className="mr-3 w-6 h-auto opacity-70" />
|
||||
{t("common.new")}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -21,12 +21,13 @@ import "@/less/memo.less";
|
||||
|
||||
interface Props {
|
||||
memo: Memo;
|
||||
readonly?: boolean;
|
||||
showCreator?: boolean;
|
||||
showVisibility?: boolean;
|
||||
showRelatedMemos?: boolean;
|
||||
}
|
||||
|
||||
const Memo: React.FC<Props> = (props: Props) => {
|
||||
const { memo, readonly, showRelatedMemos } = props;
|
||||
const { memo, showCreator, showVisibility, showRelatedMemos } = props;
|
||||
const { t, i18n } = useTranslation();
|
||||
const filterStore = useFilterStore();
|
||||
const userStore = useUserStore();
|
||||
@ -35,7 +36,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
const [createdTimeStr, setCreatedTimeStr] = useState<string>(getRelativeTimeString(memo.displayTs));
|
||||
const [relatedMemoList, setRelatedMemoList] = useState<Memo[]>([]);
|
||||
const memoContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isVisitorMode = userStore.isVisitorMode() || readonly;
|
||||
const readonly = userStore.isVisitorMode() || userStore.getCurrentUserId() !== memo.creatorId;
|
||||
|
||||
useEffect(() => {
|
||||
Promise.allSettled(memo.relationList.map((memoRelation) => memoCacheStore.getOrFetchMemoById(memoRelation.relatedMemoId))).then(
|
||||
@ -134,7 +135,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
filterStore.setTagFilter(tagName);
|
||||
}
|
||||
} else if (targetEl.classList.contains("todo-block")) {
|
||||
if (isVisitorMode) {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -173,7 +174,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
const handleMemoContentDoubleClick = (e: React.MouseEvent) => {
|
||||
if (isVisitorMode) {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -216,60 +217,62 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
<Link className="time-text" to={`/m/${memo.id}`} onClick={handleMemoCreatedTimeClick}>
|
||||
{createdTimeStr}
|
||||
</Link>
|
||||
{isVisitorMode && (
|
||||
{showCreator && (
|
||||
<Link className="name-text" to={`/u/${memo.creatorId}`}>
|
||||
@{memo.creatorName}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
{!isVisitorMode && (
|
||||
<div className="btns-container space-x-2">
|
||||
{memo.visibility !== "PRIVATE" && (
|
||||
<Tooltip title={t(`memo.visibility.${memo.visibility.toLowerCase()}`)} placement="top">
|
||||
<div onClick={() => handleMemoVisibilityClick(memo.visibility)}>
|
||||
{memo.visibility === "PUBLIC" ? (
|
||||
<Icon.Globe2 className="w-4 h-auto cursor-pointer rounded text-green-600" />
|
||||
) : (
|
||||
<Icon.Users className="w-4 h-auto cursor-pointer rounded text-gray-500 dark:text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
{memo.pinned && <Icon.Bookmark className="w-4 h-auto rounded text-green-600" />}
|
||||
<span className="btn more-action-btn">
|
||||
<Icon.MoreHorizontal className="icon-img" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container min-w-[6em]">
|
||||
<span className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
{memo.pinned ? <Icon.BookmarkMinus className="w-4 h-auto mr-2" /> : <Icon.BookmarkPlus className="w-4 h-auto mr-2" />}
|
||||
{memo.pinned ? t("common.unpin") : t("common.pin")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditMemoClick}>
|
||||
<Icon.Edit3 className="w-4 h-auto mr-2" />
|
||||
{t("common.edit")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleGenerateMemoImageBtnClick}>
|
||||
<Icon.Share className="w-4 h-auto mr-2" />
|
||||
{t("common.share")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
<Icon.Link className="w-4 h-auto mr-2" />
|
||||
Mark
|
||||
</span>
|
||||
<Divider className="!my-1" />
|
||||
<span className="btn text-orange-500" onClick={handleArchiveMemoClick}>
|
||||
<Icon.Archive className="w-4 h-auto mr-2" />
|
||||
{t("common.archive")}
|
||||
</span>
|
||||
<span className="btn text-red-600" onClick={handleDeleteMemoClick}>
|
||||
<Icon.Trash className="w-4 h-auto mr-2" />
|
||||
{t("common.delete")}
|
||||
</span>
|
||||
<div className="btns-container space-x-2">
|
||||
{showVisibility && memo.visibility !== "PRIVATE" && (
|
||||
<Tooltip title={t(`memo.visibility.${memo.visibility.toLowerCase()}`)} placement="top">
|
||||
<div onClick={() => handleMemoVisibilityClick(memo.visibility)}>
|
||||
{memo.visibility === "PUBLIC" ? (
|
||||
<Icon.Globe2 className="w-4 h-auto cursor-pointer rounded text-green-600" />
|
||||
) : (
|
||||
<Icon.Users className="w-4 h-auto cursor-pointer rounded text-gray-500 dark:text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
)}
|
||||
{memo.pinned && <Icon.Bookmark className="w-4 h-auto rounded text-green-600" />}
|
||||
{!readonly && (
|
||||
<>
|
||||
<span className="btn more-action-btn">
|
||||
<Icon.MoreHorizontal className="icon-img" />
|
||||
</span>
|
||||
<div className="more-action-btns-wrapper">
|
||||
<div className="more-action-btns-container min-w-[6em]">
|
||||
<span className="btn" onClick={handleTogglePinMemoBtnClick}>
|
||||
{memo.pinned ? <Icon.BookmarkMinus className="w-4 h-auto mr-2" /> : <Icon.BookmarkPlus className="w-4 h-auto mr-2" />}
|
||||
{memo.pinned ? t("common.unpin") : t("common.pin")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleEditMemoClick}>
|
||||
<Icon.Edit3 className="w-4 h-auto mr-2" />
|
||||
{t("common.edit")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleGenerateMemoImageBtnClick}>
|
||||
<Icon.Share className="w-4 h-auto mr-2" />
|
||||
{t("common.share")}
|
||||
</span>
|
||||
<span className="btn" onClick={handleMarkMemoClick}>
|
||||
<Icon.Link className="w-4 h-auto mr-2" />
|
||||
Mark
|
||||
</span>
|
||||
<Divider className="!my-1" />
|
||||
<span className="btn text-orange-500" onClick={handleArchiveMemoClick}>
|
||||
<Icon.Archive className="w-4 h-auto mr-2" />
|
||||
{t("common.archive")}
|
||||
</span>
|
||||
<span className="btn text-red-600" onClick={handleDeleteMemoClick}>
|
||||
<Icon.Trash className="w-4 h-auto mr-2" />
|
||||
{t("common.delete")}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<MemoContent
|
||||
content={memo.content}
|
||||
@ -282,14 +285,14 @@ const Memo: React.FC<Props> = (props: Props) => {
|
||||
|
||||
{showRelatedMemos && relatedMemoList.length > 0 && (
|
||||
<>
|
||||
<p className="text-sm dark:text-gray-300 mt-4 mb-1 pl-4 opacity-50 flex flex-row items-center">
|
||||
<p className="text-sm dark:text-gray-300 my-2 pl-4 opacity-50 flex flex-row items-center">
|
||||
<Icon.Link className="w-4 h-auto mr-1" />
|
||||
<span>Related memos</span>
|
||||
</p>
|
||||
{relatedMemoList.map((relatedMemo) => {
|
||||
return (
|
||||
<div key={relatedMemo.id} className="w-full">
|
||||
<Memo memo={relatedMemo} readonly={readonly} />
|
||||
<Memo memo={relatedMemo} showCreator />
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -24,14 +24,11 @@ interface State {
|
||||
const MemoContent: React.FC<Props> = (props: Props) => {
|
||||
const { className, content, showFull, onMemoContentClick, onMemoContentDoubleClick } = props;
|
||||
const { t } = useTranslation();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const [state, setState] = useState<State>({
|
||||
expandButtonStatus: -1,
|
||||
});
|
||||
const memoContentContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
//variable for auto-collapse
|
||||
const userStore = useUserStore();
|
||||
const isVisitorMode = userStore.isVisitorMode();
|
||||
const autoCollapse: boolean = isVisitorMode ? true : (userStore.state.user as User).localSetting.enableAutoCollapse;
|
||||
|
||||
|
@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFilterStore, useMemoStore, useShortcutStore, useUserStore } from "@/store/module";
|
||||
import { TAG_REG, LINK_REG } from "@/labs/marked/parser";
|
||||
import { TAG_REG, LINK_REG, PLAIN_LINK_REG } from "@/labs/marked/parser";
|
||||
import { getTimeStampByDate } from "@/helpers/datetime";
|
||||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import { checkShouldShowMemoWithFilters } from "@/helpers/filter";
|
||||
import Empty from "./Empty";
|
||||
import Memo from "./Memo";
|
||||
import "@/less/memo-list.less";
|
||||
import { PLAIN_LINK_REG } from "@/labs/marked/parser";
|
||||
|
||||
const MemoList = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -153,7 +153,7 @@ const MemoList = () => {
|
||||
return (
|
||||
<div className="memo-list-container">
|
||||
{sortedMemos.map((memo) => (
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} />
|
||||
<Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} showVisibility />
|
||||
))}
|
||||
{isFetching ? (
|
||||
<div className="status-text-container fetching-tip">
|
||||
@ -163,10 +163,11 @@ const MemoList = () => {
|
||||
<div ref={statusRef} className="status-text-container">
|
||||
<p className="status-text">
|
||||
{isComplete ? (
|
||||
sortedMemos.length === 0 ? (
|
||||
t("message.no-memos")
|
||||
) : (
|
||||
t("message.memos-ready")
|
||||
sortedMemos.length === 0 && (
|
||||
<div className="w-full mt-4 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<>
|
||||
|
@ -28,6 +28,8 @@ const MyAccountSection = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const exampleWithCurl = `curl '${openAPIRoute}' -H 'Content-Type: application/json' --data-raw '{"content":"Hello world!"}'`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="section-container account-section-container">
|
||||
@ -48,22 +50,15 @@ const MyAccountSection = () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="section-container openapi-section-container mt-6">
|
||||
<p className="title-text">{t("setting.account-section.openapi-title")}</p>
|
||||
<Input className="w-full mb-3" value={openAPIRoute} readOnly />
|
||||
<Button color="warning" className="mb-4" onClick={handleResetOpenIdBtnClick}>
|
||||
{t("setting.account-section.reset-api")} <Icon.RefreshCw className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
<Textarea
|
||||
className="w-full !font-mono !text-sm mt-4"
|
||||
value={`POST ${openAPIRoute}\nContent-type: application/json\n{\n "content": "${t(
|
||||
"setting.account-section.openapi-sample-post",
|
||||
{
|
||||
url: window.location.origin,
|
||||
interpolation: { escapeValue: false },
|
||||
}
|
||||
)}"\n}`}
|
||||
readOnly
|
||||
/>
|
||||
<p className="title-text">Open ID</p>
|
||||
<div className="w-full flex flex-row justify-start items-center">
|
||||
<Input className="grow mr-2" value={user.openId} readOnly />
|
||||
<Button className="shrink-0" color="warning" onClick={handleResetOpenIdBtnClick}>
|
||||
<Icon.RefreshCw className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<p className="title-text">Open API Example with cURL</p>
|
||||
<Textarea className="w-full !font-mono !text-sm whitespace-pre" value={exampleWithCurl} readOnly />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
@ -7,7 +7,7 @@ import { useGlobalStore, useUserStore } from "@/store/module";
|
||||
import { VISIBILITY_SELECTOR_ITEMS } from "@/helpers/consts";
|
||||
import AppearanceSelect from "../AppearanceSelect";
|
||||
import LocaleSelect from "../LocaleSelect";
|
||||
import HelpButton from "../kit/HelpButton";
|
||||
import LearnMore from "../LearnMore";
|
||||
import "@/less/settings/preferences-section.less";
|
||||
|
||||
const PreferencesSection = () => {
|
||||
@ -141,7 +141,7 @@ const PreferencesSection = () => {
|
||||
<div className="mb-2 w-full flex flex-row justify-between items-center">
|
||||
<div className="w-auto flex items-center">
|
||||
<span className="text-sm mr-1">{t("setting.preference-section.telegram-user-id")}</span>
|
||||
<HelpButton icon="help" url="https://usememos.com/docs/integration/telegram-bot" />
|
||||
<LearnMore url="https://usememos.com/docs/integration/telegram-bot" />
|
||||
</div>
|
||||
<Button onClick={handleSaveTelegramUserId}>{t("common.save")}</Button>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Divider } from "@mui/joy";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import * as api from "@/helpers/api";
|
||||
import { Divider } from "@mui/joy";
|
||||
import showCreateIdentityProviderDialog from "../CreateIdentityProviderDialog";
|
||||
import Dropdown from "../kit/Dropdown";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import HelpButton from "../kit/HelpButton";
|
||||
import LearnMore from "../LearnMore";
|
||||
|
||||
const SSOSection = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -43,7 +43,7 @@ const SSOSection = () => {
|
||||
<div className="section-container">
|
||||
<div className="mb-2 w-full flex flex-row justify-start items-center gap-1">
|
||||
<span className="font-mono text-sm text-gray-400">{t("setting.sso-section.sso-list")}</span>
|
||||
<HelpButton icon="help" url="https://usememos.com/docs/keycloak" />
|
||||
<LearnMore url="https://usememos.com/docs/keycloak" />
|
||||
<button
|
||||
className="btn-normal px-2 py-0 ml-1"
|
||||
onClick={() => showCreateIdentityProviderDialog(undefined, fetchIdentityProviderList)}
|
||||
@ -57,7 +57,7 @@ const SSOSection = () => {
|
||||
{identityProviderList.map((identityProvider) => (
|
||||
<div
|
||||
key={identityProvider.id}
|
||||
className="py-2 w-full border-t last:border-b dark:border-zinc-700 flex flex-row items-center justify-between"
|
||||
className="py-2 w-full border-b last:border-b dark:border-zinc-700 flex flex-row items-center justify-between"
|
||||
>
|
||||
<div className="flex flex-row items-center">
|
||||
<p className="ml-2">
|
||||
|
@ -8,7 +8,7 @@ import showCreateStorageServiceDialog from "../CreateStorageServiceDialog";
|
||||
import showUpdateLocalStorageDialog from "../UpdateLocalStorageDialog";
|
||||
import Dropdown from "../kit/Dropdown";
|
||||
import { showCommonDialog } from "../Dialog/CommonDialog";
|
||||
import HelpButton from "../kit/HelpButton";
|
||||
import LearnMore from "../LearnMore";
|
||||
|
||||
const StorageSection = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -76,7 +76,7 @@ const StorageSection = () => {
|
||||
<Divider />
|
||||
<div className="mt-4 mb-2 w-full flex flex-row justify-start items-center gap-1">
|
||||
<span className="font-mono text-sm text-gray-400">{t("setting.storage-section.storage-services-list")}</span>
|
||||
<HelpButton className="btn" icon="info" url="https://usememos.com/docs/storage" />
|
||||
<LearnMore url="https://usememos.com/docs/storage" />
|
||||
<button className="btn-normal px-2 py-0 ml-1" onClick={() => showCreateStorageServiceDialog(undefined, fetchStorageList)}>
|
||||
{t("common.create")}
|
||||
</button>
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, Divider, Input, Switch, Textarea } from "@mui/joy";
|
||||
import { Button, Divider, Input, Switch, Textarea, Tooltip } from "@mui/joy";
|
||||
import { formatBytes } from "@/helpers/utils";
|
||||
import { useGlobalStore } from "@/store/module";
|
||||
import * as api from "@/helpers/api";
|
||||
import HelpButton from "../kit/HelpButton";
|
||||
import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog";
|
||||
import Icon from "../Icon";
|
||||
import LearnMore from "../LearnMore";
|
||||
import "@/less/settings/system-section.less";
|
||||
|
||||
interface State {
|
||||
@ -224,7 +225,9 @@ const SystemSection = () => {
|
||||
<div className="form-label">
|
||||
<div className="flex flex-row items-center">
|
||||
<span className="text-sm mr-1">{t("setting.system-section.max-upload-size")}</span>
|
||||
<HelpButton icon="info" hint={t("setting.system-section.max-upload-size-hint")} />
|
||||
<Tooltip title={t("setting.system-section.max-upload-size-hint")} placement="top">
|
||||
<Icon.HelpCircle className="w-4 h-auto" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
className="w-16"
|
||||
@ -241,9 +244,9 @@ const SystemSection = () => {
|
||||
<div className="flex flex-row items-center">
|
||||
<div className="w-auto flex items-center">
|
||||
<span className="text-sm mr-1">{t("setting.system-section.telegram-bot-token")}</span>
|
||||
<HelpButton
|
||||
hint={t("setting.system-section.telegram-bot-token-description")}
|
||||
<LearnMore
|
||||
url="https://usememos.com/docs/integration/telegram-bot"
|
||||
title={t("setting.system-section.telegram-bot-token-description")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,7 +40,7 @@ const UserBanner = () => {
|
||||
<Dropdown
|
||||
className="w-full"
|
||||
trigger={
|
||||
<div className="px-3 py-2 max-w-full flex flex-row justify-start items-center cursor-pointer rounded-lg hover:shadow hover:bg-white dark:hover:bg-zinc-700">
|
||||
<div className="px-4 py-2 max-w-full flex flex-row justify-start items-center cursor-pointer rounded-full hover:shadow hover:bg-white dark:hover:bg-zinc-700">
|
||||
<UserAvatar avatarUrl={user?.avatarUrl} />
|
||||
<span className="px-1 text-lg font-medium text-slate-800 dark:text-gray-200 shrink truncate">
|
||||
{userStore.isVisitorMode() ? systemStatus.customizedProfile.name : username}
|
||||
|
@ -1,283 +0,0 @@
|
||||
import { ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button, IconButton, Tooltip } from "@mui/joy";
|
||||
import { generateDialog } from "../Dialog";
|
||||
import Icon from "../Icon";
|
||||
|
||||
const openUrl = (url?: string) => {
|
||||
window.open(url, "_blank");
|
||||
};
|
||||
|
||||
/** Options for {@link HelpButton} */
|
||||
interface HelpProps {
|
||||
/**
|
||||
* Plain text to show in the dialog.
|
||||
*
|
||||
* If the text contains "\n", it will be split to multiple paragraphs.
|
||||
*/
|
||||
text?: string;
|
||||
/**
|
||||
* The title of the dialog.
|
||||
*
|
||||
* If not provided, the title will be set according to the `icon` prop.
|
||||
*/
|
||||
title?: string;
|
||||
/**
|
||||
* External documentation URL.
|
||||
*
|
||||
* If provided, this will be shown as a link button in the bottom of the dialog.
|
||||
*
|
||||
* If provided alone, the button will just open the URL in a new tab.
|
||||
*
|
||||
* @param {string} url - External URL to the documentation.
|
||||
*/
|
||||
url?: string;
|
||||
/**
|
||||
* The tooltip of the button.
|
||||
*/
|
||||
hint?: string | "none";
|
||||
/**
|
||||
* The placement of the hovering hint.
|
||||
* @defaultValue "top"
|
||||
*/
|
||||
hintPlacement?: "top" | "bottom" | "left" | "right";
|
||||
/**
|
||||
* The icon to show in the button.
|
||||
*
|
||||
* Also used to infer `title` and `hint`, if they are not provided.
|
||||
*
|
||||
* @defaultValue Icon.HelpCircle
|
||||
* @see {@link Icon.LucideIcon}
|
||||
*/
|
||||
icon?: Icon.LucideIcon | "link" | "info" | "help" | "alert" | "warn";
|
||||
/**
|
||||
* The className for the button.
|
||||
* @defaultValue `!-mt-2` (aligns the button vertically with nearby text)
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* The color of the button.
|
||||
* @defaultValue "neutral"
|
||||
*/
|
||||
color?: "primary" | "neutral" | "danger" | "info" | "success" | "warning";
|
||||
/**
|
||||
* The variant of the button.
|
||||
* @defaultValue "plain"
|
||||
*/
|
||||
variant?: "plain" | "outlined" | "soft" | "solid";
|
||||
/**
|
||||
* The size of the button.
|
||||
* @defaultValue "md"
|
||||
*/
|
||||
size?: "sm" | "md" | "lg";
|
||||
/**
|
||||
* `ReactNode` HTML content to show in the dialog.
|
||||
*
|
||||
* If provided, will be shown before `text`.
|
||||
*
|
||||
* You'll probably want to use `text` instead.
|
||||
*/
|
||||
children?: ReactNode | undefined;
|
||||
}
|
||||
|
||||
interface HelpDialogProps extends HelpProps, DialogProps {}
|
||||
|
||||
const HelpfulDialog: React.FC<HelpDialogProps> = (props: HelpDialogProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { children, destroy, icon } = props;
|
||||
const LucideIcon = icon as Icon.LucideIcon;
|
||||
const handleCloseBtnClick = () => {
|
||||
destroy();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="dialog-header-container">
|
||||
<LucideIcon size="24" />
|
||||
<p className="title-text text-left">{props.title}</p>
|
||||
<button className="btn close-btn" onClick={handleCloseBtnClick}>
|
||||
<Icon.X />
|
||||
</button>
|
||||
</div>
|
||||
<div className="dialog-content-container max-w-sm">
|
||||
{children}
|
||||
{props.text
|
||||
? props.text.split(/\n|\\n/).map((text) => {
|
||||
return (
|
||||
<p key={text} className="mt-2 break-words text-justify">
|
||||
{text}
|
||||
</p>
|
||||
);
|
||||
})
|
||||
: null}
|
||||
<div className="mt-2 w-full flex flex-row justify-end space-x-2">
|
||||
{props.url ? (
|
||||
<Button className="btn-normal" variant="outlined" color={props.color} onClick={() => openUrl(props.url)}>
|
||||
{t("common.learn-more")}
|
||||
<Icon.ExternalLink className="ml-1 w-4 h-4 opacity-80" />
|
||||
</Button>
|
||||
) : null}
|
||||
<Button className="btn-normal" variant="outlined" color={props.color} onClick={handleCloseBtnClick}>
|
||||
{t("common.close")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function showHelpDialog(props: HelpProps) {
|
||||
generateDialog(
|
||||
{
|
||||
className: "help-dialog",
|
||||
dialogName: "help-dialog",
|
||||
clickSpaceDestroy: true,
|
||||
},
|
||||
HelpfulDialog,
|
||||
props
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a helpful `IconButton` that behaves differently depending on the props.
|
||||
*
|
||||
* The main purpose of this component is to avoid UI clutter.
|
||||
*
|
||||
* Use the property `icon` to set the icon and infer the title and hint automatically.
|
||||
*
|
||||
* Use cases:
|
||||
* - Button with just a hover hint
|
||||
* - Button with a hover hint and link
|
||||
* - Button with a hover hint that opens a dialog with text and a link.
|
||||
*
|
||||
* @example
|
||||
* <Helpful hint="Hint" />
|
||||
* <Helpful hint="This is a hint with a link" url="https://usememos.com/" />
|
||||
* <Helpful icon="warn" text={t("i18n.key.long-dialog-text")} url="https://usememos.com/" />
|
||||
* <Helpful />
|
||||
*
|
||||
* <div className="flex flex-row">
|
||||
* <span className="ml-2">Sample alignment</span>
|
||||
* <Helpful hint="Button with hint" />
|
||||
* </div>
|
||||
|
||||
* @param props.title - The title of the dialog. Defaults to "Learn more" i18n key.
|
||||
* @param props.text - Plain text to show in the dialog. Line breaks are supported.
|
||||
* @param props.url - External memos documentation URL.
|
||||
* @param props.hint - The hint when hovering the button.
|
||||
* @param props.hintPlacement - The placement of the hovering hint. Defaults to "top".
|
||||
* @param props.icon - The icon to show in the button.
|
||||
* @param props.className - The class name for the button.
|
||||
* @param {HelpProps} props - See {@link HelpDialogProps} for all exposed props.
|
||||
*/
|
||||
const HelpButton = (props: HelpProps): JSX.Element => {
|
||||
const { t } = useTranslation();
|
||||
const color = props.color ?? "neutral";
|
||||
const variant = props.variant ?? "plain";
|
||||
const className = props.className ?? "";
|
||||
const hintPlacement = props.hintPlacement ?? "top";
|
||||
const iconButtonSize = "sm";
|
||||
|
||||
const dialogAvailable = props.text || props.children;
|
||||
const clickActionAvailable = props.url || dialogAvailable;
|
||||
const onlyUrlAvailable = props.url && !dialogAvailable;
|
||||
|
||||
let LucideIcon = (() => {
|
||||
switch (props.icon) {
|
||||
case "info":
|
||||
return Icon.Info;
|
||||
case "help":
|
||||
return Icon.HelpCircle;
|
||||
case "warn":
|
||||
case "alert":
|
||||
return Icon.AlertTriangle;
|
||||
case "link":
|
||||
return Icon.ExternalLink;
|
||||
default:
|
||||
return Icon.HelpCircle;
|
||||
}
|
||||
})() as Icon.LucideIcon;
|
||||
|
||||
const hint = (() => {
|
||||
switch (props.hint) {
|
||||
case undefined:
|
||||
return t(
|
||||
(() => {
|
||||
if (!dialogAvailable) {
|
||||
LucideIcon = Icon.ExternalLink;
|
||||
}
|
||||
switch (LucideIcon) {
|
||||
case Icon.Info:
|
||||
return "common.dialog.info";
|
||||
case Icon.AlertTriangle:
|
||||
return "common.dialog.warning";
|
||||
case Icon.ExternalLink:
|
||||
return "common.learn-more";
|
||||
case Icon.HelpCircle:
|
||||
default:
|
||||
return "common.dialog.help";
|
||||
}
|
||||
})()
|
||||
);
|
||||
case "":
|
||||
case "none":
|
||||
case "false":
|
||||
case "disabled":
|
||||
return undefined;
|
||||
default:
|
||||
return props.hint;
|
||||
}
|
||||
})();
|
||||
|
||||
const sizePx = (() => {
|
||||
switch (props.size) {
|
||||
case "sm":
|
||||
return 14;
|
||||
case "lg":
|
||||
return 18;
|
||||
case "md":
|
||||
default:
|
||||
return 16;
|
||||
}
|
||||
})();
|
||||
|
||||
if (!dialogAvailable && !clickActionAvailable && !props.hint) {
|
||||
return (
|
||||
<IconButton className={className} color={color} variant={variant} size={iconButtonSize}>
|
||||
<LucideIcon size={sizePx} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
const wrapInTooltip = (element: JSX.Element) => {
|
||||
if (!hint) {
|
||||
return element;
|
||||
}
|
||||
return (
|
||||
<Tooltip placement={hintPlacement} title={hint} color={color} variant={variant} size={props.size}>
|
||||
{element}
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
if (clickActionAvailable) {
|
||||
props = { ...props, title: props.title ?? hint, hint: hint, color: color, variant: variant, icon: LucideIcon };
|
||||
const clickAction = () => {
|
||||
dialogAvailable ? showHelpDialog(props) : openUrl(props.url);
|
||||
};
|
||||
LucideIcon = dialogAvailable || onlyUrlAvailable ? LucideIcon : Icon.ExternalLink;
|
||||
return wrapInTooltip(
|
||||
<IconButton className={className} color={color} variant={variant} size={iconButtonSize} onClick={clickAction}>
|
||||
<LucideIcon size={sizePx} />
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
return wrapInTooltip(
|
||||
<IconButton className={className} color={color} variant={variant} size={iconButtonSize}>
|
||||
<LucideIcon size={sizePx} />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default HelpButton;
|
@ -1,5 +1,5 @@
|
||||
.filter-query-container {
|
||||
@apply flex flex-row justify-start items-start w-full flex-wrap px-2 pt-2 text-sm font-mono leading-7 dark:text-gray-300;
|
||||
@apply flex flex-row justify-start items-start w-full flex-wrap px-2 pb-2 text-sm font-mono leading-7 dark:text-gray-300;
|
||||
|
||||
> .filter-item-container {
|
||||
@apply flex flex-row justify-start items-center px-2 mr-2 cursor-pointer dark:text-gray-300 bg-gray-200 dark:bg-zinc-700 rounded whitespace-nowrap truncate hover:line-through;
|
||||
|
@ -1,5 +1,5 @@
|
||||
.memo-wrapper {
|
||||
@apply relative flex flex-col justify-start items-start w-full p-4 pt-3 mt-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-600 hover:border-gray-200 dark:hover:border-zinc-600;
|
||||
@apply relative flex flex-col justify-start items-start w-full p-4 pt-3 mb-2 bg-white dark:bg-zinc-700 rounded-lg border border-white dark:border-zinc-600 hover:border-gray-200 dark:hover:border-zinc-600;
|
||||
|
||||
&.pinned {
|
||||
@apply border-gray-200 border-2 dark:border-zinc-600;
|
||||
|
@ -26,7 +26,7 @@
|
||||
}
|
||||
|
||||
> .section-content-container {
|
||||
@apply w-full sm:w-auto pl-2 grow flex flex-col justify-start items-start h-full;
|
||||
@apply w-full sm:w-auto pl-2 pb-4 grow flex flex-col justify-start items-start h-full;
|
||||
|
||||
> .section-container {
|
||||
@apply flex flex-col justify-start items-start w-full;
|
||||
|
@ -324,9 +324,8 @@
|
||||
"or": "Or"
|
||||
},
|
||||
"message": {
|
||||
"no-memos": "no memos 🌃",
|
||||
"no-data": "Maybe no data was found, maybe it should be another option.",
|
||||
"memos-ready": "all memos are ready 🎉",
|
||||
"no-resource": "no resource 🌃",
|
||||
"resource-ready": "all resource are ready 🎉",
|
||||
"restored-successfully": "Restored successfully",
|
||||
"memo-updated-datetime": "Memo created datetime changed.",
|
||||
|
@ -225,8 +225,7 @@
|
||||
"memo-updated-datetime": "备忘录创建日期时间已更改。",
|
||||
"memos-ready": "所有备忘录已就绪 🎉",
|
||||
"new-password-not-match": "新密码不匹配。",
|
||||
"no-memos": "没有备忘录了 🌃",
|
||||
"no-resource": "没有资源了 🌃",
|
||||
"no-data": "也许寻觅无果,也许可重新设定标准。",
|
||||
"not-allow-chinese": "不允许包含中文",
|
||||
"not-allow-space": "不允许包含空格",
|
||||
"page-not-found": "404 - 未找到网页 😥",
|
||||
@ -450,4 +449,4 @@
|
||||
"tag-name": "标签名称",
|
||||
"tip-text": "输入`#tag`来创建标签"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -339,9 +339,8 @@
|
||||
"or": "或"
|
||||
},
|
||||
"message": {
|
||||
"no-memos": "沒有 Memo 了 🌃",
|
||||
"no-data": "或許尋覓虛空,或者改換選擇之軌跡。",
|
||||
"memos-ready": "所有 Memo 已就緒 🎉",
|
||||
"no-resource": "沒有 Resource 了 🌃",
|
||||
"resource-ready": "所有 Resource 已就緒 🎉",
|
||||
"restored-successfully": "還原成功",
|
||||
"memo-updated-datetime": "Memo 建立日期時間已變更。",
|
||||
@ -451,4 +450,4 @@
|
||||
"search": {
|
||||
"quickly-filter": "快速過濾"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { useMemoStore } from "@/store/module";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import ArchivedMemo from "@/components/ArchivedMemo";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import Empty from "@/components/Empty";
|
||||
import "@/less/archived.less";
|
||||
|
||||
const Archived = () => {
|
||||
@ -30,7 +31,7 @@ const Archived = () => {
|
||||
}, [memos]);
|
||||
|
||||
return (
|
||||
<section className="w-full min-h-full flex flex-col md:flex-row justify-start items-start px-4 sm:px-2 pt-2 pb-8 bg-zinc-100 dark:bg-zinc-800">
|
||||
<section className="w-full min-h-full flex flex-col md:flex-row justify-start items-start px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
|
||||
<MobileHeader showSearch={false} />
|
||||
<div className="archived-memo-page">
|
||||
{loadingState.isLoading ? (
|
||||
@ -38,8 +39,9 @@ const Archived = () => {
|
||||
<p className="tip-text">{t("memo.fetching-data")}</p>
|
||||
</div>
|
||||
) : archivedMemos.length === 0 ? (
|
||||
<div className="tip-text-container">
|
||||
<p className="tip-text">{t("memo.no-archived-memos")}</p>
|
||||
<div className="w-full mt-16 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="archived-memos-container">
|
||||
|
@ -14,6 +14,7 @@ import DailyMemo from "@/components/DailyMemo";
|
||||
import i18n from "@/i18n";
|
||||
import { findNearestLanguageMatch } from "@/utils/i18n";
|
||||
import { convertToMillis, getDateStampByDate, getNormalizedDateString, getTimeStampByDate } from "@/helpers/datetime";
|
||||
import Empty from "@/components/Empty";
|
||||
|
||||
const DailyReview = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -117,7 +118,7 @@ const DailyReview = () => {
|
||||
</button>
|
||||
</div>
|
||||
<DatePicker
|
||||
className={`absolute top-8 mt-2 z-20 mx-auto border bg-white dark:bg-zinc-800 dark:border-zinc-800 rounded-lg mb-6 ${
|
||||
className={`absolute top-8 mt-2 z-20 mx-auto border bg-white shadow dark:bg-zinc-800 dark:border-zinc-800 rounded-lg mb-6 ${
|
||||
showDatePicker ? "" : "!hidden"
|
||||
}`}
|
||||
datestamp={currentDateStamp}
|
||||
@ -141,8 +142,9 @@ const DailyReview = () => {
|
||||
</div>
|
||||
</div>
|
||||
{dailyMemos.length === 0 ? (
|
||||
<div className="mx-auto pt-4 pb-5 px-0">
|
||||
<p className="italic text-gray-400">{t("daily-review.no-memos")}</p>
|
||||
<div className="w-full mt-4 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col justify-start items-start w-full mt-2">
|
||||
|
@ -9,6 +9,7 @@ import useLoading from "@/hooks/useLoading";
|
||||
import MemoFilter from "@/components/MemoFilter";
|
||||
import Memo from "@/components/Memo";
|
||||
import MobileHeader from "@/components/MobileHeader";
|
||||
import Empty from "@/components/Empty";
|
||||
|
||||
interface State {
|
||||
memos: Memo[];
|
||||
@ -66,6 +67,7 @@ const Explore = () => {
|
||||
: state.memos;
|
||||
|
||||
const sortedMemos = shownMemos.filter((m) => m.rowStatus === "NORMAL");
|
||||
|
||||
const handleFetchMoreClick = async () => {
|
||||
try {
|
||||
const fetchedMemos = await memoStore.fetchAllMemos(DEFAULT_MEMO_LIMIT, state.memos.length);
|
||||
@ -87,15 +89,18 @@ const Explore = () => {
|
||||
<section className="w-full max-w-3xl min-h-full flex flex-col justify-start items-center px-4 sm:px-2 sm:pt-4 pb-8 bg-zinc-100 dark:bg-zinc-800">
|
||||
<MobileHeader showSearch={false} />
|
||||
{!loadingState.isLoading && (
|
||||
<main className="relative w-full h-auto flex flex-col justify-start items-start -mt-2">
|
||||
<main className="relative w-full h-auto flex flex-col justify-start items-start">
|
||||
<MemoFilter />
|
||||
{sortedMemos.map((memo) => {
|
||||
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} readonly={true} />;
|
||||
return <Memo key={`${memo.id}-${memo.displayTs}`} memo={memo} showCreator />;
|
||||
})}
|
||||
{isComplete ? (
|
||||
state.memos.length === 0 ? (
|
||||
<p className="w-full text-center mt-12 text-gray-600">{t("message.no-memos")}</p>
|
||||
) : null
|
||||
state.memos.length === 0 && (
|
||||
<div className="w-full mt-16 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<p className="m-auto text-center mt-4 italic cursor-pointer text-gray-500 hover:text-green-600" onClick={handleFetchMoreClick}>
|
||||
{t("memo.fetch-more")}
|
||||
|
@ -35,7 +35,7 @@ function Home() {
|
||||
<div className="flex-grow shrink w-auto px-4 sm:px-2 sm:pt-4">
|
||||
<MobileHeader />
|
||||
<div className="w-full h-auto flex flex-col justify-start items-start bg-zinc-100 dark:bg-zinc-800 rounded-lg">
|
||||
{!userStore.isVisitorMode() && <MemoEditor />}
|
||||
{!userStore.isVisitorMode() && <MemoEditor className="mb-2" />}
|
||||
<MemoFilter />
|
||||
</div>
|
||||
<MemoList />
|
||||
|
@ -56,7 +56,7 @@ const MemoDetail = () => {
|
||||
{!loadingState.isLoading && (
|
||||
<>
|
||||
<main className="relative flex-grow max-w-2xl w-full min-h-full flex flex-col justify-start items-start px-4">
|
||||
<Memo memo={state.memo} readonly showRelatedMemos />
|
||||
<Memo memo={state.memo} showCreator showRelatedMemos />
|
||||
</main>
|
||||
<div className="mt-4 w-full flex flex-row justify-center items-center gap-2">
|
||||
<Link
|
||||
|
@ -4,6 +4,7 @@ import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import useEvent from "@/hooks/useEvent";
|
||||
import { useResourceStore } from "@/store/module";
|
||||
import Icon from "@/components/Icon";
|
||||
import ResourceCard from "@/components/ResourceCard";
|
||||
@ -13,7 +14,7 @@ import Dropdown from "@/components/kit/Dropdown";
|
||||
import ResourceItem from "@/components/ResourceItem";
|
||||
import { showCommonDialog } from "@/components/Dialog/CommonDialog";
|
||||
import showCreateResourceDialog from "@/components/CreateResourceDialog";
|
||||
import useEvent from "@/hooks/useEvent";
|
||||
import Empty from "@/components/Empty";
|
||||
|
||||
const ResourcesDashboard = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -297,7 +298,10 @@ const ResourcesDashboard = () => {
|
||||
</div>
|
||||
)}
|
||||
{resourceList.length === 0 ? (
|
||||
<p className="w-full text-center text-base my-6 mt-8">{t("resource.no-resources")}</p>
|
||||
<div className="w-full mt-8 mb-8 flex flex-col justify-center items-center italic">
|
||||
<Empty />
|
||||
<p className="mt-4 text-gray-600 dark:text-gray-400">{t("message.no-data")}</p>
|
||||
</div>
|
||||
) : (
|
||||
resourceList
|
||||
)}
|
||||
|
@ -15,9 +15,9 @@ const defaultSetting: Setting = {
|
||||
};
|
||||
|
||||
const defaultLocalSetting: LocalSetting = {
|
||||
enableDoubleClickEditing: true,
|
||||
enableDoubleClickEditing: false,
|
||||
enableAutoCollapse: false,
|
||||
dailyReviewTimeOffset: 0,
|
||||
enableAutoCollapse: true,
|
||||
};
|
||||
|
||||
export const convertResponseModelUser = (user: User): User => {
|
||||
|
2
web/src/types/modules/setting.d.ts
vendored
2
web/src/types/modules/setting.d.ts
vendored
@ -13,8 +13,8 @@ interface Setting {
|
||||
|
||||
interface LocalSetting {
|
||||
enableDoubleClickEditing: boolean;
|
||||
dailyReviewTimeOffset: number;
|
||||
enableAutoCollapse: boolean;
|
||||
dailyReviewTimeOffset: number;
|
||||
}
|
||||
|
||||
interface UserLocaleSetting {
|
||||
|
Loading…
Reference in New Issue
Block a user